# tree.tcl --
#
#	Implementation of a tree data structure for Tcl.
#
# Copyright (c) 1998-2000 by Ajuba Solutions.
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# RCS: @(#) $Id: tree_tcl.tcl,v 1.5 2009/06/22 18:21:59 andreas_kupries Exp $

package require Tcl 8.5 9
package require struct::list

namespace eval ::struct::tree {
    # Data storage in the tree module
    # -------------------------------
    #
    # There's a lot of bits to keep track of for each tree:
    #	nodes
    #	node values
    #	node relationships
    #
    # It would quickly become unwieldy to try to keep these in arrays or lists
    # within the tree namespace itself.  Instead, each tree structure will get
    # its own namespace.  Each namespace contains:
    #	children	array mapping nodes to their children list
    #	parent		array mapping nodes to their parent node
    #	node:$node	array mapping keys to values for the node $node

    # counter is used to give a unique name for unnamed trees
    variable counter 0

    # Only export one command, the one used to instantiate a new tree
    namespace export tree_tcl
}

# ::struct::tree::tree_tcl --
#
#	Create a new tree with a given name; if no name is given, use
#	treeX, where X is a number.
#
# Arguments:
#	name	Optional name of the tree; if null or not given, generate one.
#
# Results:
#	name	Name of the tree created

proc ::struct::tree::tree_tcl {args} {
    variable counter

    set src     {}
    set srctype {}

    switch -exact -- [llength [info level 0]] {
	1 {
	    # Missing name, generate one.
	    incr counter
	    set name "tree${counter}"
	}
	2 {
	    # Standard call. New empty tree.
	    set name [lindex $args 0]
	}
	4 {
	    # Copy construction.
	    foreach {name as src} $args break
	    switch -exact -- $as {
		= - := - as {
		    set srctype tree
		}
		deserialize {
		    set srctype serial
		}
		default {
		    return -code error \
			    "wrong # args: should be \"tree ?name ?=|:=|as|deserialize source??\""
		}
	    }
	}
	default {
	    # Error.
	    return -code error \
		    "wrong # args: should be \"tree ?name ?=|:=|as|deserialize source??\""
	}
    }

    # FIRST, qualify the name.
    if {![string match "::*" $name]} {
        # Get caller's namespace; append :: if not global namespace.
        set ns [uplevel 1 [list namespace current]]
        if {"::" != $ns} {
            append ns "::"
        }

        set name "$ns$name"
    }
    if {[llength [info commands $name]]} {
	return -code error \
		"command \"$name\" already exists, unable to create tree"
    }

    # Set up the namespace for the object,
    # identical to the object command.
    namespace eval $name {
	variable rootname
	set      rootname root

	# Set up root node's child list
	variable children
	set      children(root) [list]

	# Set root node's parent
	variable parent
	set      parent(root) [list]

	# Set up the node attribute mapping
	variable  attribute
	array set attribute {}

	# Set up a counter for use in creating unique node names
	variable nextUnusedNode
	set      nextUnusedNode 1

	# Set up a counter for use in creating node attribute arrays.
	variable nextAttr
	set      nextAttr 0
    }

    # Create the command to manipulate the tree
    interp alias {} $name {} ::struct::tree::TreeProc $name

    # Automatic execution of assignment if a source
    # is present.
    if {$src != {}} {
	switch -exact -- $srctype {
	    tree   {
		set code [catch {_= $name $src} msg]
		if {$code} {
		    namespace delete $name
		    interp alias {} $name {}
		    return -code $code -errorinfo $::errorInfo -errorcode $::errorCode $msg
		}
	    }
	    serial {
		set code [catch {_deserialize $name $src} msg]
		if {$code} {
		    namespace delete $name
		    interp alias {} $name {}
		    return -code $code -errorinfo $::errorInfo -errorcode $::errorCode $msg
		}
	    }
	    default {
		return -code error \
			"Internal error, illegal srctype \"$srctype\""
	    }
	}
    }

    # Give object to caller for use.
    return $name
}

# ::struct::tree::prune_tcl --
#
#	Abort the walk script, and ignore any children of the
#	node we are currently at.
#
# Arguments:
#	None.
#
# Results:
#	None.
#
# Sideeffects:
#
#	Stops the execution of the script and throws a signal to the
#	surrounding walker to go to the next node, and ignore the
#	children of the current node.

proc ::struct::tree::prune_tcl {} {
    return -code 5
}

##########################
# Private functions follow

# ::struct::tree::TreeProc --
#
#	Command that processes all tree object commands.
#
# Arguments:
#	name	Name of the tree object to manipulate.
#	cmd	Subcommand to invoke.
#	args	Arguments for subcommand.
#
# Results:
#	Varies based on command to perform

proc ::struct::tree::TreeProc {name {cmd ""} args} {
    # Do minimal args checks here
    if { [llength [info level 0]] == 2 } {
	return -code error "wrong # args: should be \"$name option ?arg arg ...?\""
    }

    # Split the args into command and args components
    set sub _$cmd
    if { [llength [info commands ::struct::tree::$sub]] == 0 } {
	set optlist [lsort [info commands ::struct::tree::_*]]
	set xlist {}
	foreach p $optlist {
	    set p [namespace tail $p]
	    lappend xlist [string range $p 1 end]
	}
	set optlist [linsert [join $xlist ", "] "end-1" "or"]
	return -code error \
		"bad option \"$cmd\": must be $optlist"
    }

    set code [catch {uplevel 1 [linsert $args 0 ::struct::tree::$sub $name]} result]

    if {$code == 1} {
	return -errorinfo [ErrorInfoAsCaller uplevel $sub]  \
		-errorcode $::errorCode -code error $result
    } elseif {$code == 2} {
	return -code $code $result
    }
    return $result
}

# ::struct::tree::_:= --
#
#	Assignment operator. Copies the source tree into the
#       destination, destroying the original information.
#
# Arguments:
#	name	Name of the tree object we are copying into.
#	source	Name of the tree object providing us with the
#		data to copy.
#
# Results:
#	Nothing.

proc ::struct::tree::_= {name source} {
    _deserialize $name [$source serialize]
    return
}

# ::struct::tree::_--> --
#
#	Reverse assignment operator. Copies this tree into the
#       destination, destroying the original information.
#
# Arguments:
#	name	Name of the tree object to copy
#	dest	Name of the tree object we are copying to.
#
# Results:
#	Nothing.

proc ::struct::tree::_--> {name dest} {
    $dest deserialize [_serialize $name]
    return
}

# ::struct::tree::_ancestors --
#
#	Return the list of all parent nodes of a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to look up.
#
# Results:
#	parents	List of parents of node $node.
#		Immediate ancestor (parent) first,
#		Root of tree (ancestor of all) last.

proc ::struct::tree::_ancestors {name node} {
    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::parent
    set a {}
    while {[info exists parent($node)]} {
	set node $parent($node)
	if {$node == {}} break
	lappend a $node
    }
    return $a
}

# ::struct::tree::_attr --
#
#	Return attribute data for one key and multiple nodes, possibly all.
#
# Arguments:
#	name	Name of the tree object.
#	key	Name of the attribute to retrieve.
#
# Results:
#	children	Dictionary mapping nodes to attribute data.

