from six import with_metaclass

from django.core.urlresolvers import reverse
from django.forms.forms import BaseForm  # , get_declared_fields
from django.forms.widgets import media_property
from django.http import HttpResponseRedirect

from nine.versions import DJANGO_GTE_1_7, DJANGO_GTE_1_8

from .constants import WIZARD_TYPE_COOKIE, WIZARD_TYPE_SESSION

if DJANGO_GTE_1_8:
    from formtools.wizard.views import (
        WizardView, SessionWizardView, CookieWizardView
    )
else:
    from django.contrib.formtools.wizard.views import (
        WizardView, SessionWizardView, CookieWizardView
    )

if DJANGO_GTE_1_7:
    from collections import OrderedDict
else:
    from django.utils.datastructures import SortedDict as OrderedDict

__title__ = 'fobi.dynamic'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2014-2016 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = (
    'assemble_form_class',
    'assemble_form_wizard_class',
)

# ****************************************************************************
# ****************************************************************************
# **************************** Form generator ********************************
# ****************************************************************************
# ****************************************************************************


def assemble_form_class(form_entry, base_class=BaseForm, request=None,
                        origin=None, origin_kwargs_update_func=None,
                        origin_return_func=None, form_element_entries=None,
                        get_form_field_instances_kwargs={}):
    """Assemble a form class by given entry.

    :param form_entry:
    :param base_class:
    :param django.http.HttpRequest request:
    :param string origin:
    :param callable origin_kwargs_update_func:
    :param callable origin_return_func:
    :param iterable form_element_entries: If given, used instead of
        ``form_entry.formelemententry_set.all`` (no additional database hit).
    :param dict get_form_field_instances_kwargs: To be passed as **kwargs to
        the :method:`get_form_field_instances_kwargs`.
    """
    if form_element_entries is None:
        form_element_entries = form_entry.formelemententry_set.all()

    # DeclarativeFieldsMetaclass
    class DeclarativeFieldsMetaclass(type):
        """Declarative fields meta class.

        Copied from ``django.forms.forms.DeclarativeFieldsMetaclass``.

        Metaclass that converts Field attributes to a dictionary called
        `base_fields`, taking into account parent class 'base_fields' as well.
        """

        def __new__(cls, name, bases, attrs):
            """New."""
            base_fields = []

            for creation_counter, form_element_entry \
                    in enumerate(form_element_entries):
                plugin = form_element_entry.get_plugin(request=request)

                # We simply make sure the plugin exists. We don't handle
                # exceptions relate to the non-existent plugins here. They
                # are instead handled in registry.
                if plugin:
                    plugin_form_field_instances = \
                        plugin._get_form_field_instances(
                            form_element_entry=form_element_entry,
                            origin=origin,
                            kwargs_update_func=origin_kwargs_update_func,
                            return_func=origin_return_func,
                            extra={'counter': creation_counter},
                            request=request,
                            form_entry=form_entry,
                            form_element_entries=form_element_entries,
                            **get_form_field_instances_kwargs
                        )
                    for form_field_name, form_field_instance \
                            in plugin_form_field_instances:
                        base_fields.append(
                            (form_field_name, form_field_instance)
                        )

            attrs['base_fields'] = OrderedDict(base_fields)
            new_class = super(DeclarativeFieldsMetaclass, cls).__new__(
                cls, name, bases, attrs
            )

            if 'media' not in attrs:
                new_class.media = media_property(new_class)

            return new_class

    # DynamicForm
    class DynamicForm(with_metaclass(DeclarativeFieldsMetaclass, base_class)):
        """Dynamically created form element plugin class."""

    # Finally, return the DynamicForm
    return DynamicForm


def assemble_form_wizard_class(form_wizard_entry, base_class=SessionWizardView,
                               request=None, origin=None,
                               origin_kwargs_update_func=None,
                               origin_return_func=None,
                               form_wizard_form_entries=None,
                               template_name=None):
    """Assemble form wizard class."""

    if form_wizard_entry.wizard_type == WIZARD_TYPE_SESSION:
        base_class = SessionWizardView
    elif form_wizard_entry.wizard_type == WIZARD_TYPE_COOKIE:
        base_class = CookieWizardView
    elif not issubclass(base_class, WizardView):
        base_class = SessionWizardView

    if form_wizard_form_entries is None:
        form_entries = [
            form_wizard_form_entry.form_entry
            for form_wizard_form_entry
            in form_wizard_entry.formwizardformentry_set.all()
        ]
    else:
        form_entries = [
            form_wizard_form_entry.form_entry
            for form_wizard_form_entry
            in form_wizard_form_entries
        ]

    # DeclarativeFormsMetaclass
    class DeclarativeFormsMetaclass(type):
        """Declarative forms meta class.

        Copied from ``django.forms.forms.DeclarativeFieldsMetaclass``.

        Metaclass that converts Forms attributes to a dictionary called
        `form_list`, taking into account parent class 'form_list' as well.
        """

        def __new__(cls, name, bases, attrs):
            """New.

            Form list should be presented in the following way:

            ..code-block: python

                named_contact_forms = (
                    ('contactdata', ContactForm1),
                    ('leavemessage', ContactForm2),
                )

            In the example above, the fist item of the tuple would be
            the slug of the form included. The second one would be the
            form class.
            """
            form_list = []

            for creation_counter, form_entry \
                    in enumerate(form_entries):

                form_cls = assemble_form_class(
                    form_entry,
                    request=request
                )

                form_list.append(
                    (form_entry.slug, form_cls)
                )

            attrs['form_list'] = form_list
            attrs['template_name'] = template_name
            new_class = super(DeclarativeFormsMetaclass, cls).__new__(
                cls, name, bases, attrs
            )

            # if 'media' not in attrs:
            #     new_class.media = media_property(new_class)

            return new_class

    # DynamicFormWizard
    class DynamicFormWizard(with_metaclass(
            DeclarativeFormsMetaclass, base_class)):
        """Dynamically created form wizard class."""

        def done(self, form_list, **kwargs):
            """Done."""
            # do_something_with_the_form_data(form_list)
            redirect_url = reverse('fobi.form_wizard_entry_submitted',
                                   [form_wizard_entry.slug])
            return HttpResponseRedirect(redirect_url)

    # Finally, return the dynamic wizard
    return DynamicFormWizard
