# -*- coding: utf-8 -*-
"""
    tipfy
    ~~~~~

    Minimalist WSGI application and utilities for App Engine.

    :copyright: 2010 by tipfy.org.
    :license: BSD, see LICENSE.txt for more details.
"""
import logging
import os
from wsgiref.handlers import CGIHandler

# Werkzeug swiss knife.
# Need to import werkzeug first otherwise py_zipimport fails.
import werkzeug
from werkzeug import (cached_property, escape, import_string, redirect,
    Request as WerkzeugRequest, Response as WerkzeugResponse, url_quote)
from werkzeug.exceptions import (abort, BadGateway, BadRequest, Forbidden,
    Gone, HTTPException, InternalServerError, LengthRequired,
    MethodNotAllowed, NotAcceptable, NotFound, NotImplemented,
    PreconditionFailed, RequestEntityTooLarge, RequestTimeout,
    RequestURITooLarge, ServiceUnavailable, Unauthorized,
    UnsupportedMediaType)
from werkzeug.routing import (BaseConverter, EndpointPrefix, Map,
    RequestRedirect, Rule as WerkzeugRule, RuleFactory, RuleTemplate,
    Subdomain, Submount)

try:
    # We declare the namespace to be used outside of App Engine, so that
    # we can distribute and install separate extensions.
    __import__('pkg_resources').declare_namespace(__name__)
except ImportError, e:
    pass

__version__ = '0.6.3'
__version_info__ = tuple(int(n) for n in __version__.split('.'))

#: Default configuration values for this module. Keys are:
#:
#: apps_installed
#:     A list of active app modules as a string. Default is an empty list.
#:
#: apps_entry_points
#:     URL entry points for the installed apps, in case their URLs are mounted
#:     using base paths.
#:
#: middleware
#:     A list of middleware classes for the WSGIApplication. The classes can
#:     be defined as strings. They define hooks that plug into the application
#:     to initialize stuff when the app is built, at the start or end of a
#:     request or to handle exceptions. Default is an empty list.
#:
#: server_name
#:     A server name hint, used to calculate current subdomain.
#:     If you plan to use dynamic subdomains, you must define the main domain
#:     here so that the subdomain can be extracted and applied to URL rules.
#:
#: subdomain
#:     Force this subdomain to be used instead of extracting
#:     the subdomain from the current url.
#:
#: dev
#:     True is this is the development server, False otherwise.
#:     Default is the value of ``os.environ['SERVER_SOFTWARE']``.
#:
#: app_id
#:     The application id. Default is the value of
#:     ``os.environ['APPLICATION_ID']``.
#:
#: version_id
#:     The current deplyment version id. Default is the value
#:     of ``os.environ['CURRENT_VERSION_ID']``.
default_config = {
    'apps_installed': [],
    'apps_entry_points': {},
    'middleware': [],
    'server_name': None,
    'subdomain': None,
    'dev': os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'),
    'app_id': os.environ.get('APPLICATION_ID', None),
    'version_id': os.environ.get('CURRENT_VERSION_ID', '1'),
}

# Allowed request methods.
ALLOWED_METHODS = frozenset(['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT',
    'TRACE'])
# Value used for required values.
REQUIRED_VALUE = object()
# Value used for missing default values.
DEFAULT_VALUE = object()


