#-----------------------------------------------------------------------------
# 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 distutils.version import LooseVersion

try:
    import dfviz
    assert LooseVersion(dfviz.__version__) >= LooseVersion("0.1.0")
except ImportError:
    dfviz = False

try:
    import xrviz
    from xrviz.dashboard import Dashboard as XRViz
    assert LooseVersion(xrviz.__version__) >= LooseVersion("0.1.1")
except ImportError:
    xrviz = False

import panel as pn
from ..base import BaseView
from ...catalog.local import LocalCatalogEntry


class Plots(BaseView):
    """
    Panel for displaying pre-defined plots from catalog.

    Parameters
    ----------
    source: intake catalog entry, or list of same
        source to describe in this object

    Attributes
    ----------
    has_plots: bool
        whether the source has plots defined
    instructions_contents: str
        instructions to put on the plot selector label
    options: list
        plots options defined on the source
    selected: str
        name of selected plot
    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.
    """
    select = None

    def __init__(self, source=None, **kwargs):
        self.custom = pn.widgets.Button(name='Customize...', width_policy='min', align='center')
        self.source = source
        self.panel = pn.Column(name='Plot', width_policy='max', margin=0)
        super().__init__(**kwargs)

    def setup(self):
        self.instructions = pn.pane.Markdown(
            self.instructions_contents, align='center', width_policy='max')
        self.select = pn.widgets.Select(options=self.options, height=30,
                                        align='center', min_width=200)
        self.pane = pn.pane.HoloViews(self._plot_object(self.selected),
                                      name="Plot")

        self.watchers = [
            self.select.param.watch(self.callback, ['options', 'value']),
            self.custom.param.watch(self.interact, ['clicks'])
        ]
        self.out = pn.Row(self.pane, name="Plot")

        self.children = [
            pn.Row(
                self.instructions,
                self.select,
                self.custom
            ),
            self.out,
        ]

    @BaseView.source.setter
    def source(self, source):
        """When the source gets updated, update the the options in
        the selector"""
        if source and isinstance(source, list):
            source = source[0]
        if isinstance(source, LocalCatalogEntry):
            source = source()
        BaseView.source.fset(self, source)
        if self.select:
            self.select.options = self.options
        if source and dfviz and source.container == 'dataframe':
            self.custom.disabled = False
        elif source and xrviz and source.container in ['xarray', 'ndarray', 'numpy']:
            self.custom.disabled = False
        else:
            self.custom.disabled = True

    @property
    def has_plots(self):
        """Whether the source has plots defined"""
        return self.source is not None and len(self._source.plots) > 0

    @property
    def instructions_contents(self):
        """Instructions to put on the plot selector label"""
        if self.has_plots:
            return '**Select from the predefined plots:**'
        return '*No predefined plots found - declare these in the catalog*'

    @property
    def options(self):
        """Plots options defined on the source"""
        return (['None'] + self.source.plots) if self.source is not None else []

    @property
    def selected(self):
        """Name of selected plot"""
        return self.select.value if self.select is not None else None

    @selected.setter
    def selected(self, selected):
        """When plot is selected set, make sure widget stays upto date"""
        self.select.value = selected

    def callback(self, *events):
        for event in events:
            if event.name == 'value':
                self.pane.object = self._plot_object(event.new)
            if event.name == 'options':
                self.instructions.object = self.instructions_contents

    def interact(self, _):
        # "customize" was pressed
        if self.selected == 'None':
            kwargs = {}
        else:
            kwargs = self.source.metadata['plots'][self.selected]
        if self.source.container == 'dataframe':
            df = self.source.to_dask()
            if df.npartitions == 1:
                df = df.compute()
            viz = dfviz.DFViz(df, **kwargs)
        elif self.source.container in ['xarray', 'ndarray', 'numpy']:
            import xarray
            try:
                data = self.source.to_dask()
            except NotImplementedError:
                data = self.source.read()
            if not isinstance(data, (xarray.DataArray, xarray.Dataset)):
                data = xarray.DataArray(data)
            viz = XRViz(data, **kwargs)
        else:
            return
        self.out[0] = viz.panel

    def _plot_object(self, selected):
        if selected and str(selected) != "None":
            plot_method = getattr(self.source.plot, selected)
            self.out[0] = self.pane
            if plot_method:
                return plot_method()

    def __getstate__(self, include_source=True):
        """Serialize the current state of the object. Set include_source
        to False when using with another panel that will include source."""
        state = super().__getstate__(include_source)
        state.update({
            'selected': self.selected,
        })
        return state

    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."""
        super().__setstate__(state)
        if self.visible:
            self.selected = state.get('selected')
        return self
