# graph_tcl.tcl --
#
#	Implementation of a graph data structure for Tcl.
#
# Copyright (c) 2000-2009,2019 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
# Copyright (c) 2008           by Alejandro Paz <vidriloco@gmail.com>
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.

package require Tcl 8.5 9
package require struct::list
package require struct::set

namespace eval ::struct::graph {
    # Data storage in the graph module
    # -------------------------------
    #
    # There's a lot of bits to keep track of for each graph:
    #	nodes
    #	node values
    #	node relationships (arcs)
    #   arc values
    #
    # It would quickly become unwieldy to try to keep these in arrays or lists
    # within the graph namespace itself.  Instead, each graph structure will
    # get its own namespace.  Each namespace contains:
    #	node:$node	array mapping keys to values for the node $node
    #	arc:$arc	array mapping keys to values for the arc $arc
    #	inArcs		array mapping nodes to the list of incoming arcs
    #	outArcs		array mapping nodes to the list of outgoing arcs
    #	arcNodes	array mapping arcs to the two nodes (start & end)
    
    # counter is used to give a unique name for unnamed graph
    variable counter 0

    # Only export one command, the one used to instantiate a new graph
    namespace export graph_tcl
}

# ::struct::graph::graph_tcl --
#
#	Create a new graph with a given name; if no name is given, use
#	graphX, where X is a number.
#
# Arguments:
#	name	name of the graph; if null, generate one.
#
# Results:
#	name	name of the graph created

proc ::struct::graph::graph_tcl {args} {
    variable counter
    
    set src     {}
    set srctype {}

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

    # Set up the namespace
    namespace eval $name {

	# Set up the map for values associated with the graph itself
	variable  graphAttr
	array set graphAttr {}

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

	# Set up the arc attribute mapping
	variable  arcAttr
	array set arcAttr {}

	# Set up the map from nodes to the arcs coming to them
	variable  inArcs
	array set inArcs {}

	# Set up the map from nodes to the arcs going out from them
	variable  outArcs
	array set outArcs {}

	# Set up the map from arcs to the nodes they touch.
	variable  arcNodes
	array set arcNodes {}

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

	# Set up a value for use in creating unique arc names
	variable nextUnusedArc
	set      nextUnusedArc 1

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

        # Set up a map from arcs to their weights. Note: Only arcs
        # which actually have a weight are recorded in the map, to
        # keep memory usage down.
        variable arcWeight
        array set arcWeight {}
    }

    # Create the command to manipulate the graph
    interp alias {} $name {} ::struct::graph::GraphProc $name

    # Automatic execution of assignment if a source
    # is present.
    if {$src != {}} {
	switch -exact -- $srctype {
	    graph  {_= $name $src}
	    serial {_deserialize $name $src}
	    default {
		return -code error \
			"Internal error, illegal srctype \"$srctype\""
	    }
	}
    }

    return $name
}

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

# ::struct::graph::GraphProc --
#
#	Command that processes all graph object commands.
#
# Arguments:
#	name	name of the graph object to manipulate.
#	args	command name and args for the command
#
# Results:
#	Varies based on command to perform

proc ::struct::graph::GraphProc {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::graph::$sub]] == 0 } {
	set optlist [lsort [info commands ::struct::graph::_*]]
	set xlist {}
	foreach p $optlist {
	    set p [namespace tail $p]
	    if {[string match __* $p]} {continue}
	    lappend xlist [string range $p 1 end]
	}
	set optlist [linsert [join $xlist ", "] "end-1" "or"]
	return -code error \
		"bad option \"$cmd\": must be $optlist"
    }
    uplevel 1 [linsert $args 0 ::struct::graph::$sub $name]
}

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

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

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

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

# ::struct::graph::_append --
#
#	Append a value for an attribute in a graph.
#
# Arguments:
#	name	name of the graph.
#	args	key value
#
# Results:
#	val	value associated with the given key of the given arc

proc ::struct::graph::_append {name key value} {
    variable ${name}::graphAttr
    return [append    graphAttr($key) $value]
}

# ::struct::graph::_lappend --
#
#	lappend a value for an attribute in a graph.
#
# Arguments:
#	name	name of the graph.
#	args	key value
#
# Results:
#	val	value associated with the given key of the given arc

proc ::struct::graph::_lappend {name key value} {
    variable ${name}::graphAttr
    return [lappend   graphAttr($key) $value]
}

# ::struct::graph::_arc --
#
#	Dispatches the invocation of arc methods to the proper handler
#	procedure.
#
# Arguments:
#	name	name of the graph.
#	cmd	arc command to invoke
#	args	arguments to propagate to the handler for the arc command
#
# Results:
#	As of the invoked handler.

proc ::struct::graph::_arc {name cmd args} {
    # Split the args into command and args components

    set sub __arc_$cmd
    if { [llength [info commands ::struct::graph::$sub]] == 0 } {
	set optlist [lsort [info commands ::struct::graph::__arc_*]]
	set xlist {}
	foreach p $optlist {
	    set p [namespace tail $p]
	    lappend xlist [string range $p 6 end]
	}
	set optlist [linsert [join $xlist ", "] "end-1" "or"]
	return -code error \
		"bad option \"$cmd\": must be $optlist"
    }
    uplevel 1 [linsert $args 0 ::struct::graph::$sub $name]
}

# ::struct::graph::__arc_delete --
#
#	Remove an arc from a graph, including all of its values.
#
# Arguments:
#	name	name of the graph.
#	args	list of arcs to delete.
#
# Results:
#	None.

