#
# Rabal Tree,rb
# A basic Tree structure
#

class Tree

  include Enumerable

  # The name of this Tree
  attr_accessor :name

  # The parent of this node.  If this is nil then this Tree is a
  # root.
  attr_accessor :parent

  # The children of this node.  If this Hash is empty, then this
  # Tree is a leaf.
  attr_accessor :children

  # An abstract data point that can be utilized by child classes
  # for whatever they like.  If this is non-nil and responds to
  # method calls it will be searched as part of the
  # 'method_missing' protocol.
  attr_accessor :parameters

  #
  # Create a new Tree with the given object.to_s as its +name+.
  #

  def initialize(name)
    @name   	= name.to_s
    @parent 	= nil
    @children   = {}
    @parameters = nil
  end

  #
  # Return true if this Tree has no children.
  #
  def is_leaf?
    @children.empty?
  end

  # 
  # Return true if this Tree has no parent.
  #
  def is_root?
    @parent.nil?
  end

  # 
  # Return the root node of the tree
  #
  def root
    return self if is_root?
    return @parent.root
  end

  #
  # Return the distance from the root
  #
  def depth
    return 0 if is_root?
    return (1 + @parent.depth)
  end

  #
  # Attach the given tree at the indicated path.  The path given
  # is always assumed to be from the root of the Tree being
  # attached to.
  #
  # The path is given as a string separated by '/'.  Each portion
  # of the string is matched against the name of the particular
  # tree.  
  #
  # Given :
  #
  #   a --- b --- c
  #	\ 
  # 	d - e --- f
  #  		\
  #   		g - h
  #
  # * the path +a/b/c+ will match the path to Tree +c+
  # * the path +d/e/g+ will _not_ match anything as the path must start at +a+ here
  # * the path +a/d/e+ will _not_ match anytthin as +e+ is not a child of +d+
  # * the path +a/d/e/g+ will match node +g+
  #
  # Leading and trailing '/' on the path are not necessary and removed.
  #
  def add_at_path(path,subtree)
    parent_tree = tree_at_path(path)
    parent_tree << subtree
    return self
  end

  #
  # Return the Tree that resides at the given path
  #
  def tree_at_path(path_str)
    path_str = path_str.chomp("/").reverse.chomp("/").reverse
    path 	= path_str.split("/")

    # strip of the redundant first match if it is the same as
    # the current node
    find_subtree(path)
  end

  #
  # Add the given object to the Tree as a child of this node.  If
  # the given object is not a Tree then wrap it with a Tree.
  #
  def <<(subtree)
    # this should not generally be the case, but wrap things
    # up to be nice.
    if not subtree.kind_of?(Tree) then
      subtree = Tree.new(subtree)
    end

    subtree.parent = self

    # FIXME: techinically this should no longer be called 'post_add' 
    # but maybe 'add_hook'  
    subtree.post_add

    # Don't overwrite any existing children with the same name,
    # just put the new subtree's children into the children of
    # the already existing subtree.

    if children.has_key?(subtree.name) then
      subtree.children.each_pair do |subtree_child_name,subtree_child_tree|
        children[subtree.name] << subtree_child_tree
      end
    else
      children[subtree.name] = subtree
    end

    return self
  end

  alias :add :<<

  #
  # Allow for Enumerable to be included.  This just wraps walk.
  #
  def each
    self.walk(self,lambda { |tree| yield tree }) 
  end

  #
  # Count how many items are in the tree
  #
  def size
    inject(0) { |count,n| count + 1 }
  end

  # 
  # Walk the tree in a depth first manner, visiting the Tree
  # first, then its children
  #
  def walk(tree,method)
    method.call(tree)
    tree.children.each_pair do |name,child|
      walk(child,method) 
    end
    #for depth first
    #method.call(tree)
  end

  # 
  # Allow for a method call to cascade up the tree looking for a
  # Tree that responds to the call.
  #
  def method_missing(method_id,*params,&block)
    if not parameters.nil? and parameters.respond_to?(method_id, true) then
      return parameters.send(method_id, *params, &block)
    elsif not is_root? then
      @parent.send method_id, *params, &block
    else
      raise NoMethodError, "undefined method `#{method_id}' for #{name}:Tree"
    end
  end

  #
  # allow for a hook so newly added Tree objects may do custom
  # processing after being added to the tree, but before the tree
  # is walked or processed
  #
  def post_add
  end
  #
  # find the current path of the tree from root to here, return it
  # as a '/' separates string.
  #
  def current_path
    #
    # WMAP: removed the asterixs and modified the first return
    # to just return a '/'
    # 
    return "/" if is_root? 
    return ([name,parent.current_path]).flatten.reverse.join("/")
  end

  #
  # Given the initial path to match as an Array find the Tree that
  # matches that path.
  # 
  def find_subtree(path)
    if path.empty? then
      return self
    else
      possible_child = path.shift
      if children.has_key?(possible_child) then
        children[possible_child].find_subtree(path)
      else
        raise PathNotFoundError, "`#{possible_child}' does not match anything at the current path in the Tree (#{current_path})"
      end
    end
  end

  #
  # Return true of false if the given subtree exists or not
  #
  def has_subtree?(path)
    begin
      find_subtree(path)
      return true
    rescue PathNotFoundError
      return false
    end
  end

  #
  # Allow for numerable to be included. This just wraps walk
  #
  def each
    self.walk(self,lambda { |tree| yield tree })	
  end

  def each_datum
    self.walk(self,lambda { |tree| yield tree.data})
  end
end
