#-----------------------------------------------------------------------------
# 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 Base, enable_widget, MAX_WIDTH
from .select import CatSelector
from .add import CatAdder
from .search import Search


class CatGUI(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
        catalogs used to initalize the select
    done_callback: func, opt
        called when the object's main job has completed. In this case,
        selecting catalog(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, done_callback=None, **kwargs):
        self._cats = cats
        self.panel = pn.Column(name='Catalogs', width_policy='max', max_width=MAX_WIDTH)
        self.done_callback = done_callback or (lambda x: x)

        self.add_widget = pn.widgets.Toggle(
            name='＋',
            value=False,
            disabled=False,
            width=50)

        self.remove_widget = pn.widgets.Button(
            name='―',
            disabled=True,
            width=50)

        self.search_widget = pn.widgets.Toggle(
            name='🔍',
            value=False,
            disabled=True,
            width=50)

        self.controls = [
            self.add_widget,
            self.remove_widget,
            self.search_widget,
        ]
        self.control_panel = pn.Row(name='Controls', margin=0)

        self.select = CatSelector(cats=self._cats,
                                  done_callback=self.callback)
        self.add = CatAdder(done_callback=self.select.add,
                            visible=self.add_widget.value,
                            visible_callback=partial(setattr, self.add_widget, 'value'))
        self.search = Search(cats=self.cats,
                             done_callback=self.select.add,
                             visible=self.search_widget.value,
                             visible_callback=partial(setattr, self.search_widget, 'value'))

        self.children = [
            self.select.panel,
            self.control_panel,
            self.search.panel,
            self.add.panel,
        ]

        super().__init__(**kwargs)

    def setup(self):
        self.watchers = [
            self.add_widget.link(self.add, value='visible'),
            self.search_widget.param.watch(self.on_click_search_widget, 'value'),
            self.remove_widget.param.watch(self.select.remove_selected, 'clicks'),
        ]

    @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.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.search.visible = False
            self.add.visible = False
            self._panel.clear()
        if self.visible_callback:
            self.visible_callback(visible)

    def callback(self, cats):
        """When a catalog is selected, enable widgets that depend on that condition
        and do done_callback"""
        enable = bool(cats)
        if not enable:
            # close search if it is visible
            self.search.visible = False
        enable_widget(self.search_widget, enable)
        enable_widget(self.remove_widget, enable)

        if self.done_callback:
            self.done_callback(cats)

    def on_click_search_widget(self, event):
        """ When the search control is toggled, set visibility and hand down cats"""
        self.search.cats = self.cats
        self.search.visible = event.new
        if self.search.visible:
            self.search.watchers.append(
                self.select.widget.link(self.search, value='cats'))

    @property
    def cats(self):
        """Cats that have been selected from the select"""
        return self.select.selected

    def __getstate__(self):
        """Serialize the current state of the object"""
        return {
            'visible': self.visible,
            'select': self.select.__getstate__(),
            'add': self.add.__getstate__(),
            'search':  self.search.__getstate__(include_cats=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.add.__setstate__(state['add'])
            self.search.__setstate__(state['search'])
        return self

    @classmethod
    def from_state(cls, state):
        """Create a new object from a serialized verion of another one.

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