#-----------------------------------------------------------------------------
# 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 collections import OrderedDict

import intake
from intake.utils import remake_instance
import panel as pn

from ..base import BaseSelector, coerce_to_list


class SourceSelector(BaseSelector):
    """
    The source selector takes a variety of inputs such as cats or sources
    and uses those to populate a select widget containing all the sources.

    Once the source selector is populated with these options, the user can
    select which source(s) are of interest. These sources are stored on
    the ``selected`` property of the class.

    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
    ----------
    selected: list of sources
        list of selected sources
    items: list of sources
        list of all the source values represented in widget
    labels: list of str
        list of labels for all the sources represented in widget
    options: dict
        dict of widget labels and values (same as `dict(zip(self.labels, self.values))`)
    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.
    """
    preprocess = None
    children = []

    def __init__(self, sources=None, cats=None, done_callback=None, **kwargs):
        """Set sources or cats to initialize the class - sources trumps cats.

        The order of the calls in this method matters and is different
        from the order in other panel init methods because the top level
        gui class needs to be able to watch these widgets.
        """
        self.panel = pn.Column(name='Select Data Source', margin=0)
        self.widget = pn.widgets.MultiSelect(size=9, min_width=200, width_policy='min')
        self.done_callback = done_callback
        super().__init__(**kwargs)

        if sources is not None:
            self.items = sources
        elif cats is not None:
            self.cats = cats

    def setup(self):
        label = pn.pane.Markdown('#### Sources', max_height=40)
        self.watchers = [
            self.widget.param.watch(self.callback, 'value'),
        ]
        self.children = [label, self.widget]

    @property
    def cats(self):
        """Cats represented in the sources options"""
        return set(source._catalog for source in self.items)

    @cats.setter
    def cats(self, cats):
        """Set sources from a list of cats"""
        sources = []
        for cat in coerce_to_list(cats):
            for k, entry in cat._entries.items():
                try:
                    if entry.describe()['container'] != 'catalog':
                        sources.append(entry)
                except IOError:
                    # loading cat might fail here
                    pass
        self.items = sources

    def callback(self, event):
        if self.done_callback:
            self.done_callback(event.new)

    def __getstate__(self):
        """Serialize the current state of the object"""
        return {
            'visible': self.visible,
            'labels': self.labels,
            'sources': [source.__getstate__() for source in self.items],
            'selected': [k for k, v in self.options.items() if v in self.selected],
        }

    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."""
        sources = state['sources']
        labels = state['labels']
        self.widget.options = {l: remake_instance(s) for l, s in zip(labels, sources)}
        self.selected = state.get('selected', [])
        self.visible = state.get('visible', True)
        return self