proc ::struct::tree::_attr {name key args} {
    # Syntax:
    #
    # t attr key
    # t attr key -nodes {nodelist}
    # t attr key -glob nodepattern
    # t attr key -regexp nodepattern

    variable ${name}::attribute

    set usage "wrong # args: should be \"[list $name] attr key ?-nodes list|-glob pattern|-regexp pattern?\""
    if {([llength $args] != 0) && ([llength $args] != 2)} {
	return -code error $usage
    } elseif {[llength $args] == 0} {
	# This automatically restricts the list
	# to nodes which can have the attribute
	# in question.

	set nodes [array names attribute]
    } else {
	# Determine a list of nodes to look at
	# based on the chosen restriction.

	foreach {mode value} $args break
	switch -exact -- $mode {
	    -nodes {
		# This is the only branch where we have to
		# perform an explicit restriction to the
		# nodes which have attributes.
		set nodes {}
		foreach n $value {
		    if {![info exists attribute($n)]} continue
		    lappend nodes $n
		}
	    }
	    -glob {
		set nodes [array names attribute $value]
	    }
	    -regexp {
		set nodes {}
		foreach n [array names attribute] {
		    if {![regexp -- $value $n]} continue
		    lappend nodes $n
		}
	    }
	    default {
		return -code error $usage
	    }
	}
    }

    # Without possibly matching nodes
    # the result has to be empty.

    if {![llength $nodes]} {
	return {}
    }

    # Now locate matching keys and their values.

    set result {}
    foreach n $nodes {
	upvar ${name}::$attribute($n) data
	if {[info exists data($key)]} {
	    lappend result $n $data($key)
	}
    }

    return $result
}

# ::struct::tree::_deserialize --
#
#	Assignment operator. Copies a serialization into the
#       destination, destroying the original information.
#
# Arguments:
#	name	Name of the tree object we are copying into.
#	serial	Serialized tree to copy from.
#
# Results:
#	Nothing.

proc ::struct::tree::_deserialize {name serial} {
    # As we destroy the original tree as part of
    # the copying process we don't have to deal
    # with issues like node names from the new tree
    # interfering with the old ...

    # I. Get the serialization of the source tree
    #    and check it for validity.

    CheckSerialization $serial attr p c rn

    # Get all the relevant data into the scope

    variable ${name}::rootname
    variable ${name}::children
    variable ${name}::parent
    variable ${name}::attribute
    variable ${name}::nextAttr

    # Kill the existing parent/children information and insert the new
    # data in their place.

    foreach n [array names parent] {
	unset parent($n) children($n)
    }
    array set parent   [array get p]
    array set children [array get c]
    unset p c

    set nextAttr 0
    foreach a [array names attribute] {
	unset ${name}::$attribute($a)
    }
    foreach n [array names attr] {
	GenAttributeStorage $name $n
	array set ${name}::$attribute($n) $attr($n)
    }

    set rootname $rn

    ## Debug ## Dump internals ...
    if {0} {
	puts "___________________________________ $name"
	puts $rootname
	parray children
	parray parent
	parray attribute
	puts ___________________________________
    }
    return
}

# ::struct::tree::_children --
#
#	Return the list of children for a given node of a tree.
#
# Arguments:
#	name	Name of the tree object.
#	node	Node to look up.
#
# Results:
#	children	List of children for the node.

proc ::struct::tree::_children {name args} {
    # args := ?-all? node ?filter cmdprefix?

    # '-all' implies that not only the direct children of the
    # node, but all their children, and so on, are returned.
    #
    # 'filter cmd' implies that only those nodes in the result list
    # which pass the test 'cmd' are placed into the final result. 

    set usage "wrong # args: should be \"[list $name] children ?-all? node ?filter cmd?\""

    if {([llength $args] < 1) || ([llength $args] > 4)} {
	return -code error $usage
    }
    if {[string equal [lindex $args 0] -all]} {
	set all 1
	set args [lrange $args 1 end]
    } else {
	set all 0
    }

    # args := node ?filter cmdprefix?

    if {([llength $args] != 1) && ([llength $args] != 3)} {
	return -code error $usage
    }
    if {[llength $args] == 3} {
	foreach {node _const_ cmd} $args break
	if {![string equal $_const_ filter] || ![llength $cmd]} {
	    return -code error $usage
	}
    } else {
	set node [lindex $args 0]
	set cmd {}
    }

    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    if {$all} {
	set result [DescendantsCore $name $node]
    } else {
	variable ${name}::children
	set result $children($node)
    }

    if {[llength $cmd]} {
	lappend cmd $name
	set result [uplevel 1 [list ::struct::list filter $result $cmd]]
    }

    return $result
}

# ::struct::tree::_cut --
#
#	Destroys the specified node of a tree, but not its children.
#	These children are made into children of the parent of the
#	destroyed node at the index of the destroyed node.
#
# Arguments:
#	name	Name of the tree object.
#	node	Node to look up and cut.
#
# Results:
#	None.

proc ::struct::tree::_cut {name node} {
    variable ${name}::rootname

    if { [string equal $node $rootname] } {
	# Can't delete the special root node
	return -code error "cannot cut root node"
    }

    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::parent
    variable ${name}::children

    # Locate our parent, children and our location in the parent
    set parentNode $parent($node)
    set childNodes $children($node)

    set index [lsearch -exact $children($parentNode) $node]

    # Excise this node from the parent list,
    set newChildren [lreplace $children($parentNode) $index $index]

    # Put each of the children of $node into the parent's children list,
    # in the place of $node, and update the parent pointer of those nodes.
    foreach child $childNodes {
	set newChildren [linsert $newChildren $index $child]
	set parent($child) $parentNode
	incr index
    }
    set children($parentNode) $newChildren

    KillNode $name $node
    return
}

# ::struct::tree::_delete --
#
#	Remove a node from a tree, including all of its values.  Recursively
#	removes the node's children.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to delete.
#
# Results:
#	None.

proc ::struct::tree::_delete {name node} {
    variable ${name}::rootname
    if { [string equal $node $rootname] } {
	# Can't delete the special root node
	return -code error "cannot delete root node"
    }
    if {![_exists $name $node]} {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::children
    variable ${name}::parent

    # Remove this node from its parent's children list
    set parentNode $parent($node)
    set index [lsearch -exact $children($parentNode) $node]
    ldelete children($parentNode) $index

    # Yes, we could use the stack structure implemented in ::struct::stack,
    # but it's slower than inlining it.  Since we don't need a sophisticated
    # stack, don't bother.
    set st [list]
    foreach child $children($node) {
	lappend st $child
    }

    KillNode $name $node

    while {[llength $st] > 0} {
	set node [lindex $st end]
	ldelete           st end
	foreach child $children($node) {
	    lappend st $child
	}

	KillNode $name $node
    }
    return
}

# ::struct::tree::_depth --
#
#	Return the depth (distance from the root node) of a given node.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to find.
#
# Results:
#	depth	Number of steps from node to the root node.

proc ::struct::tree::_depth {name node} {
    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }
    variable ${name}::parent
    variable ${name}::rootname
    set depth 0
    while { ![string equal $node $rootname] } {
	incr depth
	set node $parent($node)
    }
    return $depth
}

