#!/usr/bin/env python
# -*- mode:python; tab-width: 2; coding: utf-8 -*-

"""
processors
"""

from __future__ import absolute_import

__author__ = "caelum - http://caelum.com.br"
__modified_by__  = "Carlos Martin <cmartin@liberalia.net>"
__license__ = "See LICENSE.restfulie for details"

# Import here any required modules.
from copy import copy
from urlparse import urlparse

__all__ = [
    'AuthenticationProcessor',
    'ExecuteRequestProcessor',
    'PayloadMarshallingProcessor',
    'RedirectProcessor',
    ]

# Project requirements
from tornado.gen import engine, Task
from tornado.httputil import url_concat
from tornado.httpclient import HTTPClient, AsyncHTTPClient

# local submodule requirements
from .converters import Converters
from .response import Response
from .request import Request


#pylint: disable-msg=R0903
class RequestProcessor(object):
    """Base class for all processors"""

    def execute(self, callback, chain, request, env):
        """Command called to be runned"""
        raise NotImplementedError('Subclasses must implement this method')


#pylint: disable-msg=R0903, R0922
class AuthenticationProcessor(RequestProcessor):
    """Abstract class for authentication methods"""

    backends = {}

    def execute(self, callback, chain, request, env):
        if len(request.credentials):
            #pylint: disable-msg=E1101
            path = urlparse(request.uri).path or "/"
            for math, method, credentials in request.credentials.itervalues():
                if not math.match(path):
                    continue
                if method not in self.backends:
                    error = "Unsupported auth mechanism '%s'" % method
                    raise NotImplementedError(error)
                return method, credentials


#pylint: disable-msg=R0903, R0922
class AuthenticationSyncProcessor(AuthenticationProcessor):
    """Abstract class for authentication methods. Used to sign"""

    def execute(self, callback, chain, request, env):
        method = AuthenticationProcessor.execute
        method, cred = method(self, callback, chain, request, env)
        method and self.backends[method].authorize_sync(cred, request, env)
        # follow if no method used
        return chain.follow(callback, request, env)


class AuthenticationAsyncProcessor(AuthenticationProcessor):
    """Abstract class for authentication methods. Async"""

    @engine
    def execute(self, callback, chain, request, env):
        assert not chain or len(chain) == 0
        method = AuthenticationProcessor.execute
        method, cred = method(self, callback, chain, request, env)
        self.backends[method].authorize(cred, request, env, callback)
        
class MetaAuth(type):
    """Auth Metaclass"""

    def __init__(mcs, name, bases, dct):
        type.__init__(mcs, name, bases, dct)
        if name.endswith("Auth"):
            implements = mcs.implements
            AuthenticationProcessor.backends[implements] = mcs()


#pylint: disable-msg=R0921
class AuthMixin(object):
    """Base class to derive authentication mechanisms"""

    __metaclass__ = MetaAuth

    def authorize(self, credentials, request, env):
        """Command called to be runned"""
        raise NotImplementedError('Subclasses must implement this method')


class ExecuteRequestProcessor(RequestProcessor):
    """
    Processor responsible for getting the body from environment and
    making a request with it.
    """

    @staticmethod
    def _sync(request, env):
        """Run blocked"""
        request  = HTTPClient()
        response = request.fetch(
            url_concat(request.uri, env["params"]),
            method=request.verb,
            body=env.get("body"), 
            headers=request.headers)
        return Response(response)

    @staticmethod
    def _async(callback, request, env):
        """Run async"""
        request  = AsyncHTTPClient()
        response = request.fetch(
            url_concat(request.uri, env["params"]),
            lambda x: callback(Response(x)),
            method=request.verb,
            body=env.get("body"), 
            headers=request.headers)
        return request

    def execute(self, callback, chain, request, env):
        return self._sync(request, env) \
            if not callable(callback)   \
            else self._async(callback, request, env)


class PayloadMarshallingProcessor(RequestProcessor):
    """Responsible for marshalling the payload in environment"""

    def execute(self, callback, chain, request, env):
        if env["payload"]:
            content_type = request.headers.get("content-type")
            marshaller   = Converters.marshaller_for(content_type)
            env["body"]  = marshaller.marshal(env["payload"])
            del(env["payload"])
        return chain.follow(callback, request, env)


class RedirectProcessor(RequestProcessor):
    """
    A processor responsible for redirecting a client to another URI
    when the server returns the location header and a response code
    related to redirecting.
    """
    REDIRECT_CODES = ['201', '301', '302']

    def _redirect(self, result):
        """Get redirection url, if any"""
        return result.headers.get("location") \
            if (result.code in self.REDIRECT_CODES) else None

    def execute(self, callback, chain, request, env):
        def _on_resource(resource):
            """resource callback"""
            assert callable(callback)
            # copy configuration and update url
            url = self._redirect(resource)
            if not url:
                return callback(resource)
            # make a new request
            config = copy(request.config)
            config.uri = url
            return Request(self)(callback)
        # chain
        return chain.follow(_on_resource, request, env)    \
            if callable(callback) else chain.follow(callback, request, env)


# Main tornado chain (Use follow_redirects so RedirectProcessor is not
# required anymore

#pylint:disable-msg=C0103
tornado_chain = [
    PayloadMarshallingProcessor(),
    AuthenticationSyncProcessor(),
#   RedirectProcessor(),
    ExecuteRequestProcessor(),
    ]

auth_chain = [
    PayloadMarshallingProcessor(),
    AuthenticationAsyncProcessor(),
]
