import six
import inspect
import functools
import importlib

from collections import defaultdict
from django.conf import urls
from .patterns import URLPattern


class URLView(object):
    """
        Abstract class with url and name.

        Since URLs can't be changed on runtime it is kind of pointless
        to have ``get_url`` or ``get_url_name`` methods. So there are not any.
    """

    url = NotImplemented
    url_name = NotImplemented
    url_priority = None


def url_view(url_pattern, name=None, priority=None):
    """
    Decorator for registering functional views.
    Meta decorator syntax has to be used in order to accept arguments.

    This decorator does not really do anything that magical:

    This:
    >>> from urljects import U, url_view
    >>> @url_view(U / 'my_view')
    ... def my_view(request)
    ...     pass

    is equivalent to this:
    >>> def my_view(request)
    ...     pass
    >>> my_view.urljects_view = True
    >>> my_view.url = U / 'my_view'
    >>> my_view.url_name = 'my_view'

    Those view are then supposed to be used with ``view_include`` which will
    register all views that have ``urljects_view`` set to ``True``.

    :param url_pattern: regex or URLPattern or anything passable to url()
    :param name: name of the view, __name__ will be used otherwise.
    :param priority: priority of the view, the lower the better
    """

    def meta_wrapper(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        wrapper.urljects_view = True
        wrapper.url = url_pattern
        wrapper.url_name = name or func.__name__
        wrapper.url_priority = priority

        return wrapper
    return meta_wrapper


def resolve_name(view):
    """
    Auto guesses name of the view.
    For function it will be ``view.__name__``
    For classes it will be ``view.url_name``
    """
    if inspect.isfunction(view):
        return view.__name__
    if hasattr(view, 'url_name'):
        return view.url_name
    if isinstance(view, six.string_types):
        return view.split('.')[-1]
    return None


def url(url_pattern, view, kwargs=None, name=None):
    """
    This is replacement for ``django.conf.urls.url`` function.
    This url auto calls ``as_view`` method for Class based views and resolves
    URLPattern objects.

    If ``name`` is not specified it will try to guess it.

    :param url_pattern: string with regular expression or URLPattern
    :param view: function/string/class of the view
    :param kwargs: kwargs that are to be passed to view
    :param name: name of the view, if empty it will be guessed
    """
    # Special handling for included view
    if isinstance(url_pattern, URLPattern) and isinstance(view, tuple):
        url_pattern = url_pattern.for_include()

    if name is None:
        name = resolve_name(view)

    if callable(view) and hasattr(view, 'as_view') and callable(view.as_view):
        view = view.as_view()

    return urls.url(
        regex=url_pattern,
        view=view,
        kwargs=kwargs,
        name=name)


def view_include(view_module, namespace=None, app_name=None):
    """
    Includes view in the url, works similar to django include function.
    Auto imports all class based views that are subclass of ``URLView`` and
    all functional views that have been decorated with ``url_view``.

    :param view_module: object of the module or string with importable path
    :param namespace: name of the namespaces, it will be guessed otherwise
    :param app_name: application name
    :return: result of urls.include
    """

    # since Django 1.8 patterns() are deprecated, list should be used instead
    # {priority:[views,]}
    view_dict = defaultdict(list)

    if isinstance(view_module, six.string_types):
        view_module = importlib.import_module(view_module)

    # pylint:disable=unused-variable
    for member_name, member in inspect.getmembers(view_module):
        is_class_view = inspect.isclass(member) and issubclass(member, URLView)
        is_func_view = (inspect.isfunction(member) and
                        hasattr(member, 'urljects_view') and
                        member.urljects_view)

        if (is_class_view and member is not URLView) or is_func_view:
            view_dict[member.url_priority].append(
                url(member.url, member, name=member.url_name))

    view_patterns = list(*[
        view_dict[priority] for priority in sorted(view_dict)
        ])

    return urls.include(
        arg=view_patterns,
        namespace=namespace,
        app_name=app_name)