class RequestHandler(object):
    """Base class to handle requests. Implements the minimal interface
    required by :class:`Tipfy`.

    The dispatch method implements a middleware system to execute hooks before
    and after processing a request and to handle exceptions.
    """
    #: A list of middleware classes or callables. A middleware can implement
    #: three methods that are called before and after the current request
    #: method is executed, or if an exception occurs:
    #:
    #: pre_dispatch(handler)
    #:     Called before the requested method is
    #:     executed. If returns a response, stops the middleware chain and
    #:     uses that response, not calling the requested method.
    #:
    #: post_dispatch(handler, response)
    #:     Called after the requested method is executed. Must always return
    #:     a response. All *post_dispatch* middleware are always executed.
    #:
    #: handle_exception(exception, handler)
    #:     Called if an exception occurs.
    middleware = []

    def __init__(self, app, request):
        """Initializes the handler.

        :param app:
            A :class:`Tipfy` instance.
        :param request:
            A :class:`Request` instance.
        """
        self.app = app
        self.request = request

    def __call__(self, _method, *args, **kwargs):
        """Executes a handler method. This is called by :class:`Tipfy` and
        must return a :class:`Response` object.

        :param _method:
            The method to be dispatched, normally the request method in
            lower case, e.g., 'get', 'post', 'head' or 'put'.
        :param kwargs:
            Keyword arguments from the matched :class:`Rule`.
        :return:
            A :class:`Response` instance.
        """
        method = getattr(self, _method, None)
        if method is None:
            # 405 Method Not Allowed.
            # The response MUST include an Allow header containing a
            # list of valid methods for the requested resource.
            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.6
            self.abort(405, valid_methods=get_valid_methods(self))

        if not self.middleware:
            # No middleware is set: just execute the method.
            return method(*args, **kwargs)

        # Get all middleware for this handler.
        middleware = self.app.get_middleware(self, self.middleware)

        # Execute pre_dispatch middleware.
        for hook in middleware.get('pre_dispatch', []):
            response = hook(self)
            if response is not None:
                break
        else:
            try:
                # Execute the requested method.
                response = method(*args, **kwargs)
            except Exception, e:
                # Execute handle_exception middleware.
                for hook in middleware.get('handle_exception', []):
                    response = hook(e, self)
                    if response is not None:
                        break
                else:
                    raise

        # Make sure we have a response object.
        response = self.app.make_response(self.request, response)

        # Execute post_dispatch middleware.
        for hook in middleware.get('post_dispatch', []):
            response = hook(self, response)

        # Done!
        return response

    def dispatch(self, _method, *args, **kwargs):
        """Deprecated method: a wrapper for :meth:`__call__`."""
        return self(_method, *args, **kwargs)

    def abort(self, code, *args, **kwargs):
        """Raises an :class:`HTTPException`. This stops code execution,
        leaving the HTTP exception to be handled by an exception handler.

        :param code:
            HTTP status error code (e.g., 404).
        :param args:
            Positional arguments to be passed to the exception class.
        :param kwargs:
            Keyword arguments to be passed to the exception class.
        """
        abort(code, *args, **kwargs)

    def get_config(self, module, key=None, default=REQUIRED_VALUE):
        """Returns a configuration value for a module.

        .. seealso:: :meth:`Config.get_or_load`.
        """
        return self.app.config.get_or_load(module, key=key, default=default)

    def handle_exception(self, exception=None, debug=False):
        """Handles an exception. The default behavior is to re-raise the
        exception (no exception handling is implemented).

        :param exception:
            The exception that was thrown.
        :param debug:
            True if the exception should be handled in debug mode.
        """
        raise

    def redirect(self, location, code=302):
        """Issues an HTTP redirect to the given URL. This won't stop
        code execution. You must return when calling this method::

            return self.redirect('/some-path')

        :param location:
            An absolute URI.
        :param code:
            The HTTP status code for the redirect.
        :return:
            A :class:`Response` object with headers set for redirection.
        """
        return redirect(location, code)

    def redirect_to(self, _name, _code=302, **kwargs):
        """Convenience method mixing :meth:`redirect` and :meth:`url_for`:
        redirects the client to a URL built using a named :class:`Rule`.

        :param _name:
            The rule name.
        :param _code:
            The HTTP status code for the redirect.
        :param kwargs:
            Keyword arguments to build the URL.
        :return:
            A :class:`Response` object with headers set for redirection.
        """
        return self.redirect(self.url_for(_name, **kwargs), code=_code)

    def url_for(self, _name, **kwargs):
        """Builds a URL for a named :class:`Rule`.

        .. seealso:: :meth:`Request.url_for`.
        """
        return self.request.url_for(_name, **kwargs)


class Request(WerkzeugRequest):
    """The :class:`Request` object contains all environment variables for the
    current request: GET, POST, FILES, cookies and headers. Additionally
    it stores the URL adapter bound to the request and information about the
    matched URL rule.
    """
    #: URL adapter bound to a request.
    url_adapter = None
    #: Matched URL rule for a request.
    rule = None
    #: Keyword arguments from the matched rule.
    rule_args = None
    #: Exception raised when matching URL rules, if any.
    routing_exception = None

    def __init__(self, environ):
        """Initializes the request. This also sets a context attribute to
        hold variables valid for a single request.
        """
        super(Request, self).__init__(environ)

        # A registry for objects in use during a request.
        self.registry = {}

        # A context for template variables.
        self.context = {}

    def url_for(self, endpoint, _full=False, _method=None, _anchor=None,
        **kwargs):
        """Builds and returns a URL for a named :class:`Rule`.

        For example, if you have these rules registered in the application:

        .. code-block::

            Rule('/', endoint='home/main' handler='handlers.MyHomeHandler')
            Rule('/wiki', endoint='wiki/start' handler='handlers.WikiHandler')

        Here are some examples of how to generate URLs for them:

        >>> url = url_for('home/main')
        >>> '/'
        >>> url = url_for('home/main', _full=True)
        >>> 'http://localhost:8080/'
        >>> url = url_for('wiki/start')
        >>> '/wiki'
        >>> url = url_for('wiki/start', _full=True)
        >>> 'http://localhost:8080/wiki'
        >>> url = url_for('wiki/start', _full=True, _anchor='my-heading')
        >>> 'http://localhost:8080/wiki#my-heading'

        :param endpoint:
            The rule endpoint.
        :param _full:
            If True, returns an absolute URL. Otherwise, returns a
            relative one.
        :param _method:
            The rule request method, in case there are different rules
            for different request methods.
        :param _anchor:
            An anchor to add to the end of the URL.