proc ::struct::graph::__arc_delete {name args} {
    if {![llength $args]} {
	return {wrong # args: should be "::struct::graph::__arc_delete name arc arc..."}
    }

    # seen is used to catch duplicate arcs in the args
    array set seen {}
    foreach arc $args {
	if {[info exists seen($arc)]} {
	    return -code error "arc \"$arc\" does not exist in graph \"$name\""
	}
	CheckMissingArc $name $arc
	set seen($arc) .
    }

    variable ${name}::inArcs
    variable ${name}::outArcs
    variable ${name}::arcNodes
    variable ${name}::arcAttr
    variable ${name}::arcWeight

    foreach arc $args {
	foreach {source target} $arcNodes($arc) break ; # lassign

	unset arcNodes($arc)

	if {[info exists arcAttr($arc)]} {
	    unset ${name}::$arcAttr($arc) ;# Note the double indirection here
	    unset arcAttr($arc)
	}
	if {[info exists arcWeight($arc)]} {
	    unset arcWeight($arc)
	}

	# Remove arc from the arc lists of source and target nodes.

	set index [lsearch -exact $outArcs($source) $arc]
	ldelete outArcs($source) $index

	set index [lsearch -exact $inArcs($target)  $arc]
	ldelete inArcs($target) $index
    }

    return
}

# ::struct::graph::__arc_exists --
#
#	Test for existence of a given arc in a graph.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to look for.
#
# Results:
#	1 if the arc exists, 0 else.

proc ::struct::graph::__arc_exists {name arc} {
    return [info exists ${name}::arcNodes($arc)]
}

# ::struct::graph::__arc_flip --
#
#	Exchanges origin and destination node of the specified arc.
#
# Arguments:
#	name		name of the graph object.
#	arc		arc to change.
#
# Results:
#	None

proc ::struct::graph::__arc_flip {name arc} {
    CheckMissingArc  $name $arc

    variable ${name}::arcNodes
    variable ${name}::outArcs
    variable ${name}::inArcs

    set oldsource [lindex $arcNodes($arc) 0]
    set oldtarget [lindex $arcNodes($arc) 1]

    if {[string equal $oldsource $oldtarget]} return

    set newtarget $oldsource
    set newsource $oldtarget

    set arcNodes($arc) [lreplace $arcNodes($arc) 0 0 $newsource]
    lappend outArcs($newsource) $arc
    ldelete outArcs($oldsource) [lsearch -exact $outArcs($oldsource) $arc]

    set arcNodes($arc) [lreplace $arcNodes($arc) 1 1 $newtarget]
    lappend inArcs($newtarget) $arc
    ldelete inArcs($oldtarget) [lsearch -exact $inArcs($oldtarget) $arc]
    return
}

# ::struct::graph::__arc_get --
#
#	Get a keyed value from an arc in a graph.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to query.
#	key	key to lookup
#
# Results:
#	value	value associated with the key given.

proc ::struct::graph::__arc_get {name arc key} {
    CheckMissingArc $name $arc

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

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

# ::struct::graph::__arc_getall --
#
#	Get a serialized array of key/value pairs from an arc in a graph.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to query.
#	pattern	optional glob pattern to restrict retrieval
#
# Results:
#	value	serialized array of key/value pairs.

proc ::struct::graph::__arc_getall {name arc {pattern *}} {
    CheckMissingArc $name $arc

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

    upvar ${name}::$arcAttr($arc) data
    return [array get data $pattern]
}

# ::struct::graph::__arc_keys --
#
#	Get a list of keys for an arc in a graph.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to query.
#	pattern	optional glob pattern to restrict retrieval
#
# Results:
#	value	value associated with the key given.

proc ::struct::graph::__arc_keys {name arc {pattern *}} {
    CheckMissingArc $name $arc

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

    upvar ${name}::$arcAttr($arc) data
    return [array names data $pattern]
}

# ::struct::graph::__arc_keyexists --
#
#	Test for existence of a given key for a given arc in a graph.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to query.
#	key	key to lookup
#
# Results:
#	1 if the key exists, 0 else.

proc ::struct::graph::__arc_keyexists {name arc key} {
    CheckMissingArc $name $arc

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

    upvar ${name}::$arcAttr($arc) data
    return [info exists data($key)]
}

# ::struct::graph::__arc_insert --
#
#	Add an arc to a graph.
#
# Arguments:
#	name		name of the graph.
#	source		source node of the new arc
#	target		target node of the new arc
#	args		arc to insert; must be unique.  If none is given,
#			the routine will generate a unique node name.
#
# Results:
#	arc		The name of the new arc.

proc ::struct::graph::__arc_insert {name source target args} {

    if { [llength $args] == 0 } {
	# No arc name was given; generate a unique one
	set arc [__generateUniqueArcName $name]
    } elseif { [llength $args] > 1 } {
	return {wrong # args: should be "::struct::graph::__arc_insert name source target ?arc?"}
    } else {
	set arc [lindex $args 0]
    }

    CheckDuplicateArc $name $arc    
    CheckMissingNode  $name $source {source }
    CheckMissingNode  $name $target {target }
    
    variable ${name}::inArcs
    variable ${name}::outArcs
    variable ${name}::arcNodes

    # Set up the new arc
    set arcNodes($arc) [list $source $target]

    # Add this arc to the arc lists of its source resp. target nodes.
    lappend outArcs($source) $arc
    lappend inArcs($target)  $arc

    return $arc
}

# ::struct::graph::__arc_rename --
#
#	Rename a arc in place.
#
# Arguments:
#	name	name of the graph.
#	arc	Name of the arc to rename
#	newname	The new name of the arc.
#
# Results:
#	The new name of the arc.

proc ::struct::graph::__arc_rename {name arc newname} {
    CheckMissingArc   $name $arc
    CheckDuplicateArc $name $newname

    set oldname  $arc

    # Perform the rename in the internal
    # data structures.

    # - graphAttr - not required, arc independent.
    # - nodeAttr  - not required, arc independent.
    # - counters  - not required

    variable ${name}::arcAttr
    variable ${name}::inArcs
    variable ${name}::outArcs
    variable ${name}::arcNodes
    variable ${name}::arcWeight

    # Arc relocation

    set arcNodes($newname) [set nodes $arcNodes($oldname)]
    unset                              arcNodes($oldname)

    # Update the two nodes ...
    foreach {start end} $nodes break

    set pos [lsearch -exact $inArcs($end) $oldname]
    lset inArcs($end) $pos $newname

    set pos [lsearch -exact $outArcs($start) $oldname]
    lset outArcs($start) $pos $newname

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

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

    return $newname
}

# ::struct::graph::__arc_set --
#
#	Set or get a value for an arc in a graph.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to modify or query.
#	key	attribute to modify or query
#	args	?value?
#
# Results:
#	val	value associated with the given key of the given arc

proc ::struct::graph::__arc_set {name arc key args} {
    if { [llength $args] > 1 } {
	return -code error "wrong # args: should be \"$name arc set arc key ?value?\""
    }
    CheckMissingArc $name $arc

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

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

	upvar ${name}::$arcAttr($arc) data
	return [set data($key) [lindex $args end]]
    } else {
	# Getting a value
	return [__arc_get $name $arc $key]
    }
}

# ::struct::graph::__arc_append --
#
#	Append a value for an arc in a graph.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to modify or query.
#	args	key value
#
# Results:
#	val	value associated with the given key of the given arc

proc ::struct::graph::__arc_append {name arc key value} {
    CheckMissingArc $name $arc

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

    upvar ${name}::$arcAttr($arc) data
    return [append data($key) $value]
}

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

proc ::struct::graph::__arc_attr {name key args} {
    # Syntax:
    #
    # t attr key
    # t attr key -arcs {arclist}
    # t attr key -glob arcpattern
    # t attr key -regexp arcpattern

    variable ${name}::arcAttr

    set usage "wrong # args: should be \"[list $name] arc attr key ?-arcs 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 arcs which can have the attribute
	# in question.

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

	foreach {mode value} $args break
	switch -exact -- $mode {
	    -arcs {
		# This is the only branch where we have to
		# perform an explicit restriction to the
		# arcs which have attributes.
		set arcs {}
		foreach n $value {
		    if {![info exists arcAttr($n)]} continue
		    lappend arcs $n
		}
	    }
	    -glob {
		set arcs [array names arcAttr $value]
	    }
	    -regexp {
		set arcs {}
		foreach n [array names arcAttr] {
		    if {![regexp -- $value $n]} continue
		    lappend arcs $n
		}
	    }
	    default {
		return -code error "bad type \"$mode\": must be -arcs, -glob, or -regexp"
	    }
	}
    }

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

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

    # Now locate matching keys and their values.

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

    return $result
}

# ::struct::graph::__arc_lappend --
#
#	lappend a value for an arc in a graph.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to modify or query.
#	args	key value
#
# Results:
#	val	value associated with the given key of the given arc

proc ::struct::graph::__arc_lappend {name arc key value} {
    CheckMissingArc $name $arc

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

    upvar ${name}::$arcAttr($arc) data
    return [lappend data($key) $value]
}

# ::struct::graph::__arc_source --
#
#	Return the node at the beginning of the specified arc.
#
# Arguments:
#	name	name of the graph object.
#	arc	arc to look up.
#
# Results:
#	node	name of the node.

proc ::struct::graph::__arc_source {name arc} {
    CheckMissingArc $name $arc

    variable ${name}::arcNodes
    return [lindex $arcNodes($arc) 0]
}

# ::struct::graph::__arc_target --
#
#	Return the node at the end of the specified arc.
#
# Arguments:
#	name	name of the graph object.
#	arc	arc to look up.
#
# Results:
#	node	name of the node.

proc ::struct::graph::__arc_target {name arc} {
    CheckMissingArc $name $arc

    variable ${name}::arcNodes
    return [lindex $arcNodes($arc) 1]
}

# ::struct::graph::__arc_nodes --
#
#	Return a list containing both source and target nodes of the arc.
#
# Arguments:
#	name		name of the graph object.
#	arc		arc to look up.
#
# Results:
#	nodes	list containing the names of the connected nodes node.
#	None

proc ::struct::graph::__arc_nodes {name arc} {
    CheckMissingArc  $name $arc

    variable ${name}::arcNodes
    return $arcNodes($arc)
}

# ::struct::graph::__arc_move-target --
#
#	Change the destination node of the specified arc.
#	The arc is rotated around its origin to a different
#	node.
#
# Arguments:
#	name		name of the graph object.
#	arc		arc to change.
#	newtarget	new destination/target of the arc.
#
# Results:
#	None

proc ::struct::graph::__arc_move-target {name arc newtarget} {
    CheckMissingArc  $name $arc
    CheckMissingNode $name $newtarget

    variable ${name}::arcNodes
    variable ${name}::inArcs

    set oldtarget [lindex $arcNodes($arc) 1]
    if {[string equal $oldtarget $newtarget]} return

    set arcNodes($arc) [lreplace $arcNodes($arc) 1 1 $newtarget]

    lappend inArcs($newtarget) $arc
    ldelete inArcs($oldtarget) [lsearch -exact $inArcs($oldtarget) $arc]
    return
}

# ::struct::graph::__arc_move-source --
#
#	Change the origin node of the specified arc.
#	The arc is rotated around its destination to a different
#	node.
#
# Arguments:
#	name		name of the graph object.
#	arc		arc to change.
#	newsource	new origin/source of the arc.
#
# Results:
#	None

proc ::struct::graph::__arc_move-source {name arc newsource} {
    CheckMissingArc  $name $arc
    CheckMissingNode $name $newsource

    variable ${name}::arcNodes
    variable ${name}::outArcs

    set oldsource [lindex $arcNodes($arc) 0]
    if {[string equal $oldsource $newsource]} return

    set arcNodes($arc) [lreplace $arcNodes($arc) 0 0 $newsource]

    lappend outArcs($newsource) $arc
    ldelete outArcs($oldsource) [lsearch -exact $outArcs($oldsource) $arc]
    return
}

# ::struct::graph::__arc_move --
#
#	Changes both origin and destination node of the specified arc.
#
# Arguments:
#	name		name of the graph object.
#	arc		arc to change.
#	newsource	new origin/source of the arc.
#	newtarget	new destination/target of the arc.
#
# Results:
#	None

proc ::struct::graph::__arc_move {name arc newsource newtarget} {
    CheckMissingArc  $name $arc
    CheckMissingNode $name $newsource
    CheckMissingNode $name $newtarget

    variable ${name}::arcNodes
    variable ${name}::outArcs
    variable ${name}::inArcs

    set oldsource [lindex $arcNodes($arc) 0]
    if {![string equal $oldsource $newsource]} {
	set arcNodes($arc) [lreplace $arcNodes($arc) 0 0 $newsource]
	lappend outArcs($newsource) $arc
	ldelete outArcs($oldsource) [lsearch -exact $outArcs($oldsource) $arc]
    }

    set oldtarget [lindex $arcNodes($arc) 1]
    if {![string equal $oldtarget $newtarget]} {
	set arcNodes($arc) [lreplace $arcNodes($arc) 1 1 $newtarget]
	lappend inArcs($newtarget) $arc
	ldelete inArcs($oldtarget) [lsearch -exact $inArcs($oldtarget) $arc]
    }
    return
}

# ::struct::graph::__arc_unset --
#
#	Remove a keyed value from a arc.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to modify.
#	key	attribute to remove
#
# Results:
#	None.

proc ::struct::graph::__arc_unset {name arc key} {
    CheckMissingArc $name $arc

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

    upvar ${name}::$arcAttr($arc) data
    catch {unset data($key)}

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

# ::struct::graph::__arc_getunweighted --
#
#	Return the arcs which have no weight defined.
#
# Arguments:
#	name	name of the graph.
#
# Results:
#	arcs	list of arcs without weights.

proc ::struct::graph::__arc_getunweighted {name} {
    variable ${name}::arcNodes
    variable ${name}::arcWeight
    return [struct::set difference \
		[array names arcNodes] \
		[array names arcWeight]]
}

# ::struct::graph::__arc_getweight --
#
#	Get the weight given to an arc in a graph.
#	Throws an error if the arc has no weight defined for it.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to query.
#
# Results:
#	weight	The weight defined for the arc.

proc ::struct::graph::__arc_getweight {name arc} {
    CheckMissingArc $name $arc

    variable ${name}::arcWeight
    if {![info exists arcWeight($arc)]} {
	return -code error "arc \"$arc\" has no weight"
    }
    return $arcWeight($arc)
}

# ::struct::graph::__arc_setunweighted --
#
#	Define a weight for all arcs which have no weight defined.
#	After this call no arc will be unweighted.
#
# Arguments:
#	name	name of the graph.
#	defval	weight to give to all unweighted arcs
#
# Results:
#	None

proc ::struct::graph::__arc_setunweighted {name {weight 0}} {
    variable ${name}::arcWeight
    foreach arc [__arc_getunweighted $name] {
	set arcWeight($arc) $weight
    }
    return
}

# ::struct::graph::__arc_setweight --
#
#	Define a weight for an arc.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to modify
#	weight	the weight to set for the arc
#
# Results:
#	weight	The new weight

proc ::struct::graph::__arc_setweight {name arc weight} {
    CheckMissingArc $name $arc

    variable ${name}::arcWeight
    set arcWeight($arc) $weight
    return $weight 
}

# ::struct::graph::__arc_unsetweight --
#
#	Remove the weight for an arc.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to modify
#
# Results:
#	None.

proc ::struct::graph::__arc_unsetweight {name arc} {
    CheckMissingArc $name $arc

    variable ${name}::arcWeight
    if {[info exists arcWeight($arc)]} {
	unset arcWeight($arc)
    }
    return
}

# ::struct::graph::__arc_hasweight --
#
#	Remove the weight for an arc.
#
# Arguments:
#	name	name of the graph.
#	arc	arc to modify
#
# Results:
#	None.

proc ::struct::graph::__arc_hasweight {name arc} {
    CheckMissingArc $name $arc

    variable ${name}::arcWeight
    return [info exists arcWeight($arc)]
}

# ::struct::graph::__arc_weights --
#
#	Return the arcs and weights for all arcs which have such.
#
# Arguments:
#	name	name of the graph.
#
# Results:
#	aw	dictionary mapping arcs to their weights.

proc ::struct::graph::__arc_weights {name} {
    variable ${name}::arcWeight
    return [array get arcWeight]
}

# ::struct::graph::_arcs --
#
#	Return a list of all arcs in a graph satisfying some
#	node based restriction.
#
# Arguments:
#	name	name of the graph.
#
# Results:
#	arcs	list of arcs

proc ::struct::graph::_arcs {name args} {

    CheckE $name arcs $args

    switch -exact -- $cond {
	none      {set arcs [ArcsNONE $name]}
	in        {set arcs [ArcsIN   $name $condNodes]}
	out       {set arcs [ArcsOUT  $name $condNodes]}
	adj       {set arcs [ArcsADJ  $name $condNodes]}
	inner     {set arcs [ArcsINN  $name $condNodes]}
	embedding {set arcs [ArcsEMB  $name $condNodes]}
	default   {return -code error "Can't happen, panic"}
    }

    #
    # We have a list of arcs that match the relation to the nodes.
    # Now filter according to -key and -value.
    #

    if {$haveKey && $haveValue} {
	set arcs [ArcsKV $name $key $value $arcs]
    } elseif {$haveKey} {
	set arcs [ArcsK $name $key $arcs]
    }

    #
    # Apply the general filter command, if specified.
    #

    if {$haveFilter} {
	lappend fcmd $name
	set arcs [uplevel 1 [list ::struct::list filter $arcs $fcmd]]
    }

    return $arcs
}

proc ::struct::graph::ArcsIN {name cn} {
    # arcs -in.	"Arcs going into the node set"
    #
    # ARC/in (NS) := { a | target(a) in NS }

    # The result is all arcs going to at least one node in the set
    # 'cn' of nodes.

    # As an arc has only one destination, i.e. is the
    # in-arc of exactly one node it is impossible to
    # count an arc twice. Therefore there is no need
    # to keep track of arcs to avoid duplicates.

    variable ${name}::inArcs

    set arcs {}
    foreach node $cn {
	foreach e $inArcs($node) {
	    lappend arcs $e
	}
    }

    return $arcs
}

proc ::struct::graph::ArcsOUT {name cn} {
    # arcs -out. "Arcs coming from the node set"
    #
    # ARC/out (NS) := { a | source(a) in NS }

    # The result is all arcs coming from at least one node in the list
    # of arguments.

    variable ${name}::outArcs

    set arcs {}
    foreach node $cn {
	foreach e $outArcs($node) {
	    lappend arcs $e
	}
    }

    return $arcs
}

proc ::struct::graph::ArcsADJ {name cn} {
    # arcs -adj. "Arcs adjacent to the node set"
    #
    # ARC/adj (NS) := ARC/in (NS) + ARC/out (NS)

    # Result is all arcs coming from or going to at
    # least one node in the list of arguments.

    return [struct::set union \
	    [ArcsIN  $name $cn] \
	    [ArcsOUT $name $cn]]
    if 0 {
	# Alternate implementation using arrays,
	# implementing the set union directly,
	# intertwined with the data retrieval.

	array set coll  {}
	foreach node $condNodes {
	    foreach e $inArcs($node) {
		if {[info exists coll($e)]} {continue}
		lappend arcs     $e
		set     coll($e) .
	    }
	    foreach e $outArcs($node) {
		if {[info exists coll($e)]} {continue}
		lappend arcs     $e
		set     coll($e) .
	    }
	}
    }
}

proc ::struct::graph::ArcsINN {name cn} {
    # arcs -adj. "Arcs inside the node set"
    #
    # ARC/inner (NS) := ARC/in (NS) * ARC/out (NS)

    # Result is all arcs running between nodes
    # in the list.

    return [struct::set intersect \
	    [ArcsIN  $name $cn] \
	    [ArcsOUT $name $cn]]
    if 0 {
	# Alternate implementation using arrays,
	# implementing the set intersection
	# directly, intertwined with the data
	# retrieval.

	array set coll  {}
	# Here we do need 'coll' as each might be an in- and
	# out-arc for one or two nodes in the list of arguments.

	array set group {}
	foreach node $condNodes {
	    set group($node) .
	}

	foreach node $condNodes {
	    foreach e $inArcs($node) {
		set n [lindex $arcNodes($e) 0]
		if {![info exists group($n)]} {continue}
		if { [info exists coll($e)]}  {continue}
		lappend arcs    $e
		set     coll($e) .
	    }
	    # Second iteration over outgoing arcs not
	    # required. Any arc found above would be found here as
	    # well, and arcs not recognized above can't be
	    # recognized by the out loop either.
	}
    }
}

proc ::struct::graph::ArcsEMB {name cn} {
    # arcs -adj. "Arcs bordering the node set"
    #
    # ARC/emb (NS) := ARC/inner (NS) - ARC/adj (NS)
    # <=> (ARC/in + ARC/out) - (ARC/in * ARC/out)
    # <=> (ARC/in - ARC/out) + (ARC/out - ARC/in)
    # <=> symmetric difference (ARC/in, ARC/out)

    # Result is all arcs from -adj minus the arcs from -inner.
    # IOW all arcs going from a node in the list to a node
    # which is *not* in the list

    return [struct::set symdiff \
	    [ArcsIN  $name $cn] \
	    [ArcsOUT $name $cn]]
    if 0 {
	# Alternate implementation using arrays,
	# implementing the set intersection
	# directly, intertwined with the data
	# retrieval.

	# This also means that no arc can be counted twice as it
	# is either going to a node, or coming from a node in the
	# list, but it can't do both, because then it is part of
	# -inner, which was excluded!

	array set group {}
	foreach node $condNodes {
	    set group($node) .
	}

	foreach node $condNodes {
	    foreach e $inArcs($node) {
		set n [lindex $arcNodes($e) 0]
		if {[info exists group($n)]} {continue}
		# if {[info exists coll($e)]}  {continue}
		lappend arcs    $e
		# set     coll($e) .
	    }
	    foreach e $outArcs($node) {
		set n [lindex $arcNodes($e) 1]
		if {[info exists group($n)]} {continue}
		# if {[info exists coll($e)]}  {continue}
		lappend arcs    $e
		# set     coll($e) .
	    }
	}
    }
}

proc ::struct::graph::ArcsNONE {name} {
    variable ${name}::arcNodes
    return [array names arcNodes]
}

proc ::struct::graph::ArcsKV {name key value arcs} {
    set filteredArcs {}
    foreach arc $arcs {
	catch {
	    set aval [__arc_get $name $arc $key]
	    if {$aval == $value} {
		lappend filteredArcs $arc
	    }
	}
    }
    return $filteredArcs
}

proc ::struct::graph::ArcsK {name key arcs} {
    set filteredArcs {}
    foreach arc $arcs {
	catch {
	    __arc_get $name $arc $key
	    lappend filteredArcs $arc
	}
    }
    return $filteredArcs
}

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

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

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

    CheckSerialization $serial \
	    gattr nattr aattr ina outa arcn arcw

    # Get all the relevant data into the scope

    variable ${name}::graphAttr
    variable ${name}::nodeAttr
    variable ${name}::arcAttr
    variable ${name}::inArcs
    variable ${name}::outArcs
    variable ${name}::arcNodes
    variable ${name}::nextAttr
    variable ${name}::arcWeight

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

    array unset inArcs *
    array unset outArcs *
    array set   inArcs   [array get ina]
    array set   outArcs  [array get outa]
    unset ina outa

    array unset arcNodes *
    array set   arcNodes [array get arcn]
    unset arcn

    array unset arcWeight *
    array set   arcWeight [array get arcw]
    unset arcw

    set nextAttr 0
    foreach a [array names nodeAttr] {
	unset ${name}::$nodeAttr($a)
    }
    foreach a [array names arcAttr] {
	unset ${name}::$arcAttr($a)
    }
    foreach n [array names nattr] {
	GenAttributeStorage $name node $n
	array set ${name}::$nodeAttr($n) $nattr($n)
    }
    foreach a [array names aattr] {
	GenAttributeStorage $name arc $a
	array set ${name}::$arcAttr($a) $aattr($a)
    }

    array unset graphAttr *
    array set   graphAttr $gattr

    ## Debug ## Dump internals ...
    if {0} {
	puts "___________________________________ $name"
	parray inArcs
	parray outArcs
	parray arcNodes
	parray nodeAttr
	parray arcAttr
	parray graphAttr
	parray arcWeight
	puts ___________________________________
    }
    return
}

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

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

# ::struct::graph::__generateUniqueArcName --
#
#	Generate a unique arc name for the given graph.
#
# Arguments:
#	name	name of the graph.
#
# Results:
#	arc	name of a arc guaranteed to not exist in the graph.

proc ::struct::graph::__generateUniqueArcName {name} {
    variable ${name}::nextUnusedArc
    while {[__arc_exists $name "arc${nextUnusedArc}"]} {
	incr nextUnusedArc
    }
    return "arc${nextUnusedArc}"
}

# ::struct::graph::__generateUniqueNodeName --
#
#	Generate a unique node name for the given graph.
#
# Arguments:
#	name	name of the graph.
#
# Results:
#	node	name of a node guaranteed to not exist in the graph.

proc ::struct::graph::__generateUniqueNodeName {name} {
    variable ${name}::nextUnusedNode
    while {[__node_exists $name "node${nextUnusedNode}"]} {
	incr nextUnusedNode
    }
    return "node${nextUnusedNode}"
}

# ::struct::graph::_get --
#
#	Get a keyed value from the graph itself
#
# Arguments:
#	name	name of the graph.
#	key	key to lookup
#
# Results:
#	value	value associated with the key given.

proc ::struct::graph::_get {name key} {
    variable  ${name}::graphAttr
    if { ![info exists graphAttr($key)] } {
	return -code error "invalid key \"$key\" for graph \"$name\""
    }
    return $graphAttr($key)
}

# ::struct::graph::_getall --
#
#	Get an attribute dictionary from a graph.
#
# Arguments:
#	name	name of the graph.
#	pattern	optional, glob pattern
#
# Results:
#	value	value associated with the key given.

proc ::struct::graph::_getall {name {pattern *}} { 
    variable ${name}::graphAttr
    return [array get graphAttr $pattern]
}

# ::struct::graph::_keys --
#
#	Get a list of keys from a graph.
#
# Arguments:
#	name	name of the graph.
#	pattern	optional, glob pattern
#
# Results:
#	value	list of known keys

proc ::struct::graph::_keys {name {pattern *}} { 
    variable   ${name}::graphAttr
    return [array names graphAttr $pattern]
}

# ::struct::graph::_keyexists --
#
#	Test for existence of a given key in a graph.
#
# Arguments:
#	name	name of the graph.
#	key	key to lookup
#
# Results:
#	1 if the key exists, 0 else.

proc ::struct::graph::_keyexists {name key} {
    variable   ${name}::graphAttr
    return [info exists graphAttr($key)]
}

# ::struct::graph::_node --
#
#	Dispatches the invocation of node methods to the proper handler
#	procedure.
#
# Arguments:
#	name	name of the graph.
#	cmd	node command to invoke
#	args	arguments to propagate to the handler for the node command
#
# Results:
#	As of the the invoked handler.

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

# ::struct::graph::__node_degree --
#
#	Return the number of arcs adjacent to the specified node.
#	If one of the restrictions -in or -out is given only
#	incoming resp. outgoing arcs are counted.
#
# Arguments:
#	name	name of the graph.
#	args	option, followed by the node.
#
# Results:
#	None.

proc ::struct::graph::__node_degree {name args} {

    if {([llength $args] < 1) || ([llength $args] > 2)} {
	return -code error "wrong # args: should be \"$name node degree ?-in|-out? node\""
    }

    switch -exact -- [llength $args] {
	1 {
	    set opt {}
	    set node [lindex $args 0]
	}
	2 {
	    set opt  [lindex $args 0]
	    set node [lindex $args 1]
	}
	default {return -code error "Can't happen, panic"}
    }

    # Validate the option.

    switch -exact -- $opt {
	{}   -
	-in  -
	-out {}
	default {
	    return -code error "bad option \"$opt\": must be -in or -out"
	}
    }

    # Validate the node

    CheckMissingNode $name $node

    variable ${name}::inArcs
    variable ${name}::outArcs

    switch -exact -- $opt {
	-in  {
	    set result [llength $inArcs($node)]
	}
	-out {
	    set result [llength $outArcs($node)]
	}
	{} {
	    set result [expr {[llength $inArcs($node)] \
		    + [llength $outArcs($node)]}]

	    # loops count twice, don't do <set> arithmetics, i.e. no union!
	    if {0} {
		array set coll  {}
		set result [llength $inArcs($node)]

		foreach e $inArcs($node) {
		    set coll($e) .
		}
		foreach e $outArcs($node) {
		    if {[info exists coll($e)]} {continue}
		    incr result
		    set     coll($e) .
		}
	    }
	}
	default {return -code error "Can't happen, panic"}
    }

    return $result
}

# ::struct::graph::__node_delete --
#
#	Remove a node from a graph, including all of its values.
#	Additionally removes the arcs connected to this node.
#
# Arguments:
#	name	name of the graph.
#	args	list of the nodes to delete.
#
# Results:
#	None.

proc ::struct::graph::__node_delete {name args} {
    if {![llength $args]} {
	return {wrong # args: should be "::struct::graph::__node_delete name node node..."}
    }
    # seen is used to catch duplicate nodes in the args
    array set seen {}
    foreach node $args {
	if {[info exists seen($node)]} {
	    return -code error "node \"$node\" does not exist in graph \"$name\""
	}
	CheckMissingNode $name $node
	set seen($node) .
    }

    variable ${name}::inArcs
    variable ${name}::outArcs
    variable ${name}::nodeAttr

    foreach node $args {
	# Remove all the arcs connected to this node
	foreach e $inArcs($node) {
	    __arc_delete $name $e
	}
	foreach e $outArcs($node) {
	    # Check existence to avoid problems with
	    # loops (they are in and out arcs! at
	    # the same time and thus already deleted)
	    if { [__arc_exists $name $e] } {
		__arc_delete $name $e
	    }
	}

	unset inArcs($node)
	unset outArcs($node)

	if {[info exists nodeAttr($node)]} {
	    unset ${name}::$nodeAttr($node)
	    unset nodeAttr($node)
	}
    }

    return
}

# ::struct::graph::__node_exists --
#
#	Test for existence of a given node in a graph.
#
# Arguments:
#	name	name of the graph.
#	node	node to look for.
#
# Results:
#	1 if the node exists, 0 else.

proc ::struct::graph::__node_exists {name node} {
    return [info exists ${name}::inArcs($node)]
}

# ::struct::graph::__node_get --
#
#	Get a keyed value from a node in a graph.
#
# Arguments:
#	name	name of the graph.
#	node	node to query.
#	key	key to lookup
#
# Results:
#	value	value associated with the key given.

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

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

# ::struct::graph::__node_getall --
#
#	Get a serialized list of key/value pairs from a node in a graph.
#
# Arguments:
#	name	name of the graph.
#	node	node to query.
#	pattern	optional glob pattern to restrict retrieval
#
# Results:
#	value	value associated with the key given.

proc ::struct::graph::__node_getall {name node {pattern *}} { 
    CheckMissingNode $name $node

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

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

# ::struct::graph::__node_keys --
#
#	Get a list of keys from a node in a graph.
#
# Arguments:
#	name	name of the graph.
#	node	node to query.
#	pattern	optional glob pattern to restrict retrieval
#
# Results:
#	value	value associated with the key given.

proc ::struct::graph::__node_keys {name node {pattern *}} { 
    CheckMissingNode $name $node

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

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

# ::struct::graph::__node_keyexists --
#
#	Test for existence of a given key for a node in a graph.
#
# Arguments:
#	name	name of the graph.
#	node	node to query.
#	key	key to lookup
#
# Results:
#	1 if the key exists, 0 else.

proc ::struct::graph::__node_keyexists {name node key} {
    CheckMissingNode $name $node
    
    variable ${name}::nodeAttr
    if {![info exists nodeAttr($node)]} {
	# No attribute data for this node, key cannot exist.
	return 0
    }

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

# ::struct::graph::__node_insert --
#
#	Add a node to a graph.
#
# Arguments:
#	name		name of the graph.
#	args		node to insert; must be unique.  If none is given,
#			the routine will generate a unique node name.
#
# Results:
#	node		The name of the new node.

proc ::struct::graph::__node_insert {name args} {
    if {[llength $args] == 0} {
	# No node name was given; generate a unique one
	set args [list [__generateUniqueNodeName $name]]
    } else {
	# seen is used to catch duplicate nodes in the args
	array set seen {}
	foreach node $args {
	    if {[info exists seen($node)]} {
		return -code error "node \"$node\" already exists in graph \"$name\""
	    }
	    CheckDuplicateNode $name $node
	    set seen($node) .
	}
    }
    
    variable ${name}::inArcs
    variable ${name}::outArcs

    foreach node $args {
	# Set up the new node
	set inArcs($node)  {}
	set outArcs($node) {}
    }

    return $args
}

# ::struct::graph::__node_opposite --
#
#	Retrieve node opposite to the specified one, along the arc.
#
# Arguments:
#	name		name of the graph.
#	node		node to look up.
#	arc		arc to look up.
#
# Results:
#	nodex	Node opposite to <node,arc>

proc ::struct::graph::__node_opposite {name node arc} {
    CheckMissingNode $name $node    
    CheckMissingArc  $name $arc

    variable ${name}::arcNodes

    # Node must be connected to at least one end of the arc.

    if {[string equal $node [lindex $arcNodes($arc) 0]]} {
	set result [lindex $arcNodes($arc) 1]
    } elseif {[string equal $node [lindex $arcNodes($arc) 1]]} {
	set result [lindex $arcNodes($arc) 0]
    } else {
	return -code error "node \"$node\" and arc \"$arc\" are not connected\
		in graph \"$name\""
    }

    return $result
}

# ::struct::graph::__node_set --
#
#	Set or get a value for a node in a graph.
#
# Arguments:
#	name	name of the graph.
#	node	node to modify or query.
#	key	attribute to modify or query
#	args	?value?
#
# Results:
#	val	value associated with the given key of the given node

proc ::struct::graph::__node_set {name node key args} {
    if { [llength $args] > 1 } {
	return -code error "wrong # args: should be \"$name node set node key ?value?\""
    }
    CheckMissingNode $name $node
    
    if { [llength $args] > 0 } {
	# Setting the value. This may have to create
	# the attribute array for this particular
	# node

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

	return [set data($key) [lindex $args end]]
    } else {
	# Getting a value
	return [__node_get $name $node $key]
    }
}

# ::struct::graph::__node_append --
#
#	Append a value for a node in a graph.
#
# Arguments:
#	name	name of the graph.
#	node	node to modify or query.
#	args	key value
#
# Results:
#	val	value associated with the given key of the given node

proc ::struct::graph::__node_append {name node key value} {
    CheckMissingNode $name $node

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

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

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

proc ::struct::graph::__node_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}::nodeAttr

    set usage "wrong # args: should be \"[list $name] node 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 nodeAttr]
    } 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 nodeAttr($n)]} continue
		    lappend nodes $n
		}
	    }
	    -glob {
		set nodes [array names nodeAttr $value]
	    }
	    -regexp {
		set nodes {}
		foreach n [array names nodeAttr] {
		    if {![regexp -- $value $n]} continue
		    lappend nodes $n
		}
	    }
	    default {
		return -code error "bad type \"$mode\": must be -glob, -nodes, or -regexp"
	    }
	}
    }

    # 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}::$nodeAttr($n) data
	if {[info exists data($key)]} {
	    lappend result $n $data($key)
	}
    }

    return $result
}

