#------------------------------------------------------------------------------
#
#  Copyright (c) 2005, Enthought, Inc.
#  All rights reserved.
#
#  This software is provided without warranty under the terms of the BSD
#  license included in enthought/LICENSE.txt and may be redistributed only
#  under the conditions described in the aforementioned license.  The license
#  is also available online at http://www.enthought.com/licenses/BSD.txt
#
#  Thanks for using Enthought open source!
#
#  Author: David C. Morrill
#  Date:   10/18/2004
#
#------------------------------------------------------------------------------

""" Define the ViewElements class, which is used to define a (typically
    class-based) hierarchical name space of related ViewElement objects.

    Normally there is a ViewElements object associated with each Traits-based
    class, which contains all of the ViewElement objects associated with the
    class. The ViewElements object is also linked to the ViewElements objects
    of its associated class's parent classes.
"""

#-------------------------------------------------------------------------
#  Imports:
#-------------------------------------------------------------------------

from __future__ import absolute_import

from traits.api import HasStrictTraits, List, Dict, Str, Int, Any, TraitError

from .view_element import ViewElement

#-------------------------------------------------------------------------
#  Trait definitions:
#-------------------------------------------------------------------------

# Trait for contents of a ViewElements object
content_trait = Dict(str, ViewElement)

#-------------------------------------------------------------------------
#  'ViewElements' class:
#-------------------------------------------------------------------------


class ViewElements(HasStrictTraits):
    """ Defines a hierarchical name space of related ViewElement objects.
    """
    #-------------------------------------------------------------------------
    #  Trait definitions:
    #-------------------------------------------------------------------------

    # Dictionary containing the named ViewElement items
    content = content_trait

    #-------------------------------------------------------------------------
    #  Finds a specified ViewElement within the specified (optional) search
    #  context:
    #-------------------------------------------------------------------------

    def find(self, name, stack=None):
        """ Finds a specified ViewElement within the specified (optional) search
            context.
        """
        # Assume search starts from the beginning the of the search order:
        i = 0

        # If a stack was specified, see if there is a matching entry in the
        # stack already:
        if stack is not None:
            for ssi in stack:
                if name == ssi.id:
                    # Match found, resume search at next ViewElements object
                    # in the search order:
                    i = ssi.context + 1
                    break

        # Search for a matching name starting at the specified ViewElements
        # object in the search order:
        for j, ves in enumerate(self._get_search_order()[i:]):
            result = ves.content.get(name)
            if result is not None:
                # Match found. If there is a stack, push matching name and
                # ViewElements context onto it:
                if stack is not None:
                    stack[0:0] = [SearchStackItem(id=name,
                                                  context=i + j)]

                # Return the ViewElement object that matched the name:
                return result

        # Indicate no match was found:
        return None

    #-------------------------------------------------------------------------
    #  Returns a sorted list of all names accessible from the ViewElements
    #  object that are of a specified (ViewElement) type:
    #-------------------------------------------------------------------------

    def filter_by(self, klass=None):
        """ Returns a sorted list of all names accessible from the ViewElements
            object that are of a specified (ViewElement) type.
        """
        if klass is None:
            from . import view
            klass = view.View
        result = []

        # Add each item in the search order which is of the right class and
        # which is not already in the result list:
        for ves in self._get_search_order():
            for name, ve in ves.content.items():
                if isinstance(ve, klass) and (name not in result):
                    result.append(name)

        # Sort the resulting list of names:
        result.sort()

        # Return the result:
        return result

    #-------------------------------------------------------------------------
    #  Handles the 'parents' list being updated:
    #-------------------------------------------------------------------------

    def _parents__changed(self):
        self._search_order = None

    def _parents_items_changed(self):
        self._search_order = None

    #-------------------------------------------------------------------------
    #  Returns the current search order (computing it if necessary):
    #-------------------------------------------------------------------------

    def _get_search_order(self):
        if self._search_order is None:
            self._search_order = self._mro()
        return self._search_order

    #-------------------------------------------------------------------------
    #  Compute the Python 'C3' algorithm used to determine a class's 'mro'
    #  and apply it to the 'parents' of the ViewElements to determine the
    #  correct search order:
    #-------------------------------------------------------------------------

    def _mro(self):
        return self._merge(
            [[self]] +
            [parent._get_search_order()[:] for parent in self.parents] +
            [self.parents[:]])

    def _merge(self, seqs):
        result = []
        while True:
            # Remove any empty sequences from the list:
            seqs = [seq for seq in seqs if len(seq) > 0]
            if len(seqs) == 0:
                return result

            # Find merge candidates among the sequence heads:
            for seq in seqs:
                candidate = seq[0]
                if len([s for s in seqs if candidate in s[1:]]) == 0:
                    break
            else:
                raise TraitError("Inconsistent ViewElements hierarchy")

            # Add the candidate to the result:
            result.append(candidate)

            # Then remove the candidate:
            for seq in seqs:
                if seq[0] == candidate:
                    del seq[0]

    #-------------------------------------------------------------------------
    #  Returns a 'pretty print' version of the ViewElements object:
    #-------------------------------------------------------------------------

    def __repr__(self):
        """ Returns a "pretty print" version of the ViewElements object.
        """
        return self.content.__repr__()

#-------------------------------------------------------------------------
#  Define forward reference traits:
#-------------------------------------------------------------------------

ViewElements.add_class_trait('parents', List(ViewElements))
ViewElements.add_class_trait('_search_order', Any)

#-------------------------------------------------------------------------
#  'SearchStackItem' class:
#-------------------------------------------------------------------------


class SearchStackItem(HasStrictTraits):

    #-------------------------------------------------------------------------
    #  Trait definitions:
    #-------------------------------------------------------------------------

    # Name that was looked up
    id = Str

    # Index into the 'mro' list of ViewElements that the ID was found in
    context = Int
