# -*- coding: utf-8 -*-

"""
Abstractions for complex, session-based form applications.
"""

from poobrains import app, flash, redirect, session, new_session

import poobrains.form
import poobrains.auth


@new_session
def app_sessions(session):

    session['apps'] = {}
    for cls in FormApp.class_children():
        session['apps'][cls.__name__.lower()] = {}


class FormApp(poobrains.auth.ProtectedForm):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)
        self.session = session['apps'][self.__class__.__name__.lower()]
        self.session_is_new = session == {}

    def session_delete(self):
        session['apps'][self.__class__.__name__.lower()] = {}


class BoundFormApp(FormApp):

    def __new__(cls, model_or_instance, mode=None, prefix=None, name=None, title=None, method=None, action=None):

        """ Copy of poobrains.auth.BoundForm.__new__ """

        f = super(BoundFormApp, cls).__new__(cls, prefix=prefix, name=name, title=title, method=method, action=action)

        if isinstance(model_or_instance, type(poobrains.auth.Administerable)): # hacky
            f.model = model_or_instance
            f.instance = f.model()

        else:
            f.instance = model_or_instance
            f.model = f.instance.__class__

        if hasattr(f.instance, 'menu_actions'):
            f.menu_actions = f.instance.menu_actions

        if hasattr(f.instance, 'menu_related'):
            f.menu_related = f.instance.menu_related

        return f

    def __init__(self, model_or_instance, **kwargs):

        super().__init__(**kwargs)

        self.session_is_new = False

        if not self.instance.handle_string in self.session:
            self.session[self.instance.handle_string] = {}

            self.session_is_new = True
            flash(f"Created new {self.__class__.__name__} session for '{self.instance.handle_string}'.")

        self.session = self.session[self.instance.handle_string]

    def session_delete(self):
        del(session['apps'][self.__class__.__name__.lower()][self.instance.handle_string])


class Component(poobrains.form.Fieldset):

    def __init__(self, parent, **kwargs):

        """
        `parent` must be `FormApp` (or subclass thereof) or `Component` (or subclass thereof).
        """

        super().__init__(**kwargs)

        self.parent = parent

        if isinstance(parent, FormApp):
            self.formapp = parent
        else:
            self.formapp = self.parent.formapp

        self.session_is_new = parent.session_is_new

        if not '_children' in parent.session:
            parent.session['_children'] = {}
        if not self.name in parent.session['_children']:
            parent.session['_children'][self.name] = {}
        self.session = parent.session['_children'][self.name]

    def __setattr__(self, name, value):

        if name == 'parent':
            # get around putting parent (in case it's Toolbox or MultistateFieldset)
            # into self.fields, avoiding looping references leading to infinite recursion
            super(poobrains.form.BaseForm, self).__setattr__(name, value)

        elif name == 'name' and value != self.name and hasattr(self, 'session'):

            if value in self.parent.session['_children']:
                # adopt any data already existing in session
                # a change of "name" is mainly expected when doing something like "self.foo = SomeComponent()"
                # because the name is automatically set to 'foo' by super().__setattr_
                self.session = self.parent.session['_children'][value]
            else:
                # move own session data to new location
                self.parent.session['_children'][value] = self.session
            del(self.parent.session['_children'][self.name])
            super().__setattr__(name, value)

        else:
            super().__setattr__(name, value)

    def session_delete(self):
        del(self.parent.session['_children'][self.name])

    def process_submitting_fieldset(self, submit):

        fieldset = self.find_submitting_fieldset(submit)
        child_submit = self.child_submit(submit)

        if isinstance(fieldset, Component):
            pre_return = fieldset.preprocess(child_submit) # handles 'cancel' buttons and other potential generic UI elements
            if pre_return:
                return pre_return
        return fieldset.process(child_submit)

