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

import time
from ..utils import DictSerialiseMixin, pretty_describe


class CatalogEntry(DictSerialiseMixin):
    """A single item appearing in a catalog

    This is the base class, used by local entries (i.e., read from a YAML file)
    and by remote entries (read from a server).
    """
    def __init__(self, getenv=True, getshell=True):
        self._default_source = None
        self.getenv = getenv
        self.getshell = getshell
        self._pmode = 'default'

    def describe(self):
        """Get a dictionary of attributes of this entry.

        Returns: dict with keys
          name: str
              The name of the catalog entry.
          container : str
              kind of container used by this data source
          description : str
              Markdown-friendly description of data source
          direct_access : str
              Mode of remote access: forbid, allow, force
          user_parameters : list[dict]
              List of user parameters defined by this entry

        """
        raise NotImplementedError

    def get(self, **user_parameters):
        """Open the data source.

        Equivalent to calling the catalog entry like a function.

        Note: ``entry()``, ``entry.attr``, ``entry[item]`` check for persisted
        sources, but directly calling ``.get()`` will always ignore the
        persisted store (equivalent to ``self._pmode=='never'``).

        Parameters
        ----------
          user_parameters : dict
              Values for user-configurable parameters for this data source

        Returns
        -------
        DataSource
        """
        raise NotImplementedError

    def __call__(self, persist=None, **kwargs):
        """Instantiate DataSource with given user arguments

        Parameters
        ----------
        persist: str or None
            Override persistence mode defined in the parent catalog. If not
            None, must be one of ['always', 'never', 'default']. Has no
            effect if the source has never been persisted, use source.persist()
        """
        from ..source.base import PersistMixin
        if persist is not None and persist not in [
                'always', 'never', 'default']:
            raise ValueError('Persist value (%s) not understood' % persist)
        persist = persist or self._pmode
        s = self.get(**kwargs)
        if persist != 'never' and isinstance(s, PersistMixin) and s.has_been_persisted:
            from ..container.persist import store

            s2 = s.get_persisted()
            met = s2.metadata
            if persist == 'always' or not met['ttl']:
                s = s2
            elif met['ttl'] < time.time() - met['timestamp']:
                s = s2
            else:
                s = store.refresh(s2)
        s._entry = self
        return s

    @property
    def container(self):
        return getattr(self, '_container', None)

    @container.setter
    def container(self, cont):
        # so that .container (which sources always have)  always reflects ._container,
        # which is the variable name for entries.
        self._container = cont

    @property
    def has_been_persisted(self, **kwargs):
        """For the source created with the given args, has it been persisted?"""
        return self.get(**kwargs).has_been_persisted

    @property
    def plots(self):
        """List custom associated quick-plots """
        return list(self._metadata.get('plots', {}))

    def _ipython_display_(self):
        """Display the entry as a rich object in an IPython session."""
        from IPython.display import display
        import json
        contents = self.describe()
        display({
            'application/json': json.dumps(contents),
            'text/plain': pretty_describe(contents)
        }, metadata={
            'application/json': {'root': contents["name"]}
        }, raw=True)

    def _yaml(self):
        return {"sources": {self.name: self.describe()}}

    def __iter__(self):
        # If the entry is a catalog, this allows list(cat.entry)
        if self._container == 'catalog':
            return iter(self())
        else:
            raise ValueError('Cannot iterate a catalog entry')

    def __getitem__(self, item):
        """Pass getitem to data source, assuming default parameters

        Also supports multiple items ([.., ..]), in which case the first
        component only will be used to instantiate, and the rest passed on.
        """
        if isinstance(item, tuple):
            if len(item) > 1:
                return self()[item[0]].__getitem__(item[1:])
            else:
                item = item[0]
        return self()[item]

    def __repr__(self):
        return pretty_describe(self.describe())

    @property
    def gui(self):
        if not hasattr(self, '_gui'):
            from .gui import EntryGUI
            self._gui = EntryGUI(source=self, visible=True)
        else:
            self._gui.visible = True
        return self._gui
