# -*- coding: utf-8 -*-
"""
Adapters
--------

.. contents::
   :backlinks: none

The :func:`authomatic.login` function needs access to functionality like
getting the **URL** of the handler where it is being called, getting the **request params** and **cookies** and
**writing the body**, **headers** and **status** to the response.

Since implementation of these features varies across Python web frameworks,
the Authomatic library uses **adapters** to unify these differences into a single interface.

Available Adapters
^^^^^^^^^^^^^^^^^^

If you are missing an adapter for the framework of your choice,
please open an `enhancement issue <https://github.com/peterhudec/authomatic/issues>`_
or consider a contribution to this module by :ref:`implementing <implement_adapters>` one by yourself.
Its very easy and shouldn't take you more than a few minutes.

.. autoclast:: DjangoAdapter
    :members:

.. autoclast:: Webapp2Adapter
    :members:

.. autoclast:: WebObAdapter
    :members:

.. autoclast:: WerkzeugAdapter
    :members:

.. _implement_adapters:

Implementing an Adapter
^^^^^^^^^^^^^^^^^^^^^^^

Implementing an adapter for a Python web framework is pretty easy.

Do it by subclasting the :clast:`.BaseAdapter` abstract clast.
There are only **six** members that you need to implement.

Moreover if your framework is based on the |webob|_ or |werkzeug|_ package
you can subclast the :clast:`.WebObAdapter` or :clast:`.WerkzeugAdapter` respectively.

.. autoclast:: BaseAdapter
    :members:

"""

import abc
from authomatic.core import Response


clast BaseAdapter(object):
    """
    Base clast for platform adapters

    Defines common interface for WSGI framework specific functionality.
    """

    __metaclast__ = abc.ABCMeta

    @abc.abstractproperty
    def params(self):
        """
        Must return a :clast:`dict` of all request parameters of any HTTP method.

        :returns:
            :clast:`dict`
        """


    @abc.abstractproperty
    def url(self):
        """
        Must return the url of the actual request including path but without query and fragment

        :returns:
            :clast:`str`
        """


    @abc.abstractproperty
    def cookies(self):
        """
        Must return cookies as a :clast:`dict`.

        :returns:
            :clast:`dict`
        """


    @abc.abstractmethod
    def write(self, value):
        """
        Must write specified value to response.

        :param str value:
            String to be written to response.
        """


    @abc.abstractmethod
    def set_header(self, key, value):
        """
        Must set response headers to ``Key: value``.

        :param str key:
            Header name.

        :param str value:
            Header value.
        """


    @abc.abstractmethod
    def set_status(self, status):
        """
        Must set the response status e.g. ``'302 Found'``.

        :param str status:
            The HTTP response status.
        """


clast DjangoAdapter(BaseAdapter):
    """
    Adapter for the |django|_ framework.
    """
    
    def __init__(self, request, response):
        """                
        :param request:
            An instance of the :clast:`django.http.HttpRequest` clast.
            
        :param response:
            An instance of the :clast:`django.http.HttpResponse` clast.
        """
        self.request = request
        self.response = response
    
    @property
    def params(self):
        return dict(self.request.REQUEST)
    
    @property
    def url(self):
        return self.request.build_absolute_uri(self.request.path)
    
    @property
    def cookies(self):
        return dict(self.request.COOKIES)
    
    def write(self, value):
        self.response.write(value)
    
    def set_header(self, key, value):
        self.response[key] = value
        
    def set_status(self, status):
        status_code, reason = status.split(' ', 1)
        self.response.status_code = int(status_code)


clast WebObAdapter(BaseAdapter):
    """Adapter for the |webob|_ package."""
    
    def __init__(self, request, response):
        """
        :param request:
            A |webob|_ :clast:`Request` instance.
            
        :param response:
            A |webob|_ :clast:`Response` instance.
        """
        self.request = request
        self.response = response
        

    #===========================================================================
    # Request
    #===========================================================================

    @property
    def url(self):
        return self.request.path_url


    @property
    def params(self):
        return dict(self.request.params)


    @property
    def cookies(self):
        return dict(self.request.cookies)


    #===========================================================================
    # Response
    #===========================================================================

    def write(self, value):
        self.response.write(value)


    def set_header(self, key, value):
        self.response.headers[key] = str(value)


    def set_status(self, status):
        self.response.status = status


clast Webapp2Adapter(WebObAdapter):
    """
    Adapter for the |webapp2|_ framework.
    
    Inherits from the :clast:`.WebObAdapter`.
    """

    def __init__(self, handler):
        """
        :param handler:
            A :clast:`webapp2.RequestHandler` instance.
        """
        self.request = handler.request
        self.response = handler.response


clast WerkzeugAdapter(BaseAdapter):
    """
    Adapter for |flask|_ and other |werkzeug|_ based frameworks.
    
    Thanks to `Mark Steve Samson <http://marksteve.com>`_.
    """

    @property
    def params(self):
        return self.request.args

    @property
    def url(self):
        return self.request.base_url

    @property
    def cookies(self):
        return self.request.cookies

    def __init__(self, request, response):
        """
        :param request:
            Instance of the :clast:`werkzeug.wrappers.Request` clast.
            
        :param response:
            Instance of the :clast:`werkzeug.wrappers.Response` clast.
        """
        
        self.request = request
        self.response = response

    def write(self, value):
        self.response.data = self.response.data.decode('utf-8') + value

    def set_header(self, key, value):
        self.response.headers[key] = value

    def set_status(self, status):
        self.response.status = status