class Toolbox(Component):

    """
        tools = {
            # format of 
            # <name>: (<label>, <form_class>)
            'foo': ('Le Foo': FooForm),
            'bar': ('Le Bar': BarForm)
        }
    """

    tools = {}

    def __init__(self, parent, **kwargs):

        super().__init__(parent, **kwargs)

        if self.session_is_new:
            #self.tool_active = next(iter(self.tools)) # first key of self.tools
            self.tool_active = None

        self.tools_enabled = set(self.tools.keys()) # enable all tools by default

        self.build_toolbar()
        self.build_tool()

    @property
    def tool_active(self):
        return self.session.get('tool_active', None)

    @tool_active.setter
    def tool_active(self, value):
        self.session['tool_active'] = value

    def tool_enable(self, name):
        if name not in self.tools:
            raise KeyError(f"Unknown tool to enable: '{name}'.")
        self.tools_enabled.add(name)

    def tool_disable(self, name):
        if name not in self.tools:
            raise KeyError(f"Unknown tool to disable: '{name}'.")
        self.tools_enabled.remove(name)

    def build_toolbar(self):
        self.toolbar = poobrains.form.Fieldset(title=f'{self.title} toolbar')

        for name, (label, form_class) in self.tools.items():

            disabled = not name in self.tools_enabled
            if name == self.tool_active:
                setattr(self.toolbar, name, poobrains.form.Button(type='submit', label=label, value=name, css_class=f'{name} active', disabled=True))
            elif self.tool_active is None:
                setattr(self.toolbar, name, poobrains.form.Button(type='submit', label=label, value=name, css_class=name, disabled=disabled))
            else:
                setattr(self.toolbar, name, poobrains.form.DoubleOptInButton('You are currently in the middle of an unfinished operation – override?', type='submit', label=label, value=name, css_class=name, disabled=disabled))

    def build_tool(self):

        if not self.tool_active is None:
            form_class = self.tools[self.tool_active][1]
            self.tool = form_class(self)#, name='tool')
        else:
            self.tool = poobrains.form.fields.Message(value="No tool selected.")

    def reset(self):
        #self.session['tool_active'] = None
        self.session.clear()

    def render(self, mode='full'):

        self.build_toolbar()
        self.build_tool()
        return super().render(mode=mode)

    def process(self, submit):

        if submit.startswith('toolbar.'):
            tool = submit.split('.')[1]
            self.session['tool_active'] = tool
            return redirect('#_')

        else:
            return self.process_submitting_fieldset(submit)


class MultistateFieldset(Component):

    @property
    def state(self):
        return self.session.get('state', 'start')

    @state.setter
    def state(self, state):
        self.session['state'] = state

    def build(self):

        if not isinstance(self.parent, FormApp): # FIXME: This condition makes no sense!
            self.cancel = poobrains.form.Button('submit', label='Cancel')

    def render(self, mode='full'):

        self.build()
        return super().render(mode=mode)

    def bind(self, values, files):

        self.build()
        super().bind(values, files)

    def preprocess(self, submit):
   
        if submit == 'cancel':

            if self.state == 'start':
                if isinstance(self.parent, Toolbox):
                    self.parent.tool_active = None
                    self.session_delete()
                else:
                    self.session.clear()
                flash('Cancelled operation.')
            else:
                self.state = 'start'
                flash('Went back.')
            return redirect('#_')


class FnordForm(MultistateFieldset):

    def build(self):

        super().build()

        if self.state == 'next_thing':
            self.do_next_thing = poobrains.form.Button('submit', label='Do next thing!')
        elif self.state == 'last_thing':
            self.do_last_thing = poobrains.form.Button('submit', label='Do last thing!')
        else:
            self.do_thing = poobrains.form.Button('submit', label='Do thing!')

    def process(self, submit):

        if submit == 'do_thing':
            self.state = 'next_thing'
        elif submit == 'do_next_thing':
            flash("A thing did the happen!")
            self.state = 'last_thing'
        elif submit == 'do_last_thing':
            self.parent.tool_active = None
            self.session_delete()
            flash("Did last thing.")

        return redirect('#_')


class EschatonForm(MultistateFieldset):

    def build(self):

        super().build()

        if self.state == 'start':
            self.title = 'The eschaton is upon us!'
            self.immanentize = poobrains.form.Button('submit', label='Immanentize!')

        else:
            self.title = 'The eschaton has been immanentized!'
            self.unimmanentized = poobrains.form.Button('submit', label='OH GODDESS MAKE IT STOP!1!!')

    def process(self, submit):

        if submit == 'immanentize':
            self.state = 'immanentization'
        else:
            self.state = 'start'

        return redirect('#_')


class FoolBox(Toolbox):

    tools = {
        'fnord': ('Fnord', FnordForm),
        'eschaton': ('Immanentize the Eschaton', EschatonForm)
    }


class FoolApp(FormApp):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.foolbox = FoolBox(self)

app.admin.add_view(FoolApp, '/fool/')