# ::struct::graph::__node_lappend --
#
#	lappend a value for a node in a graph.
#
# Arguments:
#	name	name of the graph.
#	node	node to modify or query.
#	args	key value
#
# Results:
#	val	value associated with the given key of the given node

proc ::struct::graph::__node_lappend {name node key value} {
    CheckMissingNode $name $node

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

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

# ::struct::graph::__node_unset --
#
#	Remove a keyed value from a node.
#
# Arguments:
#	name	name of the graph.
#	node	node to modify.
#	key	attribute to remove
#
# Results:
#	None.

proc ::struct::graph::__node_unset {name node key} {
    CheckMissingNode $name $node

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

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

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

# ::struct::graph::_nodes --
#
#	Return a list of all nodes in a graph satisfying some restriction.
#
# Arguments:
#	name	name of the graph.
#	args	list of options and nodes specifying the restriction.
#
# Results:
#	nodes	list of nodes

proc ::struct::graph::_nodes {name args} {

    CheckE $name nodes $args

    switch -exact -- $cond {
	none      {set nodes [NodesNONE $name]}
	in        {set nodes [NodesIN   $name $condNodes]}
	out       {set nodes [NodesOUT  $name $condNodes]}
	adj       {set nodes [NodesADJ  $name $condNodes]}
	inner     {set nodes [NodesINN  $name $condNodes]}
	embedding {set nodes [NodesEMB  $name $condNodes]}
	default   {return -code error "Can't happen, panic"}
    }

    #
    # We have a list of nodes that match the relation to the nodes.
    # Now filter according to -key and -value.
    #

    if {$haveKey && $haveValue} {
	set nodes [NodesKV $name $key $value $nodes]
    } elseif {$haveKey} {
	set nodes [NodesK $name $key $nodes]
    }

    #
    # Apply the general filter command, if specified.
    #

    if {$haveFilter} {
	lappend fcmd $name
	set nodes [uplevel 1 [list ::struct::list filter $nodes $fcmd]]
    }

    return $nodes
}

proc ::struct::graph::NodesIN {name cn} {
    # nodes -in.
    # "Neighbours with arcs going into the node set"
    #
    # NODES/in (NS) := { source(a) | a in ARC/in (NS) }

    # Result is all nodes with at least one arc going to
    # at least one node in the list of arguments.

    variable ${name}::inArcs
    variable ${name}::arcNodes

    set nodes {}
    array set coll {}

    foreach node $cn {
	foreach e $inArcs($node) {
	    set n [lindex $arcNodes($e) 0]
	    if {[info exists coll($n)]} {continue}
	    lappend nodes    $n
	    set     coll($n) .
	}
    }
    return $nodes
}

proc ::struct::graph::NodesOUT {name cn} {
    # nodes -out.
    # "Neighbours with arcs coming from the node set"
    #
    # NODES/out (NS) := { target(a) | a in ARC/out (NS) }

    # Result is all nodes with at least one arc coming from
    # at least one node in the list of arguments.

    variable ${name}::outArcs
    variable ${name}::arcNodes

    set nodes {}
    array set coll {}

    foreach node $cn {
	foreach e $outArcs($node) {
	    set n [lindex $arcNodes($e) 1]
	    if {[info exists coll($n)]} {continue}
	    lappend nodes    $n
	    set     coll($n) .
	}
    }
    return $nodes
}

proc ::struct::graph::NodesADJ {name cn} {
    # nodes -adj.
    # "Neighbours of the node set"
    #
    # NODES/adj (NS) := NODES/in (NS) + NODES/out (NS)

    # Result is all nodes with at least one arc coming from
    # or going to at least one node in the list of arguments.

    return [struct::set union \
	    [NodesIN  $name $cn] \
	    [NodesOUT $name $cn]]
    if 0 {
	# Alternate implementation using arrays,
	# implementing the set union directly,
	# intertwined with the data retrieval.

	foreach node $cn {
	    foreach e $inArcs($node) {
		set n [lindex $arcNodes($e) 0]
		if {[info exists coll($n)]} {continue}
		lappend nodes    $n
		set     coll($n) .
	    }
	    foreach e $outArcs($node) {
		set n [lindex $arcNodes($e) 1]
		if {[info exists coll($n)]} {continue}
		lappend nodes    $n
		set     coll($n) .
	    }
	}
    }
}

proc ::struct::graph::NodesINN {name cn} {
    # nodes -adj.
    # "Inner node of the node set"
    #
    # NODES/inner (NS) := NODES/adj (NS) * NS

    # Result is all nodes from the set with at least one arc coming
    # from or going to at least one node in the set.
    #
    # I.e the adjacent nodes also in the set.

    return [struct::set intersect \
	    [NodesADJ $name $cn] $cn]

    if 0 {
	# Alternate implementation using arrays,
	# implementing the set intersect/union
	# directly, intertwined with the data retrieval.

	array set group {}
	foreach node $cn {
	    set group($node) .
	}

	foreach node $cn {
	    foreach e $inArcs($node) {
		set n [lindex $arcNodes($e) 0]
		if {![info exists group($n)]} {continue}
		if { [info exists coll($n)]}  {continue}
		lappend nodes    $n
		set     coll($n) .
	    }
	    foreach e $outArcs($node) {
		set n [lindex $arcNodes($e) 1]
		if {![info exists group($n)]} {continue}
		if { [info exists coll($n)]}  {continue}
		lappend nodes    $n
		set     coll($n) .
	    }
	}
    }
}

proc ::struct::graph::NodesEMB {name cn} {
    # nodes -embedding.
    # "Embedding nodes for the node set"
    #
    # NODES/emb (NS) := NODES/adj (NS) - NS

    # Result is all nodes with at least one arc coming from or going
    # to at least one node in the set, but not in the set itself
    #
    # I.e the adjacent nodes not in the set.

    # Result is all nodes from the set with at least one arc coming
    # from or going to at least one node in the set.
    # I.e the adjacent nodes still in the set.

    return [struct::set difference \
	    [NodesADJ $name $cn] $cn]

    if 0 {
	# Alternate implementation using arrays,
	# implementing the set diff/union directly,
	# intertwined with the data retrieval.

	array set group {}
	foreach node $cn {
	    set group($node) .
	}

	foreach node $cn {
	    foreach e $inArcs($node) {
		set n [lindex $arcNodes($e) 0]
		if {[info exists group($n)]} {continue}
		if {[info exists coll($n)]}  {continue}
		lappend nodes    $n
		set     coll($n) .
	    }
	    foreach e $outArcs($node) {
		set n [lindex $arcNodes($e) 1]
		if {[info exists group($n)]} {continue}
		if {[info exists coll($n)]}  {continue}
		lappend nodes    $n
		set     coll($n) .
	    }
	}
    }
}

proc ::struct::graph::NodesNONE {name} {
    variable ${name}::inArcs
    return [array names inArcs]
}

proc ::struct::graph::NodesKV {name key value nodes} {
    set filteredNodes {}
    foreach node $nodes {
	catch {
	    set nval [__node_get $name $node $key]
	    if {$nval == $value} {
		lappend filteredNodes $node
	    }
	}
    }
    return $filteredNodes
}

proc ::struct::graph::NodesK {name key nodes} {
    set filteredNodes {}
    foreach node $nodes {
	catch {
	    __node_get $name $node $key
	    lappend filteredNodes $node
	}
    }
    return $filteredNodes
}

# ::struct::graph::__node_rename --
#
#	Rename a node in place.
#
# Arguments:
#	name	name of the graph.
#	node	Name of the node to rename
#	newname	The new name of the node.
#
# Results:
#	The new name of the node.

proc ::struct::graph::__node_rename {name node newname} {
    CheckMissingNode   $name $node
    CheckDuplicateNode $name $newname

    set oldname  $node

    # Perform the rename in the internal
    # data structures.

    # - graphAttr - not required, node independent.
    # - arcAttr   - not required, node independent.
    # - counters  - not required

    variable ${name}::nodeAttr
    variable ${name}::inArcs
    variable ${name}::outArcs
    variable ${name}::arcNodes

    # Node relocation

    set inArcs($newname)    [set in $inArcs($oldname)]
    unset                            inArcs($oldname)
    set outArcs($newname) [set out $outArcs($oldname)]
    unset                           outArcs($oldname)

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

    # Update all relevant arcs.
    # 8.4: lset ...

    foreach a $in {
	set arcNodes($a) [list [lindex $arcNodes($a) 0] $newname]
    }
    foreach a $out {
	set arcNodes($a) [list $newname [lindex $arcNodes($a) 1]]
    }

    return $newname
}

# ::struct::graph::_serialize --
#
#	Serialize a graph object (partially) into a transportable value.
#	If only a subset of nodes is serialized the result will be a sub-
#	graph in the mathematical sense of the word: These nodes and all
#	arcs which are only between these nodes. No arcs to modes outside
#	of the listed set.
#
# Arguments:
#	name	Name of the graph.
#	args	list of nodes to place into the serialized graph
#
# Results:
#	A list structure describing the part of the graph which was serialized.

proc ::struct::graph::_serialize {name args} {

    # all - boolean flag - set if and only if the all nodes of the
    # graph are chosen for serialization. Because if that is true we
    # can skip the step finding the relevant arcs and simply take all
    # arcs.

    variable ${name}::arcNodes
    variable ${name}::arcWeight
    variable ${name}::inArcs

    set all 0
    if {[llength $args] > 0} {
	set nodes [luniq $args]
	foreach n $nodes {CheckMissingNode $name $n}
	if {[llength $nodes] == [array size inArcs]} {
	    set all 1
	}
    } else {
	set nodes [array names inArcs]
	set all 1
    }

    if {$all} {
	set arcs [array names arcNodes]
    } else {
	set arcs [eval [linsert $nodes 0 _arcs $name -inner]]
    }

    variable ${name}::nodeAttr
    variable ${name}::arcAttr
    variable ${name}::graphAttr

    set na {}
    set aa {}
    array set np {}

    # node indices, attribute data ...
    set i 0
    foreach n $nodes {
	set np($n) [list $i]
	incr i 3

	if {[info exists nodeAttr($n)]} {
	    upvar ${name}::$nodeAttr($n) data
	    lappend np($n) [array get data]
	} else {
	    lappend np($n) {}
	}
    }

    # arc dictionary
    set arcdata  {}
    foreach a $arcs {
	foreach {src dst} $arcNodes($a) break
	# Arc information

	set     arc [list $a]
	lappend arc [lindex $np($dst) 0]
	if {[info exists arcAttr($a)]} {
	    upvar ${name}::$arcAttr($a) data
	    lappend arc [array get data]
	} else {
	    lappend arc {}
	}

	# Add weight information, if there is any.

	if {[info exists arcWeight($a)]} {
	    lappend arc $arcWeight($a)
	}

	# Add the information to the node
	# indices ...

	lappend np($src) $arc
    }

    # Combine the transient data into one result.

    set result [list]
    foreach n $nodes {
	lappend result $n
	lappend result [lindex $np($n) 1]
	lappend result [lrange $np($n) 2 end]
    }
    lappend result [array get graphAttr]

    return $result
}

# ::struct::graph::_set --
#
#	Set or get a keyed value from the graph itself
#
# Arguments:
#	name	name of the graph.
#	key	attribute to modify or query
#	args	?value?
#
# Results:
#	value	value associated with the key given.

proc ::struct::graph::_set {name key args} {
    if { [llength $args] > 1 } {
	return -code error "wrong # args: should be \"$name set key ?value?\""
    }
    if { [llength $args] > 0 } {
	variable ${name}::graphAttr
	return [set graphAttr($key) [lindex $args end]]
    } else {
	# Getting a value
	return [_get $name $key]
    }
}

# ::struct::graph::_swap --
#
#	Swap two nodes in a graph.
#
# Arguments:
#	name	name of the graph.
#	node1	first node to swap.
#	node2	second node to swap.
#
# Results:
#	None.

proc ::struct::graph::_swap {name node1 node2} {
    # Can only swap two real nodes
    CheckMissingNode $name $node1
    CheckMissingNode $name $node2

    # 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, values and arcs
    variable ${name}::outArcs
    variable ${name}::inArcs
    variable ${name}::arcNodes
    variable ${name}::nodeAttr

    # Redirect arcs to the new nodes.

    foreach e $inArcs($node1)  {lset arcNodes($e) end $node2}
    foreach e $inArcs($node2)  {lset arcNodes($e) end $node1}
    foreach e $outArcs($node1) {lset arcNodes($e) 0 $node2}
    foreach e $outArcs($node2) {lset arcNodes($e) 0 $node1}

    # Swap arc lists

    set tmp            $inArcs($node1)
    set inArcs($node1) $inArcs($node2)
    set inArcs($node2) $tmp

    set tmp             $outArcs($node1)
    set outArcs($node1) $outArcs($node2)
    set outArcs($node2) $tmp

    # Swap the values
    # More complicated now with the possibility that nodes do not have
    # attribute storage associated with them. But also
    # simpler as we just have to swap/move the array
    # reference

    if {
	[set ia [info exists nodeAttr($node1)]] ||
	[set ib [info exists nodeAttr($node2)]]
    } {
	# At least one of the nodes has attribute data. We simply swap
	# the references to the arrays containing them. No need to
	# copy the actual data around.

	if {$ia && $ib} {
	    set tmp               $nodeAttr($node1)
	    set nodeAttr($node1) $nodeAttr($node2)
	    set nodeAttr($node2) $tmp
	} elseif {$ia} {
	    set   nodeAttr($node2) $nodeAttr($node1)
	    unset nodeAttr($node1)
	} elseif {$ib} {
	    set   nodeAttr($node1) $nodeAttr($node2)
	    unset nodeAttr($node2)
	} else {
	    return -code error "Impossible condition."
	}
    } ; # else: No attribute storage => Nothing to do {}

    return
}

# ::struct::graph::_unset --
#
#	Remove a keyed value from the graph itself
#
# Arguments:
#	name	name of the graph.
#	key	attribute to remove
#
# Results:
#	None.

proc ::struct::graph::_unset {name key} {
    variable ${name}::graphAttr
    if {[info exists  graphAttr($key)]} {
	unset graphAttr($key)
    }
    return
}

# ::struct::graph::_walk --
#
#	Walk a graph 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 graph and the node.
#
# Arguments:
#	name	name of the graph.
#	node	node at which to start.
#	args	additional args: ?-order pre|post|both? ?-type {bfs|dfs}?
#		-command cmd
#
# Results:
#	None.

proc ::struct::graph::_walk {name node args} {
    set usage "$name walk node ?-dir forward|backward?\
	    ?-order pre|post|both? ?-type bfs|dfs? -command cmd"

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

    CheckMissingNode $name $node

    # Set defaults
    set type  dfs
    set order pre
    set cmd   ""
    set dir   forward

    # Process specified options
    for {set i 0} {$i < [llength $args]} {incr i} {
	set flag [lindex $args $i]
	switch -glob -- $flag {
	    "-type" {
		incr i
		if { $i >= [llength $args] } {
		    return -code error "value for \"$flag\" missing: should be \"$usage\""
		}
		set type [string tolower [lindex $args $i]]
	    }
	    "-order" {
		incr i
		if { $i >= [llength $args] } {
		    return -code error "value for \"$flag\" missing: should be \"$usage\""
		}
		set order [string tolower [lindex $args $i]]
	    }
	    "-command" {
		incr i
		if { $i >= [llength $args] } {
		    return -code error "value for \"$flag\" missing: should be \"$usage\""
		}
		set cmd [lindex $args $i]
	    }
	    "-dir" {
		incr i
		if { $i >= [llength $args] } {
		    return -code error "value for \"$flag\" missing: should be \"$usage\""
		}
		set dir [string tolower [lindex $args $i]]
	    }
	    default {
		return -code error "unknown option \"$flag\": should be \"$usage\""
	    }
	}
    }
    
    # Make sure we have a command to run, otherwise what's the point?
    if { [string equal $cmd ""] } {
	return -code error "no command specified: should be \"$usage\""
    }

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

    # Validate that the given direction is good
    switch -glob -- $dir {
	"forward" {
	    set dir -out
	}
	"backward" {
	    set dir -in
	}
	default {
	    return -code error "bad search direction \"$dir\": must be\
		    backward or forward"
	}
    }

    # Do the walk

    set st [list ]
    lappend st $node
    array set visited {}

    if { [string equal $type "dfs"] } {
	if { [string equal $order "pre"] } {
	    # Pre-order Depth-first search

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

		# Skip all nodes already visited via some other path
		# through the graph.
		if {[info exists visited($node)]} continue
		
		# Evaluate the command at this node
		set cmdcpy $cmd
		lappend cmdcpy enter $name $node
		uplevel 1 $cmdcpy

		set visited($node) .

		# Add this node's neighbours (according to direction)
		#  Have to add them in reverse order
		#  so that they will be popped left-to-right

		set next [_nodes $name $dir $node]
		set len  [llength $next]

		for {set i [expr {$len - 1}]} {$i >= 0} {incr i -1} {
		    set nextnode [lindex $next $i]
		    if {[info exists visited($nextnode)]} {
			# Skip nodes already visited
			continue
		    }
		    lappend st $nextnode
		}
	    }
	} elseif { [string equal $order "post"] } {
	    # Post-order Depth-first search

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

		if {[info exists visited($node)]} {
		    # Second time we are here, pop it,
		    # then evaluate the command.

		    ldelete st end
		    # Bug 2420330. Note: The visited node may be
		    # multiple times on the stack (neighbour of more
		    # than one node). Remove all occurences.
		    while {[set index [lsearch -exact $st $node]] != -1} {
			set st [lreplace $st $index $index]
		    }

		    # Evaluate the command at this node
		    set cmdcpy $cmd
		    lappend cmdcpy leave $name $node
		    uplevel 1 $cmdcpy
		} else {
		    # First visit. Remember it.
		    set visited($node) .
	    
		    # Add this node's neighbours.
		    set next [_nodes $name $dir $node]
		    set len  [llength $next]

		    for {set i [expr {$len - 1}]} {$i >= 0} {incr i -1} {
			set nextnode [lindex $next $i]
			if {[info exists visited($nextnode)]} {
			    # Skip nodes already visited
			    continue
			}
			lappend st $nextnode
		    }
		}
	    }
	} else {
	    # Both-order Depth-first search

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

		if {[info exists visited($node)]} {
		    # Second time we are here, pop it,
		    # then evaluate the command.

		    ldelete st end

		    # Evaluate the command at this node
		    set cmdcpy $cmd
		    lappend cmdcpy leave $name $node
		    uplevel 1 $cmdcpy
		} else {
		    # First visit. Remember it.
		    set visited($node) .

		    # Evaluate the command at this node
		    set cmdcpy $cmd
		    lappend cmdcpy enter $name $node
		    uplevel 1 $cmdcpy
	    
		    # Add this node's neighbours.
		    set next [_nodes $name $dir $node]
		    set len  [llength $next]

		    for {set i [expr {$len - 1}]} {$i >= 0} {incr i -1} {
			set nextnode [lindex $next $i]
			if {[info exists visited($nextnode)]} {
			    # Skip nodes already visited
			    continue
			}
			lappend st $nextnode
		    }
		}
	    }
	}

    } else {
	if { [string equal $order "pre"] } {
	    # Pre-order Breadth first search
	    while { [llength $st] > 0 } {
		set node [lindex $st 0]
		ldelete st 0
		# Evaluate the command at this node
		set cmdcpy $cmd
		lappend cmdcpy enter $name $node
		uplevel 1 $cmdcpy
	    
		set visited($node) .

		# Add this node's neighbours.
		foreach child [_nodes $name $dir $node] {
		    if {[info exists visited($child)]} {
			# Skip nodes already visited
			continue
		    }
		    lappend st $child
		}
	    }
	} else {
	    # Post-order Breadth first search
	    # Both-order Breadth first search
	    # Haven't found anything in Knuth
	    # and unable to define something
	    # consistent for myself. Leave it
	    # out.

	    return -code error "unable to do a ${order}-order breadth first walk"
	}
    }
    return
}

