
"""
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/authomatic/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.

.. autoclass:: DjangoAdapter
    :members:

.. autoclass:: Webapp2Adapter
    :members:

.. autoclass:: WebObAdapter
    :members:

.. autoclass:: WerkzeugAdapter
    :members:

.. _implement_adapters:

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

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

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

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

.. autoclass:: BaseAdapter
    :members:

"""

import abc
from authomatic.core import Response


class BaseAdapter(object, metaclass=abc.ABCMeta):
    """
    Base class for platform adapters.

    Defines common interface for WSGI framework specific functionality.

    """

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

        :returns:
            :class:`dict`

        """

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

        :returns:
            :class:`str`

        """

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

        :returns:
            :class:`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.

        """


class DjangoAdapter(BaseAdapter):
    """
    Adapter for the |django|_ framework.
    """

    def __init__(self, request, response):
        """
        :param request:
            An instance of the :class:`django.http.HttpRequest` class.

        :param response:
            An instance of the :class:`django.http.HttpResponse` class.
        """
        self.request = request
        self.response = response

    @property
    def params(self):
        params = {}
        params.update(self.request.GET.dict())
        params.update(self.request.POST.dict())
        return params

    @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)


class WebObAdapter(BaseAdapter):
    """
    Adapter for the |webob|_ package.
    """

    def __init__(self, request, response):
        """
        :param request:
            A |webob|_ :class:`Request` instance.

        :param response:
            A |webob|_ :class:`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


class Webapp2Adapter(WebObAdapter):
    """
    Adapter for the |webapp2|_ framework.

    Inherits from the :class:`.WebObAdapter`.

    """

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


class 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 :class:`werkzeug.wrappers.Request` class.

        :param response:
            Instance of the :class:`werkzeug.wrappers.Response` class.
        """

        self.request = request
        self.response = response

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

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

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