# -*- coding: utf-8 -*-
"Flask pluggable view mixins"

import copy
from itertools import dropwhile, ifilter
import logging

from flask import abort, current_app, redirect, render_template, request, \
    session, url_for, views, jsonify, Response


log = logging.getLogger(__name__)


class RequiresSession(object):
    """
    View mixin which redirects to session expired page if no session
    """

    session_expired_url = '/session-expired'

    def dispatch_request(self, *args, **kwargs):
        if not (session or session.is_current) \
                and request.method.lower() != 'options':
            return redirect(self.session_expired_url)
        return super(RequiresSession, self).dispatch_request(*args, **kwargs)


class ValidFormOnOptions(object):

    def options(self, *args, **kwargs):
        """
        Returns validation errors if a form is submitted to it otherwise
        returns Response with allowed methods
        """
        if request.content_type == 'application/x-www-form-urlencoded':
            self.form.validate()
            return jsonify(self.form.errors)
        rv = Response()
        rv.allow.update(self.methods)
        return rv


class HasFormMixin(object):
    form_class = None

    @property
    def form(self):
        """
        Instance of the form for this view. Prepopulated with any POST and
        session data.
        """
        if getattr(self, '_form', None) is None:
            self._form = self.form_class(
                request.form,
                **session.checker.get(self.form_class.__name__, {}))
        return self._form


class SessionBackedFormView(
    HasFormMixin,
    RequiresSession,
    views.MethodView,
    ValidFormOnOptions,
    object
):
    """
    Saves and loads form data to and from the session
    """

    template = None

    def get(self, *args, **kwargs):
        """
        Render template with form
        """
        return render_template(self.template, form=self.form)

    def post(self, *args, **kwargs):
        """
        Update session with form data if valid, remove from session if not.
        """
        is_submitted = getattr(self.form, 'is_submitted', lambda: True)
        if is_submitted() and self.form.validate():
            self.save_form_data_in_session()
            return self.on_valid_submit()

        self.remove_form_data_from_session()
        return self.get(*args, **kwargs)

    def save_form_data_in_session(self):
        """
        Store the form data in the session
        """
        session.checker[self.form_class.__name__] = dict(self.form.data.items())
        session.checker[self.form_class.__name__]['is_completed'] = True

    def remove_form_data_from_session(self):
        """
        Remove the form data from the session
        """
        if self.form_class.__name__ in session.checker:
            del session.checker[self.form_class.__name__]

    def on_valid_submit(self):
        """
        Handle a valid form submission
        """
        raise NotImplementedError


class AllowSessionOverride(object):
    """
    Mixin which allows overriding session vars with URL parameters
    """

    def get(self, *args, **kwargs):
        """
        Inject URL parameters into the session (if in debug mode)
        """
        if current_app.debug:
            parsed = {}
            for arg, val in request.args.items():
                form, _, field = arg.partition('_')
                parsed[form] = parsed.get(form, {})
                parsed[form].update({
                    field: val})
            session.checker.update(parsed)

        return super(AllowSessionOverride, self).get(*args, **kwargs)


class FormWizard(SessionBackedFormView):
    """
    A sequence of forms
    """

    steps = []

    def __init__(self, name):
        super(FormWizard, self).__init__()
        self.name = name
        self._steps = {}

        def add_step(step):
            name, step = step
            step = copy.copy(step)
            step.wizard = self
            step.name = name
            self._steps[name] = step
            return step

        self.steps = map(add_step, self.steps)

    def dispatch_request(self, *args, **kwargs):
        """
        Get the form and template for the current step
        """
        step = kwargs.get('step')
        if step not in self._steps:
            abort(404)

        self.step = self._steps[step]
        self.form_class = self.step.form_class
        self.template = self.step.template
        return super(FormWizard, self).dispatch_request(*args, **kwargs)

    def get(self, *args, **kwargs):
        if hasattr(self.step, 'render'):
            return self.step.render(*args, **kwargs)
        return super(FormWizard, self).get(*args, **kwargs)

    def remaining_steps(self, skip_current=False):
        """
        Get a list of the remaining steps in the wizard
        """
        if not hasattr(self, 'step'):
            self.step = self.steps[0]
        previous = lambda step: step != self.step

        def relevant(step):
            if step == self.step:
                return not skip_current
            return not self.skip(step)

        return ifilter(relevant, dropwhile(previous, self.steps))

    def next_url(self):
        """
        Get the URL for next step of the wizard
        """
        step = self.remaining_steps(skip_current=True).next()
        return self.url_for(step.name)

    def url_for(self, step_name):
        return url_for('.{0}'.format(self.name), step=step_name)

    def skip(self, step):
        """
        Skip steps if certain conditions are met
        """
        return False

    def on_valid_submit(self):
        """
        Delegate handling form submission to current state
        """
        try:
            return self.step.on_valid_submit()
        except StopIteration:
            return self.complete()

    def complete(self):
        raise NotImplementedError

    @classmethod
    def as_view(cls, name, *class_args, **class_kwargs):
        """
        Converts the class into a view function.
        """
        def view(*args, **kwargs):
            self = view.view_class(name, *class_args, **class_kwargs)
            return self.dispatch_request(*args, **kwargs)

        if cls.decorators:
            view.__name__ = name
            view.__module__ = cls.__module__
            for decorator in cls.decorators:
                view = decorator(view)

        view.view_class = cls
        view.__name__ = name
        view.__doc__ = cls.__doc__
        view.__module__ = cls.__module__
        view.methods = cls.methods
        return view


class FormWizardStep(object):
    """
    A step in a form wizard
    """
    is_completed = False

    def __init__(self, form_class, template):
        self.form_class = form_class
        self.template = template
        self.wizard = None
        self.name = None

    @property
    def form(self):
        return self.wizard.form

    def get(self, **kwargs):
        return self.wizard.get(**kwargs)

    def on_valid_submit(self):
        return redirect(self.wizard.next_url())

@@ -962,32 +962,8 @@
   if
- request.content_type ==
  'ap
@@ -994,16 +994,40 @@
 encoded'
+ in request.content_type
 :%0A      

allow for content_type in different formats