# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

""".

"""
import collections

# HIDDEN is a marker used to suppress a value, making it as if it were not set
# in that object. This causes the search to continue through the tree.
# This is most useful as a return value of dynamic values that want to find
# the value they are shadowing.
HIDDEN = object()


class VisitComplete(Exception):
  """Indicates a vist traversal has finished early."""


class Visitor(object):
  """The base class for anything that wants to "visit" all variables.

  The two main uses of visitor are search and export. They differ in that export
  is trying to find all variables, whereas search is just looking for one.
  """

  def __init__(self):
    self.stack = []

  def VisitNode(self, node):
    """Called for every node in the tree."""
    if not node.enabled:
      return self
    try:
      try:
        self.stack.append(node)
        self.StartNode()
        # Visit all the values first
        for key in self.KeysOf(node.values):
          self.Visit(key, node.values[key])
        # And now recurse into all the children
        for child in  node.children:
          self.VisitNode(child)
      finally:
        self.EndNode()
        self.stack.pop()
    except VisitComplete:
      if self.stack:
        # propagate back up the stack
        raise
    return self

  def Visit(self, key, value):
    """Visit is called for every variable in each node."""

  def StartNode(self):
    """StartNode is called once for each node before traversal."""

  def EndNode(self):
    """Visit is called for every node after traversal."""

  @property
  def root_node(self):
    """Returns the variable at the root of the current traversal."""
    return self.stack[0]

  @property
  def current_node(self):
    """Returns the node currently being scanned."""
    return self.stack[-1]

  def Resolve(self, key, value):
    """Returns a fully substituted value.

    This asks the root node to do the actual work.
    Args:
      key: The key being visited.
      value: The unresolved value associated with the key.
    Returns:
      the fully resolved value.
    """
    return self.root_node.Resolve(self, key, value)

  def Where(self):
    """Returns the current traversal stack as a string."""
    return '/'.join([entry.name for entry in self.stack])


class SearchVisitor(Visitor):
  """A Visitor that finds a single matching key."""

  def __init__(self, key):
    super(SearchVisitor, self).__init__()
    self.key = key
    self.found = False
    self.error = None

  def KeysOf(self, store):
    if self.key in store:
      yield self.key

  def Visit(self, key, value):
    value, error = self.Resolve(key, value)
    if value is not HIDDEN:
      self.found = True
      self.value = value
      self.error = error
      raise VisitComplete()


class WhereVisitor(SearchVisitor):
  """A SearchVisitor that returns the path to the matching key."""

  def Visit(self, key, value):
    self.where = self.Where()
    super(WhereVisitor, self).Visit(key, value)


class ExportVisitor(Visitor):
  """A visitor that builds a fully resolved map of all variables."""

  def __init__(self, store):
    super(ExportVisitor, self).__init__()
    self.store = store

  def KeysOf(self, store):
    if self.current_node.export is False:
      # not exporting from this config
      return
    for key in store.keys():
      if key in self.store:
        # duplicate
        continue
      if (self.current_node.export is None) and key.startswith('_'):
        # non exported name
        continue
      yield key

  def Visit(self, key, value):
    value, _ = self.Resolve(key, value)
    if value is not HIDDEN:
      self.store[key] = value


class Node(object):
  """The base class for objects in a visitable node tree."""

  def __init__(self, name='--', enabled=True, export=True):
    self._name = name
    self._children = collections.deque()
    self._values = {}
    self._viewers = []
    self.trail = []
    self._enabled = enabled
    self._export = export
    self._export_cache = None

  @property
  def name(self):
    return self._name

  @name.setter
  def name(self, value):
    self._name = value

  @property
  def enabled(self):
    return self._enabled

  @enabled.setter
  def enabled(self, value):
    if self._enabled == value:
      return
    self._enabled = value
    self.NotifyChanged()

  @property
  def export(self):
    return self._export

  @property
  def exported(self):
    if self._export_cache is None:
      self._export_cache = ExportVisitor({}).VisitNode(self).store
    return self._export_cache

  @property
  def values(self):
    return self._values

  @property
  def children(self):
    return self._children

  def RegisterViewer(self, viewer):
    self._viewers.append(viewer)

  def UnregisterViewer(self, viewer):
    self._viewers.remove(viewer)

  def OnChanged(self, child):
    _ = child
    self.NotifyChanged()

  def NotifyChanged(self):
    self._export_cache = None
    for viewers in self._viewers:
      viewers.OnChanged(self)

  def _AddChild(self, child):
    if child and child != self and child not in self._children:
      self._children.appendleft(child)
      child.RegisterViewer(self)

  def AddChild(self, child):
    self._AddChild(child)
    self.NotifyChanged()
    return self

  def AddChildren(self, *children):
    for child in children:
      self._AddChild(child)
    self.NotifyChanged()
    return self

  def Find(self, key):
    search = SearchVisitor(key).VisitNode(self)
    if not search.found:
      return None
    return search.value

  def WhereIs(self, key):
    search = WhereVisitor(key).VisitNode(self)
    if not search.found:
      return None
    return search.where

  def Get(self, key, raise_errors=False):
    search = SearchVisitor(key).VisitNode(self)
    if not search.found:
      self.Missing(key)
    if search.error and raise_errors:
      raise search.error  # bad type inference pylint: disable=raising-bad-type
    return search.value

  def Missing(self, key):
    raise KeyError(key)

  def Resolve(self, visitor, key, value):
    _ = visitor, key
    return value

  def Wipe(self):
    for child in self._children:
      child.UnregisterViewer(self)
    self._children = collections.deque()
    self._values = {}
    self.NotifyChanged()