# ::struct::tree::_descendants --
#
#	Return the list containing all descendants of a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to look at.
#
# Results:
#	desc	(filtered) List of nodes descending from 'node'.

proc ::struct::tree::_descendants {name node args} {
    # children -all sucessor, allows filtering.

    set usage "wrong # args: should be \"[list $name] descendants node ?filter cmd?\""

    if {[llength $args] > 2} {
	return -code error $usage
    } elseif {[llength $args] == 2} {
	foreach {_const_ cmd} $args break
	if {![string equal $_const_ filter] || ![llength $cmd]} {
	    return -code error $usage
	}
    } else {
	set cmd {}
    }

    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    set result [DescendantsCore $name $node]

    if {[llength $cmd]} {
	lappend cmd $name
	set result [uplevel 1 [list ::struct::list filter $result $cmd]]
    }

    return $result
}

proc ::struct::tree::DescendantsCore {name node} {
    # CORE for listing of node descendants.
    # No checks ...
    # No filtering ...

    variable ${name}::children

    # New implementation. Instead of keeping a second, and explicit,
    # list of pending nodes to shift through (= copying of array data
    # around), we reuse the result list for that, using a counter and
    # direct access to list elements to keep track of what nodes have
    # not been handled yet. This eliminates a whole lot of array
    # copying within the list implementation in the Tcl core. The
    # result is unchanged, i.e. the nodes are in the same order as
    # before.

    set result  $children($node)
    set at      0

    while {$at < [llength $result]} {
	set n [lindex $result $at]
	incr at
	foreach c $children($n) {
	    lappend result $c
	}
    }

    return $result
}

# ::struct::tree::_destroy --
#
#	Destroy a tree, including its associated command and data storage.
#
# Arguments:
#	name	Name of the tree to destroy.
#
# Results:
#	None.

proc ::struct::tree::_destroy {name} {
    namespace delete $name
    interp alias {} $name {}
}

# ::struct::tree::_exists --
#
#	Test for existence of a given node in a tree.
#
# Arguments:
#	name	Name of the tree to query.
#	node	Node to look for.
#
# Results:
#	1 if the node exists, 0 else.

proc ::struct::tree::_exists {name node} {
    return [info exists ${name}::parent($node)]
}

# ::struct::tree::_get --
#
#	Get a keyed value from a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to query.
#	key	Key to lookup.
#
# Results:
#	value	Value associated with the key given.

