# -----------------------------------------------------------------------------
# Copyright (c) 2012 - 2019, Anaconda, Inc. and Intake contributors
# All rights reserved.
#
# The full license is in the LICENSE file, distributed with this software.
# -----------------------------------------------------------------------------
from functools import partial

import panel as pn

from ..base import MAX_WIDTH, Base, enable_widget
from .defined_plots import Plots
from .description import Description
from .select import SourceSelector


class SourceGUI(Base):
    """
    Top level GUI panel that contains controls and all visible sub-panels

    This class is responsible for coordinating the inputs and outputs
    of various sup-panels and their effects on each other.

    Parameters
    ----------
    cats: list of catalogs, opt
        catalogs used to initalize, provided as objects.
    sources: list of sources, opt
        sources used to initalize, provided as objects.
    done_callback: func, opt
        called when the object's main job has completed. In this case,
        selecting source(s).

    Attributes
    ----------
    children: list of panel objects
        children that will be used to populate the panel when visible
    panel: panel layout object
        instance of a panel layout (row or column) that contains children
        when visible
    watchers: list of param watchers
        watchers that are set on children - cleaned up when visible
        is set to false.
    """

    def __init__(self, cats=None, sources=None, done_callback=None, **kwargs):
        self._cats = cats
        self._sources = sources
        self.panel = pn.Column(name="Entries", width_policy="max", max_width=MAX_WIDTH)
        self.done_callback = done_callback

        self.plot_widget = pn.widgets.Toggle(name="📊", value=False, disabled=True, width=50)
        self.pars_widget = pn.widgets.Toggle(name="⚙", value=False, disabled=True, width=50)
        self.controls = [self.plot_widget, self.pars_widget]
        self.control_panel = pn.Row(name="Controls", margin=0)

        self.pars_editor = ParsEditor()
        self.select = SourceSelector(cats=self._cats, sources=self._sources, done_callback=self.callback)
        self.description = Description()
        self.description.source = self.sources

        self.plot = Plots(source=self.source_instance, visible=self.plot_widget.value, visible_callback=partial(setattr, self.plot_widget, "value"))

        super().__init__(**kwargs)

    def _setup_watchers(self):
        self.watchers = [
            self.plot_widget.param.watch(self.on_click_plot_widget, "value"),
            self.pars_widget.param.watch(self.on_click_pars_widget, "value"),
            self.select.widget.link(self.description, value="source"),
        ]
        self.plot.watch(self.on_plot_edited)

    def setup(self):
        self._setup_watchers()
        self.children = [
            pn.Column(
                pn.Row(
                    pn.Column(
                        self.select.panel,
                        self.control_panel,
                        margin=0,
                    ),
                    self.description.panel,
                    margin=0,
                ),
                self.plot.panel,
                margin=0,
                width_policy="max",
            )
        ]

    @Base.visible.setter
    def visible(self, visible):
        """When visible changed, do setup or unwatch and call visible_callback"""
        self._visible = visible

        if visible and len(self._panel.objects) == 0:
            self.setup()
            self.select.visible = True
            self.description.visible = True
            if len(self.control_panel.objects) == 0:
                self.control_panel.extend(self.controls)
            self._panel.extend(self.children)
        elif not visible and len(self._panel.objects) > 0:
            self.unwatch()
            # do children
            self.select.visible = False
            self.control_panel.clear()
            self.description.visible = False
            self.plot.visible = False
            self._panel.clear()
        if self.visible_callback:
            self.visible_callback(visible)

    def callback(self, sources):
        """When a source is selected, enable widgets that depend on that condition
        and do done_callback"""
        if hasattr(self, "plot"):
            # guard since this cannot happen until plot is ready
            self.plot.visible = False
        enable = bool(sources)
        self.plot_widget.value = False
        self.pars_widget.value = False

        enable_widget(self.plot_widget, enable)
        enable_widget(self.pars_widget, enable and sources[0]._user_parameters)
        self.pars_editor.dirty = True  # reset pars editor

        if self.done_callback:
            self.done_callback(sources)

    def on_click_plot_widget(self, event):
        """When the plot control is toggled, set visibility and hand down source"""
        self.plot.source = self.source_instance
        self.plot.visible = event.new

    def on_click_pars_widget(self, event):
        if event.new:
            pars = self.sources[0]._user_parameters
            self.pars_editor.remake(pars)
            self.description.panel.append(self.pars_editor.panel)
        else:
            self.description.panel.remove(self.pars_editor.panel)

    def on_plot_edited(self, event):
        # Redraw source YAML
        self.description.source = self.sources

    @property
    def sources(self):
        """Sources that have been selected from the source GUI"""
        return self.select.selected

    @property
    def source_instance(self):
        """DataSource from the current selection using current parameters"""
        sel = self.select.selected
        args = self.pars_editor.kwargs
        if sel:
            return sel[0](**args)

    def __getstate__(self):
        """Serialize the current state of the object"""
        return {
            "visible": self.visible,
            "select": self.select.__getstate__(),
            "description": self.description.__getstate__(include_source=False),
            "plot": self.plot.__getstate__(include_source=False),
        }

    def __setstate__(self, state):
        """Set the current state of the object from the serialized version.
        Works inplace. See ``__getstate__`` to get serialized version and
        ``from_state`` to create a new object."""
        self.visible = state.get("visible", True)
        if self.visible:
            self.select.__setstate__(state["select"])
            self.description.__setstate__(state["description"])
            self.plot.__setstate__(state["plot"])
        return self

    @classmethod
    def from_state(cls, state):
        """Create a new object from a serialized exising object.

        Example
        -------
        original = SourceGUI()
        copy = SourceGUI.from_state(original.__getstate__())
        """
        return cls(cats=[], sources=[]).__setstate__(state)


class ParsEditor(Base):
    """Edit user parameters using widgets"""

    def __init__(self):
        self.panel = pn.Column(pn.Spacer())
        self.dirty = True  # don't use kwargs until source is set

    def remake(self, upars):
        """Set up parameter widgets for given list of UserParameter objects"""
        self.panel.clear()
        for upar in upars:
            self.panel.append(self.par_to_widget(upar))
        self.dirty = False

    @property
    def kwargs(self):
        """The current selections"""
        if self.dirty:
            return {}
        else:
            return {w.name: w.value for w in self.panel}

    @staticmethod
    def par_to_widget(par):
        if par.allowed:
            w = pn.widgets.Select(options=par.allowed)
        elif par.type in ["str", "unicode"]:
            w = pn.widgets.TextInput()
        elif par.type == "int":
            w = pn.widgets.IntSlider(start=par.min, end=par.max, step=1)
        elif par.type == "float":
            w = pn.widgets.FloatSlider(start=par.min, end=par.max)
        elif par.type == "datetime":
            w = pn.widgets.DatetimeInput()
        else:
            w = pn.widgets.LiteralInput()
        w.name = par.name
        w.value = par.default
        return w