# ::struct::graph::Union --
#
#	Return a list which is the union of the elements
#	in the specified lists.
#
# Arguments:
#	args	list of lists representing sets.
#
# Results:
#	set	list representing the union of the argument lists.

proc ::struct::graph::Union {args} {
    switch -- [llength $args] {
	0 {
	    return {}
	}
	1 {
	    return [lindex $args 0]
	}
	default {
	    foreach set $args {
		foreach e $set {
		    set tmp($e) .
		}
	    }
	    return [array names tmp]
	}
    }
}

# ::struct::graph::GenAttributeStorage --
#
#	Create an array to store the attributes of a node in.
#
# Arguments:
#	name	Name of the graph containing the node
#	type	Type of object for the attribute
#	obj	Name of the node or arc which got attributes.
#
# Results:
#	none

proc ::struct::graph::GenAttributeStorage {name type obj} {
    variable ${name}::nextAttr
    upvar    ${name}::${type}Attr attribute

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

proc ::struct::graph::CheckMissingArc {name arc} {
    if {![__arc_exists $name $arc]} {
	return -code error "arc \"$arc\" does not exist in graph \"$name\""
    }
}

proc ::struct::graph::CheckMissingNode {name node {prefix {}}} {
    if {![__node_exists $name $node]} {
	return -code error "${prefix}node \"$node\" does not exist in graph \"$name\""
    }
}

proc ::struct::graph::CheckDuplicateArc {name arc} {
    if {[__arc_exists $name $arc]} {
	return -code error "arc \"$arc\" already exists in graph \"$name\""
    }
}

proc ::struct::graph::CheckDuplicateNode {name node} {
    if {[__node_exists $name $node]} {
	return -code error "node \"$node\" already exists in graph \"$name\""
    }
}

proc ::struct::graph::CheckE {name what arguments} {

    # Discriminate between conditions and nodes

    upvar 1 haveCond   haveCond   ; set haveCond   0
    upvar 1 haveKey    haveKey    ; set haveKey    0
    upvar 1 key        key        ; set key        {}
    upvar 1 haveValue  haveValue  ; set haveValue  0
    upvar 1 value      value      ; set value      {}
    upvar 1 haveFilter haveFilter ; set haveFilter 0
    upvar 1 fcmd       fcmd       ; set fcmd       {}
    upvar 1 cond       cond       ; set cond       "none"
    upvar 1 condNodes  condNodes  ; set condNodes  {}

    set wa_usage "wrong # args: should be \"$name $what ?-key key? ?-value value? ?-filter cmd? ?-in|-out|-adj|-inner|-embedding node node...?\""
    set seenodes 0
    
    for {set i 0} {$i < [llength $arguments]} {incr i} {
	set arg [lindex $arguments $i]
	switch -glob -- $arg {
	    -in -
	    -out -
	    -adj -
	    -inner -
	    -embedding {
		if {$haveCond} {
		    return -code error "invalid restriction:\
			    illegal multiple use of\
			    \"-in\"|\"-out\"|\"-adj\"|\"-inner\"|\"-embedding\""
		}

		set haveCond 1
		set cond [string range $arg 1 end]
		set seenodes 1
	    }
	    -key {
		if {($i + 1) == [llength $arguments]} {
		    return -code error $wa_usage
		}
		if {$haveKey} {
		    return -code error {invalid restriction: illegal multiple use of "-key"}
		}

		incr i
		set key [lindex $arguments $i]
		set haveKey 1
		set seenodes 0
	    }
	    -value {
		if {($i + 1) == [llength $arguments]} {
		    return -code error $wa_usage
		}
		if {$haveValue} {
		    return -code error {invalid restriction: illegal multiple use of "-value"}
		}

		incr i
		set value [lindex $arguments $i]
		set haveValue 1
		set seenodes 0
	    }
	    -filter {
		if {($i + 1) == [llength $arguments]} {
		    return -code error $wa_usage
		}
		if {$haveFilter} {
		    return -code error {invalid restriction: illegal multiple use of "-filter"}
		}

		incr i
		set fcmd [lindex $arguments $i]
		set haveFilter 1
		set seenodes 0
	    }
	    -* {
		if {$seenodes} {
		    lappend condNodes $arg
		} else {
		    return -code error "bad restriction \"$arg\": must be -adj, -embedding,\
			-filter, -in, -inner, -key, -out, or -value"
		}
	    }
	    default {
		lappend condNodes $arg
	    }
	}
    }

    # Validate that there are nodes to use in the restriction.
    # otherwise what's the point?
    if {$haveCond} {
	if {[llength $condNodes] == 0} {
	    return -code error $wa_usage
	}

	# Remove duplicates. Note: lsort -unique is not present in Tcl
	# 8.2, thus not usable here.

	array set nx {}
	foreach c $condNodes {set nx($c) .}
	set condNodes [array names nx]
	unset nx

	# Make sure that the specified nodes exist!
	foreach node $condNodes {CheckMissingNode $name $node}
    }

    if {$haveValue && !$haveKey} {
	return -code error {invalid restriction: use of "-value" without "-key"}
    }

    return
}

proc ::struct::graph::CheckSerialization {ser gavar navar aavar inavar outavar arcnvar arcwvar} {
    upvar 1 \
	    $gavar   graphAttr \
	    $navar   nodeAttr  \
	    $aavar   arcAttr   \
	    $inavar  inArcs    \
	    $outavar outArcs   \
	    $arcnvar arcNodes  \
	    $arcwvar arcWeight

    array set nodeAttr  {}
    array set arcAttr   {}
    array set inArcs    {}
    array set outArcs   {}
    array set arcNodes  {}
    array set arcWeight {}

    # Overall length ok ?
    if {[llength $ser] % 3 != 1} {
	return -code error \
		"error in serialization: list length not 1 mod 3."
    }

    # Attribute length ok ? Dictionary!
    set graphAttr [lindex $ser end]
    if {[llength $graphAttr] % 2} {
	return -code error \
		"error in serialization: malformed graph attribute dictionary."
    }

    # Basic decoder pass

    foreach {node attr narcs} [lrange $ser 0 end-1] {
	if {![info exists inArcs($node)]} {
	    set inArcs($node)  [list]
	}
	set outArcs($node) [list]

	# Attribute length ok ? Dictionary!
	if {[llength $attr] % 2} {
	    return -code error \
		    "error in serialization: malformed node attribute dictionary."
	}
	# Remember attribute data only for non-empty nodes
	if {[llength $attr]} {
	    set nodeAttr($node) $attr
	}

	foreach arcd $narcs {
	    if {
		([llength $arcd] != 3) &&
		([llength $arcd] != 4)
	    } {
		return -code error \
			"error in serialization: arc information length not 3 or 4."
	    }

	    foreach {arc dst aattr} $arcd break

	    if {[info exists arcNodes($arc)]} {
		return -code error \
			"error in serialization: duplicate definition of arc \"$arc\"."
	    }

	    # Attribute length ok ? Dictionary!
	    if {[llength $aattr] % 2} {
		return -code error \
			"error in serialization: malformed arc attribute dictionary."
	    }
	    # Remember attribute data only for non-empty nodes
	    if {[llength $aattr]} {
		set arcAttr($arc) $aattr
	    }

	    # Remember weight data if it was specified.
	    if {[llength $arcd] == 4} {
		set arcWeight($arc) [lindex $arcd 3]
	    }

	    # Destination reference ok ?
	    ##nagelfar ignore
	    if {
		![string is integer -strict $dst] ||
		($dst % 3) ||
		($dst < 0) ||
		($dst >= [llength $ser])
	    } {
		return -code error \
			"error in serialization: bad arc destination reference \"$dst\"."
	    }

	    # Get destination and reconstruct the
	    # various relationships.

	    set dstnode [lindex $ser $dst]

	    set arcNodes($arc) [list $node $dstnode]
	    lappend inArcs($dstnode) $arc
	    lappend outArcs($node)   $arc
	}
    }

    # Duplicate node names ?

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

    # 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::graph::K { x y } { set x }

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

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

proc ::struct::graph::luniq {list} {
    array set _ {}
    set result [list]
    foreach e $list {
	if {[info exists _($e)]} {continue}
	lappend result $e
	set _($e) .
    }
    return $result
}

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

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

    namespace import -force graph::graph_tcl
}

