from __future__ import unicode_literals

from builtins import object
from functools import update_wrapper

from django.conf.urls import url

from .decorators import cradminview, has_access_to_cradmin_instance

#: The name of the app index view (the landing page for the app).
#: We do not enforce this, but we assume that each app has a
#: view with this name.
INDEXVIEW_NAME = 'INDEX'


class Url(object):
    """
    Url is mostly the same as func:`django.conf.urls.url`.
    You use Url to add urls to an app.
    """
    def __init__(self, regex, view, kwargs=None, name=None):
        """
        Args:
            regex: The URL regex.
            view: The view (E.g.: ``MyView.as_view()``).
            kwargs: Keyword arguments for the view.
            name: The name of the view. This just have to be unique within
                the :class:`.App` - the actual URL name is generated
                based on the app name and the :obj:`cradmin_legacy.crinstance.BaseCrAdminInstance.id`.
        """
        self.regex = regex
        self.view = view
        self.kwargs = kwargs
        self.name = name


class App(object):
    """
    A cradmin App.

    Added to a :class:`cradmin_legacy.crinstance.BaseCrAdminInstance`
    with :obj:`cradmin_legacy.crinstance.BaseCrAdminInstance.apps`.
    """
    #: See :meth:`~.App.get_appurls`.
    appurls = []

    def __init__(self, appname, request, active_viewname):
        self.appname = appname
        self.request = request
        self.active_viewname = active_viewname

    def reverse_appurl(self, viewname, args=None, kwargs=None):
        """
        Works just like :func:`django.urls.reverse`, except
        that the name is the name given in :obj:`.appurls`, not the full
        name of the URL.

        This means that you should use this to reverse urls within this app.
        """
        return self.request.cradmin_instance.reverse_url(
            appname=self.appname, viewname=viewname,
            args=args, kwargs=kwargs)

    def reverse_appindexurl(self, args=None, kwargs=None):
        """
        Shortcut for::

            reverse_appurl(crapp.INDEXVIEW_NAME, args=args, kwargs=kwargs)
        """
        return self.reverse_appurl(viewname=INDEXVIEW_NAME, args=args, kwargs=kwargs)

    @classmethod
    def get_appurls(cls):
        """
        Get app URLs. Defaults to :obj:`~.App.appurls`.

        Returns:
            A list of :class:`.Url` objects.
        """
        return cls.appurls

    @classmethod
    def _wrap_view(cls, appname, view, viewname):
        def viewwrapper(request, *args, **kwargs):
            request.cradmin_app = cls(appname, request, active_viewname=viewname)
            return has_access_to_cradmin_instance(cradminview(view))(request, *args, **kwargs)

        # Take name and docstring from view
        update_wrapper(viewwrapper, view, updated=())

        return viewwrapper

    @classmethod
    def build_urls(cls, cradmin_instance_id, appname):
        """
        Used internally by :meth:`cradmin_legacy.crinstance.BaseCrAdminInstance.urls`
        to build urls for all views in the app.
        """
        urls = []
        for pattern in cls.get_appurls():
            urls.append(
                url(
                    pattern.regex, cls._wrap_view(appname, pattern.view, pattern.name),
                    name='{}-{}-{}'.format(cradmin_instance_id, appname, pattern.name),
                    kwargs=pattern.kwargs))
        return urls