proc ::struct::tree::_get {name node key} {
    if {![_exists $name $node]} {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::attribute
    if {![info exists attribute($node)]} {
	# No attribute data for this node, key has to be invalid.
	return -code error "invalid key \"$key\" for node \"$node\""
    }

    upvar ${name}::$attribute($node) data
    if {![info exists data($key)]} {
	return -code error "invalid key \"$key\" for node \"$node\""
    }
    return $data($key)
}

# ::struct::tree::_getall --
#
#	Get a serialized list of key/value pairs from a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to query.
#
# Results:
#	value	A serialized list of key/value pairs.

proc ::struct::tree::_getall {name node {pattern *}} {
    if {![_exists $name $node]} {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::attribute
    if {![info exists attribute($node)]} {
	# No attributes ...
	return {}
    }

    upvar ${name}::$attribute($node) data
    return [array get data $pattern]
}

# ::struct::tree::_height --
#
#	Return the height (distance from the given node to its deepest child)
#
# Arguments:
#	name	Name of the tree.
#	node	Node we wish to know the height for..
#
# Results:
#	height	Distance to deepest child of the node.

proc ::struct::tree::_height {name node} {
    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::children
    variable ${name}::parent

    if {[llength $children($node)] == 0} {
	# No children, is a leaf, height is 0.
	return 0
    }

    # New implementation. We iteratively compute the height for each
    # node under the specified one, from the bottom up. The previous
    # implementation, using recursion will fail if the encountered
    # subtree has a height greater than the currently set recursion
    # limit.

    array set h {}

    # NOTE: Check out if a for loop doing direct access, i.e. without
    #       list reversal, is faster.

    foreach n [struct::list reverse [DescendantsCore $name $node]] {
	# Height of leafs
	if {![llength $children($n)]} {set h($n) 0}

	# Height of our parent is max of our and previous height.
	set p $parent($n)
	if {![info exists h($p)] || ($h($n) >= $h($p))} {
	    set h($p) [expr {$h($n) + 1}]
	}
    }

    # NOTE: Check out how much we gain by caching the result.
    #       For all nodes we have this computed. Use cache here
    #       as well to cut the inspection of descendants down.
    #       This may degenerate into a recursive solution again
    #       however.

    return $h($node)
}

# ::struct::tree::_keys --
#
#	Get a list of keys from a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to query.
#
# Results:
#	value	A serialized list of key/value pairs.

proc ::struct::tree::_keys {name node {pattern *}} {
    if {![_exists $name $node]} {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::attribute
    if {![info exists attribute($node)]} {
	# No attribute data for this node.
	return {}
    }

    upvar ${name}::$attribute($node) data
    return [array names data $pattern]
}

# ::struct::tree::_keyexists --
#
#	Test for existence of a given key for a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to query.
#	key	Key to lookup.
#
# Results:
#	1 if the key exists, 0 else.

proc ::struct::tree::_keyexists {name node key} {
    if {![_exists $name $node]} {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::attribute
    if {![info exists attribute($node)]} {
	# No attribute data for this node, key cannot exist
	return 0
    }

    upvar ${name}::$attribute($node) data
    return [info exists data($key)]
}

# ::struct::tree::_index --
#
#	Determine the index of node with in its parent's list of children.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to look up.
#
# Results:
#	index	The index of the node in its parent

proc ::struct::tree::_index {name node} {
    variable ${name}::rootname
    if { [string equal $node $rootname] } {
	# The special root node has no parent, thus no index in it either.
	return -code error "cannot determine index of root node"
    }

    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::children
    variable ${name}::parent

    # Locate the parent and ourself in its list of children
    set parentNode $parent($node)

    return [lsearch -exact $children($parentNode) $node]
}

# ::struct::tree::_insert --
#
#	Add a node to a tree; if the node(s) specified already exist, they
#	will be moved to the given location.
#
# Arguments:
#	name		Name of the tree.
#	parentNode	Parent to add the node to.
#	index		Index at which to insert.
#	args		Node(s) to insert.  If none is given, the routine
#			will insert a single node with a unique name.
#
# Results:
#	nodes		List of nodes inserted.

proc ::struct::tree::_insert {name parentNode index args} {
    if { [llength $args] == 0 } {
	# No node name was given; generate a unique one
	set args [list [GenerateUniqueNodeName $name]]
    }
    if { ![_exists $name $parentNode] } {
	return -code error "parent node \"$parentNode\" does not exist in tree \"$name\""
    }

    variable ${name}::parent
    variable ${name}::children
    variable ${name}::rootname

    # Make sure the index is numeric

    if {[string equal $index "end"]} {
	set index [llength $children($parentNode)]
    } elseif {[regexp {^end-([0-9]+)$} $index -> n]} {
	set index [expr {[llength $children($parentNode)] - $n}]
    }

    foreach node $args {
	if {[_exists $name $node] } {
	    # Move the node to its new home
	    if { [string equal $node $rootname] } {
		return -code error "cannot move root node"
	    }
	
	    # Cannot make a node its own descendant (I'm my own grandpa...)
	    set ancestor $parentNode
	    while { ![string equal $ancestor $rootname] } {
		if { [string equal $ancestor $node] } {
		    return -code error "node \"$node\" cannot be its own descendant"
		}
		set ancestor $parent($ancestor)
	    }
	    # Remove this node from its parent's children list
	    set oldParent $parent($node)
	    set ind [lsearch -exact $children($oldParent) $node]
	    ldelete children($oldParent) $ind
	
	    # If the node is moving within its parent, and its old location
	    # was before the new location, decrement the new location, so that
	    # it gets put in the right spot
	    if { [string equal $oldParent $parentNode] && $ind < $index } {
		incr index -1
	    }
	} else {
	    # Set up the new node
	    set children($node) [list]
	}

	# Add this node to its parent's children list
	set children($parentNode) [linsert $children($parentNode) $index $node]

	# Update the parent pointer for this node
	set parent($node) $parentNode
	incr index
    }

    return $args
}

# ::struct::tree::_isleaf --
#
#	Return whether the given node of a tree is a leaf or not.
#
# Arguments:
#	name	Name of the tree object.
#	node	Node to look up.
#
# Results:
#	isleaf	True if the node is a leaf; false otherwise.

proc ::struct::tree::_isleaf {name node} {
    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::children
    return [expr {[llength $children($node)] == 0}]
}

# ::struct::tree::_move --
#
#	Move a node (and all its subnodes) from where ever it is to a new
#	location in the tree.
#
# Arguments:
#	name		Name of the tree
#	parentNode	Parent to add the node to.
#	index		Index at which to insert.
#	node		Node to move; the node must exist in the tree.
#	args		Additional nodes to move; these nodes must exist
#			in the tree.
#
# Results:
#	None.

proc ::struct::tree::_move {name parentNode index node args} {
    set args [linsert $args 0 $node]

    # Can only move a node to a real location in the tree
    if { ![_exists $name $parentNode] } {
	return -code error "parent node \"$parentNode\" does not exist in tree \"$name\""
    }

    variable ${name}::parent
    variable ${name}::children
    variable ${name}::rootname

    # Make sure the index is numeric

    if {[string equal $index "end"]} {
	set index [llength $children($parentNode)]
    } elseif {[regexp {^end-([0-9]+)$} $index -> n]} {
	set index [expr {[llength $children($parentNode)] - $n}]
    }

    # Validate all nodes to move before trying to move any.
    foreach node $args {
	if { [string equal $node $rootname] } {
	    return -code error "cannot move root node"
	}

	# Can only move real nodes
	if { ![_exists $name $node] } {
	    return -code error "node \"$node\" does not exist in tree \"$name\""
	}

	# Cannot move a node to be a descendant of itself
	set ancestor $parentNode
	while { ![string equal $ancestor $rootname] } {
	    if { [string equal $ancestor $node] } {
		return -code error "node \"$node\" cannot be its own descendant"
	    }
	    set ancestor $parent($ancestor)
	}
    }

    # Remove all nodes from their current parent's children list
    foreach node $args {
	set oldParent $parent($node)
	set ind [lsearch -exact $children($oldParent) $node]

	ldelete children($oldParent) $ind

	# Update the nodes parent value
	set parent($node) $parentNode
    }

    # Add all nodes to their new parent's children list
    set children($parentNode) \
	[eval [list linsert $children($parentNode) $index] $args]

    return
}

# ::struct::tree::_next --
#
#	Return the right sibling for a given node of a tree.
#
# Arguments:
#	name		Name of the tree object.
#	node		Node to retrieve right sibling for.
#
# Results:
#	sibling		The right sibling for the node, or null if node was
#			the rightmost child of its parent.

proc ::struct::tree::_next {name node} {
    # The 'root' has no siblings.
    variable ${name}::rootname
    if { [string equal $node $rootname] } {
	return {}
    }

    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    # Locate the parent and our place in its list of children.
    variable ${name}::parent
    variable ${name}::children

    set parentNode $parent($node)
    set  index [lsearch -exact $children($parentNode) $node]

    # Go to the node to the right and return its name.
    return [lindex $children($parentNode) [incr index]]
}

# ::struct::tree::_numchildren --
#
#	Return the number of immediate children for a given node of a tree.
#
# Arguments:
#	name		Name of the tree object.
#	node		Node to look up.
#
# Results:
#	numchildren	Number of immediate children for the node.

proc ::struct::tree::_numchildren {name node} {
    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::children
    return [llength $children($node)]
}

# ::struct::tree::_nodes --
#
#	Return a list containing all nodes known to the tree.
#
# Arguments:
#	name		Name of the tree object.
#
# Results:
#	nodes	List of nodes in the tree.

proc ::struct::tree::_nodes {name} {
    variable ${name}::children
    return [array names children]
}

# ::struct::tree::_parent --
#
#	Return the name of the parent node of a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to look up.
#
# Results:
#	parent	Parent of node $node

proc ::struct::tree::_parent {name node} {
    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }
    # FRINK: nocheck
    return [set ${name}::parent($node)]
}

# ::struct::tree::_previous --
#
#	Return the left sibling for a given node of a tree.
#
# Arguments:
#	name		Name of the tree object.
#	node		Node to look up.
#
# Results:
#	sibling		The left sibling for the node, or null if node was
#			the leftmost child of its parent.

proc ::struct::tree::_previous {name node} {
    # The 'root' has no siblings.
    variable ${name}::rootname
    if { [string equal $node $rootname] } {
	return {}
    }

    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    # Locate the parent and our place in its list of children.
    variable ${name}::parent
    variable ${name}::children

    set parentNode $parent($node)
    set  index [lsearch -exact $children($parentNode) $node]

    # Go to the node to the right and return its name.
    return [lindex $children($parentNode) [incr index -1]]
}

# ::struct::tree::_rootname --
#
#	Query or change the name of the root node.
#
# Arguments:
#	name	Name of the tree.
#
# Results:
#	The name of the root node

proc ::struct::tree::_rootname {name} {
    variable ${name}::rootname
    return $rootname
}

# ::struct::tree::_rename --
#
#	Change the name of any node.
#
# Arguments:
#	name	Name of the tree.
#	node	Name of node to be renamed
#	newname	New name for the node.
#
# Results:
#	The new name of the node.

proc ::struct::tree::_rename {name node newname} {
    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }
    if {[_exists $name $newname]} {
	return -code error "unable to rename node to \"$newname\",\
		node of that name already present in the tree \"$name\""
    }

    set oldname  $node

    # Perform the rename in the internal
    # data structures.

    variable ${name}::rootname
    variable ${name}::children
    variable ${name}::parent
    variable ${name}::attribute

    set children($newname) $children($oldname)
    unset                   children($oldname)
    set parent($newname)     $parent($oldname)
    unset                     parent($oldname)

    foreach c $children($newname) {
	set parent($c) $newname
    }

    if {[string equal $oldname $rootname]} {
	set rootname $newname
    } else {
	set p $parent($newname)
	set pos  [lsearch -exact $children($p) $oldname]
	lset children($p) $pos $newname
    }

    if {[info exists attribute($oldname)]} {
	set attribute($newname) $attribute($oldname)
	unset                    attribute($oldname)
    }

    return $newname
}

# ::struct::tree::_serialize --
#
#	Serialize a tree object (partially) into a transportable value.
#
# Arguments:
#	name	Name of the tree.
#	node	Root node of the serialized tree.
#
# Results:
#	A list structure describing the part of the tree which was serialized.

proc ::struct::tree::_serialize {name args} {
    if {[llength $args] > 1} {
	return -code error \
		"wrong # args: should be \"[list $name] serialize ?node?\""
    } elseif {[llength $args] == 1} {
	set node [lindex $args 0]

	if {![_exists $name $node]} {
	    return -code error "node \"$node\" does not exist in tree \"$name\""
	}
    } else {
	variable ${name}::rootname
	set node $rootname
    }

    set                   tree [list]
    Serialize $name $node tree
    return               $tree
}

# ::struct::tree::_set --
#
#	Set or get a value for a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to modify or query.
#	args	Optional argument specifying a value.
#
# Results:
#	val	Value associated with the given key of the given node

proc ::struct::tree::_set {name node key args} {
    if {[llength $args] > 1} {
	return -code error "wrong # args: should be \"$name set node key\
		?value?\""
    }
    if {![_exists $name $node]} {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    # Process the arguments ...

    if {[llength $args] > 0} {
	# Setting the value. This may have to create
	# the attribute array for this particular
	# node

	variable ${name}::attribute
	if {![info exists attribute($node)]} {
	    # No attribute data for this node,
	    # so create it as we need it now.
	    GenAttributeStorage $name $node
	}
	upvar ${name}::$attribute($node) data

	return [set data($key) [lindex $args end]]
    } else {
	# Getting the value

	return [_get $name $node $key]
    }
}

# ::struct::tree::_append --
#
#	Append a value for a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to modify.
#	key	Name of attribute to modify.
#	value	Value to append
#
# Results:
#	val	Value associated with the given key of the given node

proc ::struct::tree::_append {name node key value} {
    if {![_exists $name $node]} {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::attribute
    if {![info exists attribute($node)]} {
	# No attribute data for this node,
	# so create it as we need it.
	GenAttributeStorage $name $node
    }

    upvar ${name}::$attribute($node) data
    return [append data($key) $value]
}

# ::struct::tree::_lappend --
#
#	lappend a value for a node in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to modify or query.
#	key	Name of attribute to modify.
#	value	Value to append
#
# Results:
#	val	Value associated with the given key of the given node

proc ::struct::tree::_lappend {name node key value} {
    if {![_exists $name $node]} {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::attribute
    if {![info exists attribute($node)]} {
	# No attribute data for this node,
	# so create it as we need it.
	GenAttributeStorage $name $node
    }

    upvar ${name}::$attribute($node) data
    return [lappend data($key) $value]
}

# ::struct::tree::_leaves --
#
#	Return a list containing all leaf nodes known to the tree.
#
# Arguments:
#	name		Name of the tree object.
#
# Results:
#	nodes	List of leaf nodes in the tree.

proc ::struct::tree::_leaves {name} {
    variable ${name}::children

    set res {}
    foreach n [array names children] {
	if {[llength $children($n)]} continue
	lappend res $n
    }
    return $res
}

# ::struct::tree::_size --
#
#	Return the number of descendants of a given node.  The default node
#	is the special root node.
#
# Arguments:
#	name	Name of the tree.
#	node	Optional node to start counting from (default is root).
#
# Results:
#	size	Number of descendants of the node.

proc ::struct::tree::_size {name args} {
    variable ${name}::rootname
    if {[llength $args] > 1} {
	return -code error \
		"wrong # args: should be \"[list $name] size ?node?\""
    } elseif {[llength $args] == 1} {
	set node [lindex $args 0]

	if { ![_exists $name $node] } {
	    return -code error "node \"$node\" does not exist in tree \"$name\""
	}
    } else {
	# If the node is the root, we can do the cheap thing and just count the
	# number of nodes (excluding the root node) that we have in the tree with
	# array size.

	return [expr {[array size ${name}::parent] - 1}]
    }

    # If the node is the root, we can do the cheap thing and just count the
    # number of nodes (excluding the root node) that we have in the tree with
    # array size.

    if { [string equal $node $rootname] } {
	return [expr {[array size ${name}::parent] - 1}]
    }

    # Otherwise we have to do it the hard way and do a full tree search
    variable ${name}::children
    set size 0
    set st [list ]
    foreach child $children($node) {
	lappend st $child
    }
    while { [llength $st] > 0 } {
	set node [lindex $st end]
	ldelete st end
	incr size
	foreach child $children($node) {
	    lappend st $child
	}
    }
    return $size
}

# ::struct::tree::_splice --
#
#	Add a node to a tree, making a range of children from the given
#	parent children of the new node.
#
# Arguments:
#	name		Name of the tree.
#	parentNode	Parent to add the node to.
#	from		Index at which to insert.
#	to		Optional end of the range of children to replace.
#			Defaults to 'end'.
#	args		Optional node name; if given, must be unique.  If not
#			given, a unique name will be generated.
#
# Results:
#	node		Name of the node added to the tree.

proc ::struct::tree::_splice {name parentNode from {to end} args} {

    if { ![_exists $name $parentNode] } {
	return -code error "node \"$parentNode\" does not exist in tree \"$name\""
    }

    if { [llength $args] == 0 } {
	# No node name given; generate a unique node name
	set node [GenerateUniqueNodeName $name]
    } else {
	set node [lindex $args 0]
    }

    if { [_exists $name $node] } {
	return -code error "node \"$node\" already exists in tree \"$name\""
    }

    variable ${name}::children
    variable ${name}::parent

    if {[string equal $from "end"]} {
	set from [expr {[llength $children($parentNode)] - 1}]
    } elseif {[regexp {^end-([0-9]+)$} $from -> n]} {
	set from [expr {[llength $children($parentNode)] - 1 - $n}]
    }
    if {[string equal $to "end"]} {
	set to [expr {[llength $children($parentNode)] - 1}]
    } elseif {[regexp {^end-([0-9]+)$} $to -> n]} {
	set to   [expr {[llength $children($parentNode)] - 1 - $n}]
    }

    # Save the list of children that are moving
    set moveChildren [lrange $children($parentNode) $from $to]

    # Remove those children from the parent
    ldelete children($parentNode) $from $to

    # Add the new node
    _insert $name $parentNode $from $node

    # Move the children
    set children($node) $moveChildren
    foreach child $moveChildren {
	set parent($child) $node
    }

    return $node
}

# ::struct::tree::_swap --
#
#	Swap two nodes in a tree.
#
# Arguments:
#	name	Name of the tree.
#	node1	First node to swap.
#	node2	Second node to swap.
#
# Results:
#	None.

proc ::struct::tree::_swap {name node1 node2} {
    # Can't swap the magic root node
    variable ${name}::rootname
    if {[string equal $node1 $rootname] || [string equal $node2 $rootname]} {
	return -code error "cannot swap root node"
    }

    # Can only swap two real nodes
    if {![_exists $name $node1]} {
	return -code error "node \"$node1\" does not exist in tree \"$name\""
    }
    if {![_exists $name $node2]} {
	return -code error "node \"$node2\" does not exist in tree \"$name\""
    }

    # Can't swap a node with itself
    if {[string equal $node1 $node2]} {
	return -code error "cannot swap node \"$node1\" with itself"
    }

    # Swapping nodes means swapping their labels and values
    variable ${name}::children
    variable ${name}::parent

    set parent1 $parent($node1)
    set parent2 $parent($node2)

    # Replace node1 with node2 in node1's parent's children list, and
    # node2 with node1 in node2's parent's children list
    set i1 [lsearch -exact $children($parent1) $node1]
    set i2 [lsearch -exact $children($parent2) $node2]

    lset children($parent1) $i1 $node2
    lset children($parent2) $i2 $node1

    # Make node1 the parent of node2's children, and vis versa
    foreach child $children($node2) {
	set parent($child) $node1
    }
    foreach child $children($node1) {
	set parent($child) $node2
    }

    # Swap the children lists
    set children1 $children($node1)
    set children($node1) $children($node2)
    set children($node2) $children1

    if { [string equal $node1 $parent2] } {
	set parent($node1) $node2
	set parent($node2) $parent1
    } elseif { [string equal $node2 $parent1] } {
	set parent($node1) $parent2
	set parent($node2) $node1
    } else {
	set parent($node1) $parent2
	set parent($node2) $parent1
    }

    return
}

# ::struct::tree::_unset --
#
#	Remove a keyed value from a node.
#
# Arguments:
#	name	Name of the tree.
#	node	Node to modify.
#	key	Name of attribute to unset.
#
# Results:
#	None.

proc ::struct::tree::_unset {name node key} {
    if {![_exists $name $node]} {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    variable ${name}::attribute
    if {![info exists attribute($node)]} {
	# No attribute data for this node,
	# nothing to do.
	return
    }

    upvar ${name}::$attribute($node) data
    catch {unset data($key)}

    if {[array size data] == 0} {
	# No attributes stored for this node, squash the whole array.
	unset attribute($node)
	unset data
    }
    return
}

# ::struct::tree::_walk --
#
#	Walk a tree using a pre-order depth or breadth first
#	search. Pre-order DFS is the default.  At each node that is visited,
#	a command will be called with the name of the tree and the node.
#
# Arguments:
#	name	Name of the tree.
#	node	Node at which to start.
#	args	Optional additional arguments specifying the type and order of
#		the tree walk, and the command to execute at each node.
#		Format is
#		    ?-type {bfs|dfs}? ?-order {pre|post|in|both}? a n script
#
# Results:
#	None.

proc ::struct::tree::_walk {name node args} {
    set usage "$name walk node ?-type {bfs|dfs}? ?-order {pre|post|in|both}? ?--? loopvar script"

    if {[llength $args] > 7 || [llength $args] < 2} {
	return -code error "wrong # args: should be \"$usage\""
    }

    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    set args [WalkOptions $args 2 $usage]
    # Remainder is 'a n script'

    foreach {loopvariables script} $args break

    if {[llength $loopvariables] > 2} {
	return -code error "too many loop variables, at most two allowed"
    } elseif {[llength $loopvariables] == 2} {
	foreach {avar nvar} $loopvariables break
    } else {
	set nvar [lindex $loopvariables 0]
	set avar {}
    }

    # Make sure we have a script to run, otherwise what's the point?
    if { [string equal $script ""] } {
	return -code error "no script specified, or empty"
    }

    # Do the walk
    variable ${name}::children
    set st [list ]
    lappend st $node

    # Compute some flags for the possible places of command evaluation
    set leave [expr {[string equal $order post] || [string equal $order both]}]
    set enter [expr {[string equal $order pre]  || [string equal $order both]}]
    set touch [string equal $order in]

    if {$leave} {
	set lvlabel leave
    } elseif {$touch} {
	# in-order does not provide a sense
	# of nesting for the parent, hence
	# no enter/leave, just 'visit'.
	set lvlabel visit
    }

    set rcode 0
    set rvalue {}

    if {[string equal $type "dfs"]} {
	# Depth-first walk, several orders of visiting nodes
	# (pre, post, both, in)

	array set visited {}

	while { [llength $st] > 0 } {
	    set node [lindex $st end]

	    if {[info exists visited($node)]} {
		# Second time we are looking at this 'node'.
		# Pop it, then evaluate the command (post, both, in).

		ldelete st end

		if {$leave || $touch} {
		    # Evaluate the script at this node
		    WalkCall $avar $nvar $name $node $lvlabel $script
		    # prune stops execution of loop here.
		}
	    } else {
		# First visit of this 'node'.
		# Do *not* pop it from the stack so that we are able
		# to visit again after its children

		# Remember it.
		set visited($node) .

		if {$enter} {
		    # Evaluate the script at this node (pre, both).
		    #
		    # Note: As this is done before the children are
		    # looked at the script may change the children of
		    # this node and thus affect the walk.

		    WalkCall $avar $nvar $name $node "enter" $script
		    # prune stops execution of loop here.
		}

		# Add the children of this node to the stack.
		# The exact behaviour depends on the chosen
		# order. For pre, post, both-order we just
		# have to add them in reverse-order so that
		# they will be popped left-to-right. For in-order
		# we have rearrange the stack so that the parent
		# is revisited immediately after the first child.
		# (but only if there is ore than one child,)

		set clist        $children($node)
		set len [llength $clist]

		if {$touch && ($len > 1)} {
		    # Pop node from stack, insert into list of children
		    ldelete st end
		    set clist [linsert $clist 1 $node]
		    incr len
		}

		for {set i [expr {$len - 1}]} {$i >= 0} {incr i -1} {
		    lappend st [lindex $clist $i]
		}
	    }
	}
    } else {
	# Breadth first walk (pre, post, both)
	# No in-order possible. Already captured.

	if {$leave} {
	    set backward $st
	}

	while { [llength $st] > 0 } {
	    set node [lindex   $st 0]
	    ldelete st 0

	    if {$enter} {
		# Evaluate the script at this node
		WalkCall $avar $nvar $name $node "enter" $script
		# prune stops execution of loop here.
	    }

	    # Add this node's children
	    # And create a mirrored version in case of post/both order.

	    foreach child $children($node) {
		lappend st $child
		if {$leave} {
		    set backward [linsert $backward 0 $child]
		}
	    }
	}

	if {$leave} {
	    foreach node $backward {
		# Evaluate the script at this node
		WalkCall $avar $nvar $name $node "leave" $script
	    }
	}
    }

    if {$rcode != 0} {
	return -code $rcode $rvalue
    }
    return
}

proc ::struct::tree::_walkproc {name node args} {
    set usage "$name walkproc node ?-type {bfs|dfs}? ?-order {pre|post|in|both}? ?--? cmdprefix"

    if {[llength $args] > 6 || [llength $args] < 1} {
	return -code error "wrong # args: should be \"$usage\""
    }

    if { ![_exists $name $node] } {
	return -code error "node \"$node\" does not exist in tree \"$name\""
    }

    set args [WalkOptions $args 1 $usage]
    # Remainder is 'n cmdprefix'

    set script [lindex $args 0]

    # Make sure we have a script to run, otherwise what's the point?
    if { ![llength $script] } {
	return -code error "no script specified, or empty"
    }

    # Do the walk
    variable ${name}::children
    set st [list ]
    lappend st $node

    # Compute some flags for the possible places of command evaluation
    set leave [expr {[string equal $order post] || [string equal $order both]}]
    set enter [expr {[string equal $order pre]  || [string equal $order both]}]
    set touch [string equal $order in]

    if {$leave} {
	set lvlabel leave
    } elseif {$touch} {
	# in-order does not provide a sense
	# of nesting for the parent, hence
	# no enter/leave, just 'visit'.
	set lvlabel visit
    }

    set rcode 0
    set rvalue {}

    if {[string equal $type "dfs"]} {
	# Depth-first walk, several orders of visiting nodes
	# (pre, post, both, in)

	array set visited {}

	while { [llength $st] > 0 } {
	    set node [lindex $st end]

	    if {[info exists visited($node)]} {
		# Second time we are looking at this 'node'.
		# Pop it, then evaluate the command (post, both, in).

		ldelete st end

		if {$leave || $touch} {
		    # Evaluate the script at this node
		    WalkCallProc $name $node $lvlabel $script
		    # prune stops execution of loop here.
		}
	    } else {
		# First visit of this 'node'.
		# Do *not* pop it from the stack so that we are able
		# to visit again after its children

		# Remember it.
		set visited($node) .

		if {$enter} {
		    # Evaluate the script at this node (pre, both).
		    #
		    # Note: As this is done before the children are
		    # looked at the script may change the children of
		    # this node and thus affect the walk.

		    WalkCallProc $name $node "enter" $script
		    # prune stops execution of loop here.
		}

		# Add the children of this node to the stack.
		# The exact behaviour depends on the chosen
		# order. For pre, post, both-order we just
		# have to add them in reverse-order so that
		# they will be popped left-to-right. For in-order
		# we have rearrange the stack so that the parent
		# is revisited immediately after the first child.
		# (but only if there is ore than one child,)

		set clist        $children($node)
		set len [llength $clist]

		if {$touch && ($len > 1)} {
		    # Pop node from stack, insert into list of children
		    ldelete st end
		    set clist [linsert $clist 1 $node]
		    incr len
		}

		for {set i [expr {$len - 1}]} {$i >= 0} {incr i -1} {
		    lappend st [lindex $clist $i]
		}
	    }
	}
    } else {
	# Breadth first walk (pre, post, both)
	# No in-order possible. Already captured.

	if {$leave} {
	    set backward $st
	}

	while { [llength $st] > 0 } {
	    set node [lindex   $st 0]
	    ldelete st 0

	    if {$enter} {
		# Evaluate the script at this node
		WalkCallProc $name $node "enter" $script
		# prune stops execution of loop here.
	    }

	    # Add this node's children
	    # And create a mirrored version in case of post/both order.

	    foreach child $children($node) {
		lappend st $child
		if {$leave} {
		    set backward [linsert $backward 0 $child]
		}
	    }
	}

	if {$leave} {
	    foreach node $backward {
		# Evaluate the script at this node
		WalkCallProc $name $node "leave" $script
	    }
	}
    }

    if {$rcode != 0} {
	return -code $rcode $rvalue
    }
    return
}

proc ::struct::tree::WalkOptions {theargs n usage} {
    upvar 1 type type order order

    # Set defaults
    set type dfs
    set order pre

    while {[llength $theargs]} {
	set flag [lindex $theargs 0]
	switch -exact -- $flag {
	    "-type" {
		if {[llength $theargs] < 2} {
		    return -code error "value for \"$flag\" missing"
		}
		set type [string tolower [lindex $theargs 1]]
		set theargs [lrange $theargs 2 end]
	    }
	    "-order" {
		if {[llength $theargs] < 2} {
		    return -code error "value for \"$flag\" missing"
		}
		set order [string tolower [lindex $theargs 1]]
		set theargs [lrange $theargs 2 end]
	    }
	    "--" {
		set theargs [lrange $theargs 1 end]
		break
	    }
	    default {
		break
	    }
	}
    }

    if {[llength $theargs] == 0} {
	return -code error "wrong # args: should be \"$usage\""
    }
    if {[llength $theargs] != $n} {
	return -code error "unknown option \"$flag\""
    }

    # Validate that the given type is good
    switch -exact -- $type {
	"dfs" - "bfs" {
	    set type $type
	}
	default {
	    return -code error "bad search type \"$type\": must be bfs or dfs"
	}
    }

    # Validate that the given order is good
    switch -exact -- $order {
	"pre" - "post" - "in" - "both" {
	    set order $order
	}
	default {
	    return -code error "bad search order \"$order\":\
		    must be both, in, pre, or post"
	}
    }

    if {[string equal $order "in"] && [string equal $type "bfs"]} {
	return -code error "unable to do a ${order}-order breadth first walk"
    }

    return $theargs
}

# ::struct::tree::WalkCall --
#
#	Helper command to 'walk' handling the evaluation
#	of the user-specified command. Information about
#	the tree, node and current action are substituted
#	into the command before it evaluation.
#
# Arguments:
#	tree	Tree we are walking
#	node	Node we are at.
#	action	The current action.
#	cmd	The command to call, already partially substituted.
#
# Results:
#	None.

proc ::struct::tree::WalkCall {avar nvar tree node action cmd} {

    if {$avar != {}} {
	upvar 2 $avar a ; set a $action
    }
    upvar 2 $nvar n ; set n $node

    set code [catch {uplevel 2 $cmd} result]

    # decide what to do upon the return code:
    #
    #               0 - the body executed successfully
    #               1 - the body raised an error
    #               2 - the body invoked [return]
    #               3 - the body invoked [break]
    #               4 - the body invoked [continue]
    #               5 - the body invoked [struct::tree::prune]
    # everything else - return and pass on the results
    #
    switch -exact -- $code {
	0 {}
	1 {
	    return -errorinfo [ErrorInfoAsCaller uplevel WalkCall]  \
		    -errorcode $::errorCode -code error $result
	}
	3 {
	    # FRINK: nocheck
	    return -code break
	}
	4 {}
	5 {
	    upvar order order
	    if {[string equal $order post] || [string equal $order in]} {
		return -code error "Illegal attempt to prune ${order}-order walking"
	    }
	    return -code continue
	}
	default {
	    upvar 1 rcode rcode rvalue rvalue
	    set rcode $code
	    set rvalue $result
	    return -code break
	    #return -code $code $result
	}
    }
    return {}
}

proc ::struct::tree::WalkCallProc {tree node action cmd} {

    lappend cmd $tree $node $action
    set code [catch {uplevel 2 $cmd} result]

    # decide what to do upon the return code:
    #
    #               0 - the body executed successfully
    #               1 - the body raised an error
    #               2 - the body invoked [return]
    #               3 - the body invoked [break]
    #               4 - the body invoked [continue]
    #               5 - the body invoked [struct::tree::prune]
    # everything else - return and pass on the results
    #
    switch -exact -- $code {
	0 {}
	1 {
	    return -errorinfo [ErrorInfoAsCaller uplevel WalkCallProc]  \
		    -errorcode $::errorCode -code error $result
	}
	3 {
	    # FRINK: nocheck
	    return -code break
	}
	4 {}
	5 {
	    upvar order order
	    if {[string equal $order post] || [string equal $order in]} {
		return -code error "Illegal attempt to prune ${order}-order walking"
	    }
	    return -code continue
	}
	default {
	    upvar 1 rcode rcode rvalue rvalue
	    set rcode $code
	    set rvalue $result
	    return -code break
	}
    }
    return {}
}

proc ::struct::tree::ErrorInfoAsCaller {find replace} {
    set info $::errorInfo
    set i [string last "\n    (\"$find" $info]
    if {$i == -1} {return $info}
    set result [string range $info 0 [incr i 6]]	;# keep "\n    (\""
    append result $replace			;# $find -> $replace
    incr i [string length $find]
    set j [string first ) $info [incr i]]	;# keep rest of parenthetical
    append result [string range $info $i $j]
    return $result
}

# ::struct::tree::GenerateUniqueNodeName --
#
#	Generate a unique node name for the given tree.
#
# Arguments:
#	name	Name of the tree to generate a unique node name for.
#
# Results:
#	node	Name of a node guaranteed to not exist in the tree.

proc ::struct::tree::GenerateUniqueNodeName {name} {
    variable ${name}::nextUnusedNode
    while {[_exists $name "node${nextUnusedNode}"]} {
	incr nextUnusedNode
    }
    return "node${nextUnusedNode}"
}

# ::struct::tree::KillNode --
#
#	Delete all data of a node.
#
# Arguments:
#	name	Name of the tree containing the node
#	node	Name of the node to delete.
#
# Results:
#	none

proc ::struct::tree::KillNode {name node} {
    variable ${name}::parent
    variable ${name}::children
    variable ${name}::attribute

    # Remove all record of $node
    unset parent($node)
    unset children($node)

    if {[info exists attribute($node)]} {
	# FRINK: nocheck
	unset ${name}::$attribute($node)
	unset attribute($node)
    }
    return
}

# ::struct::tree::GenAttributeStorage --
#
#	Create an array to store the attributes of a node in.
#
# Arguments:
#	name	Name of the tree containing the node
#	node	Name of the node which got attributes.
#
# Results:
#	none

proc ::struct::tree::GenAttributeStorage {name node} {
    variable ${name}::nextAttr
    variable ${name}::attribute

    set   attr "a[incr nextAttr]"
    set   attribute($node) $attr
    return
}

# ::struct::tree::Serialize --
#
#	Serialize a tree object (partially) into a transportable value.
#
# Arguments:
#	name	Name of the tree.
#	node	Root node of the serialized tree.
#
# Results:
#	None

proc ::struct::tree::Serialize {name node tvar} {
    upvar 1 $tvar tree

    variable ${name}::attribute
    variable ${name}::parent

    # 'node' is the root of the tree to serialize. The precondition
    # for the call is that this node is already stored in the list
    # 'tvar', at index 'rootidx'.

    # The attribute data for 'node' goes immediately after the 'node'
    # data. the node information is _not_ yet stored, and this command
    # has to do this.


    array set r {}
    set loc($node) 0

    lappend tree $node {}
    if {[info exists attribute($node)]} {
	upvar ${name}::$attribute($node) data
	lappend tree [array get data]
    } else {
	# Encode nodes without attributes.
	lappend tree {}
    }

    foreach n [DescendantsCore $name $node] {
	set loc($n) [llength $tree]
	lappend tree $n $loc($parent($n))

	if {[info exists attribute($n)]} {
	    upvar ${name}::$attribute($n) data
	    lappend tree [array get data]
	} else {
	    # Encode nodes without attributes.
	    lappend tree {}
	}
    }

    return $tree
}


proc ::struct::tree::CheckSerialization {ser avar pvar cvar rnvar} {
    upvar 1 $avar attr $pvar p $cvar ch $rnvar rn

    # Overall length ok ?

    if {[llength $ser] % 3} {
	return -code error \
		"error in serialization: list length not a multiple of 3."
    }

    set rn {}
    array set p    {}
    array set ch   {}
    array set attr {}

    # Basic decoder pass

    foreach {node parent nattr} $ser {

	# Initialize children data, if not already done
	if {![info exists ch($node)]} {
	    set ch($node) {}
	}
	# Attribute length ok ? Dictionary!
	if {[llength $nattr] % 2} {
	    return -code error \
		    "error in serialization: malformed attribute dictionary."
	}
	# Remember attribute data only for non-empty nodes
	if {[llength $nattr]} {
	    set attr($node) $nattr
	}
	# Remember root
	if {$parent == {}} {
	    lappend rn $node
	    set p($node) {}
	    continue
	}
	# Parent reference ok ?
	##nagelfar ignore
	if {
	    ![string is integer -strict $parent] ||
	    ($parent % 3) ||
	    ($parent < 0) ||
	    ($parent >= [llength $ser])
	} {
	    return -code error \
		    "error in serialization: bad parent reference \"$parent\"."
	}
	# Remember parent, and reconstruct children

	set p($node) [lindex $ser $parent]
	lappend ch($p($node)) $node
    }

    # Root node information ok ?

    if {[llength $rn] < 1} {
	return -code error \
		"error in serialization: no root specified."
    } elseif {[llength $rn] > 1} {
	return -code error \
		"error in serialization: multiple root nodes."
    }
    set rn [lindex $rn 0]

    # Duplicate node names ?

    if {[array size ch] < ([llength $ser] / 3)} {
	return -code error \
		"error in serialization: duplicate node names."
    }

    # Cycles in the parent relationship ?

    array set visited {}
    foreach n [array names p] {
	if {[info exists visited($n)]} {continue}
	array set _ {}
	while {$n != {}} {
	    if {[info exists _($n)]} {
		# Node already converted, cycle.
		return -code error \
			"error in serialization: cycle detected."
	    }
	    set _($n)       .
	    # root ?
	    if {$p($n) == {}} {break}
	    set n $p($n)
	    if {[info exists visited($n)]} {break}
	    set visited($n) .
	}
	unset _
    }
    # Ok. The data is now ready for the caller.

    return
}

##########################
# Private functions follow
#
# Do a compatibility version of [lset] for pre-8.4 versions of Tcl.
# This version does not do multi-arg [lset]!

proc ::struct::tree::K { x y } { set x }

if { [package vcompare [package provide Tcl] 8.4] < 0 } {
    proc ::struct::tree::lset { var index arg } {
	upvar 1 $var list
	set list [::lreplace [K $list [set list {}]] $index $index $arg]
    }
}

proc ::struct::tree::ldelete {var index {end {}}} {
    upvar 1 $var list
    if {$end == {}} {set end $index}
    set list [lreplace [K $list [set list {}]] $index $end]
    return
}

# ### ### ### ######### ######### #########
## Ready

namespace eval ::struct {
    # Put 'tree::tree' into the general structure namespace
    # for pickup by the main management.

    namespace import -force tree::tree_tcl
}
