# Copyright 2012 Nebula, Inc.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import copy
import inspect
import logging

from django.core import urlresolvers
from django import forms
from django.forms.forms import NON_FIELD_ERRORS  # noqa
from django import template
from django.template.defaultfilters import linebreaks  # noqa
from django.template.defaultfilters import safe  # noqa
from django.template.defaultfilters import slugify  # noqa
from django.utils.encoding import force_unicode
from django.utils.importlib import import_module  # noqa
from django.utils.translation import ugettext_lazy as _
import six

from horizon import base
from horizon import exceptions
from horizon.templatetags.horizon import has_permissions  # noqa
from horizon.utils import html


LOG = logging.getLogger(__name__)


class WorkflowContext(dict):
    def __init__(self, workflow, *args, **kwargs):
        super(WorkflowContext, self).__init__(*args, **kwargs)
        self._workflow = workflow

    def __setitem__(self, key, val):
        super(WorkflowContext, self).__setitem__(key, val)
        return self._workflow._trigger_handlers(key)

    def __delitem__(self, key):
        return self.__setitem__(key, None)

    def set(self, key, val):
        return self.__setitem__(key, val)

    def unset(self, key):
        return self.__delitem__(key)


class ActionMetaclass(forms.forms.DeclarativeFieldsMetaclass):
    def __new__(mcs, name, bases, attrs):
        # Pop Meta for later processing
        opts = attrs.pop("Meta", None)
        # Create our new class
        cls = super(ActionMetaclass, mcs).__new__(mcs, name, bases, attrs)
        # Process options from Meta
        cls.name = getattr(opts, "name", name)
        cls.slug = getattr(opts, "slug", slugify(name))
        cls.permissions = getattr(opts, "permissions", ())
        cls.progress_message = getattr(opts,
                                            "progress_message",
                                            _("Processing..."))
        cls.help_text = getattr(opts, "help_text", "")
        cls.help_text_template = getattr(opts, "help_text_template", None)
        return cls


@six.add_metaclass(ActionMetaclass)
class Action(forms.Form):
    """An ``Action`` represents an atomic logical interaction you can have with
    the system. This is easier to understand with a conceptual example: in the
    context of a "launch instance" workflow, actions would include "naming
    the instance", "selecting an image", and ultimately "launching the
    instance".

    Because ``Actions`` are always interactive, they always provide form
    controls, and thus inherit from Django's ``Form`` class. However, they
    have some additional intelligence added to them:

    * ``Actions`` are aware of the permissions required to complete them.

    * ``Actions`` have a meta-level concept of "help text" which is meant to be
      displayed in such a way as to give context to the action regardless of
      where the action is presented in a site or workflow.

    * ``Actions`` understand how to handle their inputs and produce outputs,
      much like :class:`~horizon.forms.SelfHandlingForm` does now.

    ``Action`` classes may define the following attributes in a ``Meta``
    class within them:

    .. attribute:: name

        The verbose name for this action. Defaults to the name of the class.

    .. attribute:: slug

        A semi-unique slug for this action. Defaults to the "slugified" name
        of the class.

    .. attribute:: permissions

        A list of permission names which this action requires in order to be
        completed. Defaults to an empty list (``[]``).

    .. attribute:: help_text

        A string of simple help text to be displayed alongside the Action's
        fields.

    .. attribute:: help_text_template

        A path to a template which contains more complex help text to be
        displayed alongside the Action's fields. In conjunction with
        :meth:`~horizon.workflows.Action.get_help_text` method you can
        customize your help text template to display practically anything.
    """

    def __init__(self, request, context, *args, **kwargs):
        if request.method == "POST":
            super(Action, self).__init__(request.POST, initial=context)
        else:
            super(Action, self).__init__(initial=context)

        if not hasattr(self, "handle"):
            raise AttributeError("The action %s must define a handle method."
                                 % self.__class__.__name__)
        self.request = request
        self._populate_choices(request, context)
        self.required_css_class = 'required'

    def __unicode__(self):
        return force_unicode(self.name)

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self.slug)

    def _populate_choices(self, request, context):
        for field_name, bound_field in self.fields.items():
            meth = getattr(self, "populate_%s_choices" % field_name, None)
            if meth is not None and callable(meth):
                bound_field.choices = meth(request, context)

    def get_help_text(self, extra_context=None):
        """Returns the help text for this step."""
        text = ""
        extra_context = extra_context or {}
        if self.help_text_template:
            tmpl = template.loader.get_template(self.help_text_template)
            context = template.RequestContext(self.request, extra_context)
            text += tmpl.render(context
)
        else:
            text += linebreaks(force_unicode(self.help_text))
        return safe(text)

    def add_error(self, message):
        """Adds an error to the Action's Step based on API issues."""
        self.errors[NON_FIELD_ERRORS] = self.error_class([message])

    def handle(self, request, context):
        """Handles any requisite processing for this action. The method should
        return either ``None`` or a dictionary of da
ta to be passed to
        :meth:`~horizon.workflows.Step.contribute`.

        Returns ``None`` by default, effectively making it a no-op.
        """
        return None


class MembershipAction(Action):
    """An action that allows a user to add/remove members from a group.

    Extend the Action class with additional helper method for membership
    management.
    """
    def get_default_role_field_name(self):
        return "default_" + self.slug + "_role"

    def get_member_field_name(self, role_id):
        return self.slug + "_role_" + role_id


class Step(object):
    """A step is a wrapper around an action which defines its context in a
    workflow. It knows about details such as:

    * The workflow's context data (data passed from step to step).

    * The data which must be present in the context to begin this step (the
      step's dependencies).

    * The keys which will be added to the context data upon completion of the
      step.

    * The connections between this step's fields and changes in the context
      data (e.g. if that piece of data changes, what needs to be updated in
      this step).

    A ``Step`` class has the following attributes:

    .. attribute:: action

        The :class:`~horizon.workflows.Action` class which this step wraps.

    .. attribute:: depends_on

        A list of context data keys which this step requires in order to
        begin interaction.

    .. attribute:: contributes

        A list of keys which this step will contribute to the workflow's
        context data. Optional keys should still be listed, even if their
        values may be set to ``None``.

    .. attribute:: connections

       