#
# Copyright 2005-2008 University of Zagreb, Croatia.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# This work was supported in part by the Croatian Ministry of Science
# and Technology through the research contract #IP-2003-143.
#

#****h* imunes/cfgparse.tcl
# NAME
#  cfgparse.tcl -- file used for parsing the configuration
# FUNCTION
#  This module is used for parsing the configuration, i.e. reading the
#  configuration from a file or a string and writing the configuration
#  to a file or a string. This module also contains a function for returning
#  a new ID for nodes, links and canvases.
#****

#****f* nodecfg.tcl/dumpputs
# NAME
#   dumpputs -- puts a string to a file or a string configuration
# SYNOPSIS
#   dumpputs $method $destination $string
# FUNCTION
#   Puts a sting to the file or appends the string configuration (used for
#   undo functions), the choice depends on the value of method parameter.
# INPUTS
#   * method -- method used. Possiable values are file (if saving the string
#   to the file) and string (if appending the string configuration)
#   * dest -- destination used. File_id for files, and string name for string
#   configuration
#   * string -- the string that is inserted to a file or appended to the string
#   configuartion
#****

proc dumpputs {method dest string} {
    switch -exact -- $method {
	file {
	    puts $dest $string
	}
	string {
	    global $dest
	    append $dest "$string
"
	}
    }
}

#****f* nodecfg.tcl/dumpCfg
# NAME
#   dumpCfg -- puts the current configuraton to a file or a string
# SYNOPSIS
#   dumpCfg $method $destination
# FUNCTION
#   Writes the working (current) configuration to a file or a string.
# INPUTS
#   * method -- used method. Possiable values are file (saving current congif
#   to the file) and string (saving current config in a string)
#   * dest -- destination used. File_id for files, and string name for string
#   configurations
#****

proc dumpCfg {method dest} {
    global node_list plot_list link_list canvas_list annotation_list

    global g_comments
    if { [info exists g_comments] && $g_comments != "" } {
	dumpputs $method $dest "comments \{"
	foreach line [split $g_comments "\n"] { dumpputs $method $dest "$line" }
	dumpputs $method $dest "\}"
	dumpputs $method $dest ""
    }

    foreach node $node_list {
	global $node
	upvar 0 $node lnode
	dumpputs $method $dest "node $node \{"
	foreach element $lnode {
	    if { "[lindex $element 0]" == "network-config" } {
		dumpputs $method $dest "    network-config \{"
		foreach line [lindex $element 1] {
		    dumpputs $method $dest "	$line"
		}
		dumpputs $method $dest "    \}"
	    } elseif { "[lindex $element 0]" == "custom-config" } {
		dumpputs $method $dest "    custom-config \{"
		foreach line [lindex $element 1] {
		    if { $line != {} } {
			if { [catch {set str [lindex $line 0]} err] } {
				puts "error loading config: $err"
				puts "problem section: [lindex $element 0]"
				puts "problem line: $line"
				set str ""
			}
			if { $str == "config" } {
			    dumpputs $method $dest "	config \{"
			    foreach element [lindex $line 1] {
				dumpputs $method $dest "	$element"
			    }
			    dumpputs $method $dest "	\}"
			} else {
			    dumpputs $method $dest "	$line"
			}
		    }
		}
		dumpputs $method $dest "    \}"
	    } elseif { "[lindex $element 0]" == "ipsec-config" } {
		dumpputs $method $dest "    ipsec-config \{"
		foreach line [lindex $element 1] {
		    if { $line != {} } {
			dumpputs $method $dest "	$line"
		    }
		}
		dumpputs $method $dest "    \}"
	    } elseif { "[lindex $element 0]" == "custom-pre-config-commands" } {
		#Boeing custom pre config commands
		dumpputs $method $dest "    custom-pre-config-commands \{"
		foreach line [lindex $element 1] {
		    dumpputs $method $dest "	$line"
		}
		dumpputs $method $dest "    \}"
	    } elseif { "[lindex $element 0]" == "custom-post-config-commands" } {
		#Boeing custom post config commands
		dumpputs $method $dest "    custom-post-config-commands \{"
		foreach line [lindex $element 1] {
		    dumpputs $method $dest "	$line"
		}
		dumpputs $method $dest "    \}"
	    } elseif { "[lindex $element 0]" == "ine-config" } {
	    # Boeing: INE config support
		dumpputs $method $dest "    ine-config \{"
		foreach line [lindex $element 1] {
		    dumpputs $method $dest "	$line"
		}
		dumpputs $method $dest "    \}"
	    # end Boeing
	    } else {
		dumpputs $method $dest "    $element"
            }
	}
	dumpputs $method $dest "\}"
	dumpputs $method $dest ""
    }

    foreach obj "link annotation canvas plot" {
	upvar 0 ${obj}_list obj_list
	foreach elem $obj_list {
	    global $elem
	    upvar 0 $elem lelem
	    dumpputs $method $dest "$obj $elem \{"
	    foreach element $lelem {
		dumpputs $method $dest "    $element"
	    }
	    dumpputs $method $dest "\}"
	    dumpputs $method $dest ""
	}
    }

    global g_traffic_flows
    if { [info exists g_traffic_flows] && [llength $g_traffic_flows] > 0 } {
	dumpputs $method $dest "traffic \{"
	foreach flow $g_traffic_flows {
	    dumpputs $method $dest "    $flow"
	}
	dumpputs $method $dest "\}"
	dumpputs $method $dest ""
    }

    global g_hook_scripts
    if { [info exists g_hook_scripts] && [llength $g_hook_scripts] > 0 } {
	foreach hook $g_hook_scripts {
	    set name [lindex $hook 0]
	    set state [lindex $hook 1]
	    set script [lindex $hook 2]
	    dumpputs $method $dest "hook $state:$name \{"
	    # remove the final newline here because dumpputs adds a
	    # newline automatically
	    if {[string index $script end] == "\n"} {
		set script [string replace $script end end]
	    }
	    dumpputs $method $dest $script
	    dumpputs $method $dest "\}"
	    dumpputs $method $dest ""
	}
    }

    dumpGlobalOptions $method $dest

    # session options
    dumpputs $method $dest "option session \{"
    foreach kv [getSessionOptionsList] { dumpputs $method $dest "    $kv" }
    dumpputs $method $dest "\}"
    dumpputs $method $dest ""
}

proc dumpGlobalOptions { method dest } {
    global showIfNames showNodeLabels showLinkLabels
    global showIfIPaddrs showIfIPv6addrs
    global showBkgImage showGrid showAnnotations
    global showAPI
    global g_view_locked
    global g_traffic_start_opt
    global mac_addr_start

    dumpputs $method $dest "option global \{"
    if {$showIfNames == 0} {
	dumpputs $method $dest "    interface_names no"
    } else {
	dumpputs $method $dest "    interface_names yes" }
    if {$showIfIPaddrs == 0} {
	dumpputs $method $dest "    ip_addresses no"
    } else {
	dumpputs $method $dest "    ip_addresses yes" }
    if {$showIfIPv6addrs == 0} {
	dumpputs $method $dest "    ipv6_addresses no"
    } else {
	dumpputs $method $dest "    ipv6_addresses yes" }
    if {$showNodeLabels == 0} {
	dumpputs $method $dest "    node_labels no"
    } else {
	dumpputs $method $dest "    node_labels yes" }
    if {$showLinkLabels == 0} {
	dumpputs $method $dest "    link_labels no"
    } else {
	dumpputs $method $dest "    link_labels yes" }
    if {$showAPI == 0} {
	dumpputs $method $dest "    show_api no"
    } else {
	dumpputs $method $dest "    show_api yes" }
    if {$showBkgImage == 0} {
	dumpputs $method $dest "    background_images no"
    } else {
	dumpputs $method $dest "    background_images yes" }
    if {$showAnnotations == 0} {
	dumpputs $method $dest "    annotations no"
    } else {
	dumpputs $method $dest "    annotations yes" }
    if {$showGrid == 0} {
	dumpputs $method $dest "    grid no"
    } else {
	dumpputs $method $dest "    grid yes" }
    if {$g_view_locked == 1} {
	dumpputs $method $dest "    locked yes" }
    if { [info exists g_traffic_start_opt] } {
	dumpputs $method $dest "    traffic_start $g_traffic_start_opt"
    }
    if { [info exists mac_addr_start] && $mac_addr_start > 0 } {
	dumpputs $method $dest "    mac_address_start $mac_addr_start"
    }
    dumpputs $method $dest "\}"
    dumpputs $method $dest ""
}

# get the global options into a list of key=value pairs
proc getGlobalOptionList {} {
    global tmp
    set tmp ""
    dumpGlobalOptions string tmp ;# put "options global {items}" into tmp
    set items [lindex $tmp 2]
    return [listToKeyValues $items]
}

proc setGlobalOption { field value } {
    global showIfNames showNodeLabels showLinkLabels
    global showIfIPaddrs showIfIPv6addrs
    global showBkgImage showGrid showAnnotations
    global showAPI
    global mac_addr_start
    global g_traffic_start_opt
    global g_view_locked

    switch -exact -- $field {
	interface_names {
	    if { $value == "no" } {
		set showIfNames 0
	    } elseif { $value == "yes" } {
		set showIfNames 1
	    }
	}
	ip_addresses {
	    if { $value == "no" } {
		set showIfIPaddrs 0
	    } elseif { $value == "yes" } {
		set showIfIPaddrs 1
	    }
	}
	ipv6_addresses {
	    if { $value == "no" } {
		set showIfIPv6addrs 0
	    } elseif { $value == "yes" } {
		set showIfIPv6addrs 1
	    }
	}
	node_labels {
	    if { $value == "no" } {
		set showNodeLabels 0
	    } elseif { $value == "yes" } {
		set showNodeLabels 1
	    }
	}
	link_labels {
	    if { $value == "no" } {
		set showLinkLabels 0
	    } elseif { $value == "yes" } {
		set showLinkLabels 1
	    }
	}
	show_api {
	    if { $value == "no" } {
		set showAPI 0
	    } elseif { $value == "yes" } {
		set showAPI 1
	    }
	}
	background_images {
	    if { $value == "no" } {
		set showBkgImage 0
	    } elseif { $value == "yes" } {
		set showBkgImage 1
	    }
	}
	annotations {
	    if { $value == "no" } {
		set showAnnotations 0
	    } elseif { $value == "yes" } {
		set showAnnotations 1
	    }
	}
	grid {
	    if { $value == "no" } {
		set showGrid 0
	    } elseif { $value == "yes" } {
		set showGrid 1
	    }
	}
	locked {
	    if { $value == "yes" } {
		set g_view_locked 1
	    } else {
		set g_view_locked 0
	    }
	}
	mac_address_start {
	    set mac_addr_start  $value
	}
	traffic_start {
	    set g_traffic_start_opt $value
	}
    }
}

# reset global vars when opening a new file
proc cleanupGUIState {} {
    global node_list link_list plot_list canvas_list annotation_list
    global mac_addr_start g_comments
    global g_traffic_flows g_traffic_start_opt g_hook_scripts
    global g_view_locked

    set node_list {}
    set link_list {}
    set annotation_list {}
    set plot_list {}
    set canvas_list {}
    set g_traffic_flows ""
    set g_traffic_start_opt 0
    set g_hook_scripts ""
    set g_comments ""
    set g_view_locked 0
    resetSessionOptions
}

#****f* nodecfg.tcl/loadCfg
# NAME
#   loadCfg -- loads the current configuration.
# SYNOPSIS
#   loadCfg $cfg
# FUNCTION
#   Loads the configuration written in the cfg string to a current
#   configuration.
# INPUTS
#   * cfg -- string containing the new working configuration.
#****

proc loadCfg { cfg } {
    global node_list plot_list link_list canvas_list annotation_list
    global g_traffic_flows g_traffic_start_opt g_hook_scripts
    global g_view_locked
    global g_comments

    # maximum coordinates
    set maxX 0
    set maxY 0
    set do_upgrade [upgradeOldConfig cfg]
    if { $do_upgrade == "no"} { return }

    # Cleanup first
    cleanupGUIState
    set class ""
    set object ""
    foreach entry $cfg {
	if {"$class" == ""} {
	    set class $entry
	    continue
	} elseif {"$object" == ""} {
	    set object $entry
	    if {"$class" == "node"} {
		lappend node_list $object
	    } elseif {"$class" == "link"} {
		lappend link_list $object
	    } elseif {"$class" == "canvas"} {
		lappend canvas_list $object
	    } elseif {"$class" == "plot"} {
		lappend plot_list $object
            } elseif {"$class" == "option"} {
		# do nothing
	    } elseif {"$class" == "traffic"} { ;# save traffic flows
		set g_traffic_flows [split [string trim $object] "\n"]
		set class ""; set object ""; continue
	    } elseif {"$class" == "script"} {
		# global_script (old config) becomes a runtime hook
		set name "runtime_hook.sh"
		set script [string trim $object]
		lappend g_hook_scripts [list $name 4 $script] ;# 4=RUNTIME_STATE
		set class ""; set object ""; continue
	    } elseif {"$class" == "hook"} {
		continue
	    } elseif {"$class" == "comments"} {
		set g_comments [string trim $object]
		set class ""; set object ""; continue
	    } elseif {"$class" == "annotation"} {
		lappend annotation_list $object
	    } else {
		puts "configuration parsing error: unknown object class $class"
		#exit 1
	    }
	    # create an empty global variable named object for most objects
	    global $object
	    set $object {}
	    continue
	} else {
	    set line [concat $entry]
	    # uses 'key=value' instead of 'key value'
	    if  { $object == "session" } {
		# 'key=value', values with space needs quoting 'key={space val}'
		setSessionOptions "" [split $line "\n"]
		set class ""
		set object ""
		continue
	    }
	    # extracts "field { value }"  elements from line
	    if { [catch { set tmp [llength $line] } e] } {
		puts "*** Error with line ('$e'):\n$line"
		puts "*** Line will be skipped. This is a Tcl limitation, "
		puts "*** consider using XML or fixing with whitespace."
		continue
	    }
	    while {[llength $line] >= 2} {
		set field [lindex $line 0]
		if {"$field" == ""} {
		    set line [lreplace $line 0 0]
		    continue
		}

		# consume first two list elements from line
		set value [lindex $line 1]
		set line [lreplace $line 0 1]

		if {"$class" == "node"} {
		    switch -exact -- $field {
			type {
			    lappend $object "type $value"
			}
			mirror {
			    lappend $object "mirror $value"
			}
			model {
			    lappend $object "model $value"
			}
			cpu {
			    lappend $object "cpu {$value}"
			}
			interface-peer {
			    lappend $object "interface-peer {$value}"
			}
			network-config {
			    set cfg ""
			    foreach zline [split $value {
}] {
				if { [string index "$zline" 0] == "	" } {
				    set zline [string replace "$zline" 0 0]
				}
				lappend cfg $zline
			    }
			    set cfg [lrange $cfg 1 [expr {[llength $cfg] - 2}]]
			    lappend $object "network-config {$cfg}"
			}
			custom-enabled {
			    lappend $object "custom-enabled $value"
			}
			custom-command {
			    lappend $object "custom-command {$value}"
			}
			custom-config {
			    set cfg ""
			    set have_config 0
			    set ccfg {}
			    foreach zline [split $value "\n"] {
				if { [string index "$zline" 0] == \
						"	" } {
				    # remove leading tab character
				    set zline [string replace "$zline" 0 0]
				}

				# flag for config lines
				if { $zline == "config \{" } {
				    set have_config 1
				# collect custom config lines into list
				} elseif { $have_config == 1 } {
				    lappend ccfg $zline
				# add non-config lines
				} else {
				    lappend cfg $zline
				}
  			    }
			    # chop off last brace in config { } block and add it
			    if { $have_config } {
				set ccfg [lrange $ccfg 0 \
					 [expr {[llength $ccfg] - 3}]]
				lappend cfg [list config $ccfg]
			    }
			    #set cfg [lrange $cfg 1 [expr {[llength $cfg] - 2}]]
			    lappend $object "custom-config {$cfg}"
			}
			ipsec-enabled {
			    lappend $object "ipsec-enabled $value"
			}
			ipsec-config {
			    set cfg ""

			    foreach zline [split $value {
}] {
				if { [string index "$zline" 0] == "	" } {
				    set zline [string replace "$zline" 0 0]
				}
				lappend cfg $zline
			    }
			    set cfg [lrange $cfg 1 [expr {[llength $cfg] - 2}]]
				lappend $object "ipsec-config {$cfg}"
			}
			iconcoords {
			    checkMaxCoords $value maxX maxY
			    lappend $object "iconcoords {$value}"
			}
			labelcoords {
			    checkMaxCoords $value maxX maxY
			    lappend $object "labelcoords {$value}"
			}
			canvas {
			    lappend $object "canvas $value"
			}
			hidden {
			    lappend $object "hidden $value"
			}
			/* {
			    set comment "$field $value"
			    foreach c $line {
				lappend comment $c
				# consume one element from line
				set line [lreplace $line 0 0]
				if { $c == "*/" } { break }
			    }
			    lappend $object "$comment"
			}

			custom-pre-config-commands {
			    # Boeing - custom pre config commands
			    set cfg ""
			    foreach zline [split $value {
}] {
				if { [string index "$zline" 0] == "	" } {
				    set zline [string replace "$zline" 0 0]
				}
				lappend cfg $zline
			    }
			    set cfg [lrange $cfg 1 [expr [llength $cfg] - 2]]
			    lappend $object "custom-pre-config-commands {$cfg}"
		        }
			custom-post-config-commands {
			    # Boeing - custom post config commands
			    set cfg ""
			    foreach zline [split $value {
}] {
				if { [string index "$zline" 0] == "	" } {
				    set zline [string replace "$zline" 0 0]
				}
				lappend cfg $zline
			    }
			    set cfg [lrange $cfg 1 [expr [llength $cfg] - 2]]
			    lappend $object "custom-post-config-commands {$cfg}"
		        }
			custom-image {
			    # Boeing - custom-image
			    lappend $object "custom-image $value"
		        }
			ine-config {
			    # Boeing - INE
			    set cfg ""
			    foreach zline [split $value {
}] {
				if { [string index "$zline" 0] == "	" } {
				    set zline [string replace "$zline" 0 0]
				}
				lappend cfg $zline
			    }
			    set cfg [lrange $cfg 1 [expr [llength $cfg] - 2]]
			    lappend $object "ine-config {$cfg}"
			}
			tunnel-peer {
			    # Boeing - Span tunnels
			    lappend $object "tunnel-peer {$value}"
		        }
			range {
			    # Boeing - WLAN range
			    lappend $object "range $value"
			}
			bandwidth {
			    # Boeing - WLAN bandwidth
			    lappend $object "bandwidth $value"
			}
			cli-enabled {
			    puts "Warning: cli-enabled setting is deprecated"
			}
			delay {
			    # Boeing - WLAN delay
			    lappend $object "delay $value"
			}
			ber {
			    # Boeing - WLAN BER
			    lappend $object "ber $value"
			}
			location {
			    # Boeing - node location
			    lappend $object "location $value"
			}
			os {
			    # Boeing - node OS
			    # just ignore it, set at runtime
			}
			services {
			    lappend $object "services {$value}"
			}

			default {
			    # Boeing - added warning
			    puts -nonewline "config file warning: unknown confi"
			    puts "guration item '$field' ignored for $object"
			}
		    }
		} elseif {"$class" == "plot"} {
		    switch -exact -- $field {
		  	name {
			    lappend $object "name $value"
			}
			height {
			    lappend $object "height $value"
			}
 			width {
			    lappend $object "width $value"
			}
			x {
			    lappend $object "x $value"
			}
			y {
			    lappend $object "y $value"
			}
 			color {
			    lappend $object "color $value"
			}
 		    }
                } elseif {"$class" == "link"} {
		    switch -exact -- $field {
			nodes {
			    lappend $object "nodes {$value}"
			}
			mirror {
			    lappend $object "mirror $value"
			}
			bandwidth -
			delay -
			ber -
			duplicate -
			jitter {
			    if { [llength $value] > 1 } { ;# down/up-stream
				lappend $object "$field {$value}"
			    } else {
				lappend $object "$field $value"
			    }
			}
			color {
			    lappend $object "color $value"
			}
			width {
			    lappend $object "width $value"
			}
			default {
			    # this enables opaque data to be stored along with
			    # each link (any key is stored)
			    lappend $object "$field $value"
			    # Boeing - added warning
			    #puts -nonewline "config file warning: unknown conf"
			    #puts "iguration item '$field' ignored for $object"
			}
		    }
		} elseif {"$class" == "canvas"} {
		    switch -exact -- $field {
			name {
			    lappend $object "name {$value}"
			}
			size {
			    lappend $object "size {$value}"
			}
			bkgImage {
			    lappend $object "wallpaper {$value}"
			}
			wallpaper {
			    lappend $object "wallpaper {$value}"
			}
			wallpaper-style {
			    lappend $object "wallpaper-style {$value}"
			}
			scale {
			    lappend $object "scale {$value}"
			}
			refpt {
			    lappend $object "refpt {$value}"
			}
		    }
		} elseif {"$class" == "option"} {
		    setGlobalOption $field $value
		} elseif {"$class" == "annotation"} {
		    switch -exact -- $field {
			type {
			    lappend $object "type $value"
			}
			iconcoords {
			    lappend $object "iconcoords {$value}"
			}
			color {
			    lappend $object "color $value"
			}
			border {
			    lappend $object "border $value"
			}
			label {
			    lappend $object "label {$value}"
			}
			labelcolor {
			    lappend $object "labelcolor $value"
			}
			size {
			    lappend $object "size $value"
			}
			canvas {
			    lappend $object "canvas $value"
			}
			font {
			    lappend $object "font {$value}"
			}
			fontfamily {
			    lappend $object "fontfamily {$value}"
			}
			fontsize {
			    lappend $object "fontsize {$value}"
			}
			effects {
			    lappend $object "effects {$value}"
			}
			width {
			    lappend $object "width $value"
			}
			rad {
			    lappend $object "rad $value"
			}
		    } ;# end switch
		} elseif {"$class" == "hook"} {
		    set state_name [split $object :]
		    if { [llength $state_name] != 2 } {
			puts "invalid hook in config file"
			continue
		    }
		    set state [lindex $state_name 0]
		    set name [lindex $state_name 1]
		    set lines [split $entry "\n"]
		    set lines [lreplace $lines 0 0] ;# chop extra newline
		    set lines [join $lines "\n"]
		    set hook [list $name $state $lines]
		    lappend g_hook_scripts $hook
		    set line "" ;# exit this while loop
		} ;#endif class
	    }
	}
	set class ""
	set object ""
    }

    #
    # Hack for comaptibility with old format files (no canvases)
    #
    if { $canvas_list == "" } {
	set curcanvas [newCanvas ""]
	foreach node $node_list {
	    setNodeCanvas $node $curcanvas
	}
    }


    # auto resize canvas
    set curcanvas [lindex $canvas_list 0]
    set newX 0
    set newY 0
    if { $maxX > [lindex [getCanvasSize $curcanvas] 0] } {
	set newX [expr {$maxX + 50}]
    }
    if { $maxY > [lindex [getCanvasSize $curcanvas] 1] } {
	set newY [expr {$maxY + 50}]
    }
    if { $newX > 0 || $newY > 0 } {
    	if { $newX == 0 } { set newX [lindex [getCanvasSize $curcanvas] 0] }
    	if { $newY == 0 } { set newY [lindex [getCanvasSize $curcanvas] 1] }
	setCanvasSize $curcanvas $newX $newY
    }

    # extra upgrade steps
    if { $do_upgrade == "yes" } {
	upgradeNetworkConfigToServices
    }
    upgradeConfigRemoveNode0
    upgradeConfigServices
    upgradeWlanConfigs
}

#****f* nodecfg.tcl/newObjectId
# NAME
#   newObjectId -- new object Id
# SYNOPSIS
#   set obj_id [newObjectId $type]
# FUNCTION
#   Returns the Id for a new object of the defined type. Supported types
#   are node, link and canvas. The Id is in the form $mark$number. $mark is the
#   first letter of the given type and $number is the first available number to
#   that can be used for id.
# INPUTS
#   * type -- the type of the new object. Can be node, link or canvas.
# RESULT
#   * obj_id -- object Id in the form $mark$number. $mark is the
#   first letter of the given type and $number is the first available number to
#   that can be used for id.
#****

proc newObjectId { type } {
    global node_list link_list annotation_list canvas_list

    set mark [string range [set type] 0 0]
    set id 1 ;# start numbering at 1, not 0
    while {[lsearch [set [set type]_list] "$mark$id"]  != -1} {
	incr id
    }
    return $mark$id
}



# Boeing: pick a new link id for temporary newlinks
proc newlinkId { } {
    global link_list
    set id [newObjectId link]
    set mark "l"
    set id 0

    # alllinks contains a list of all existing and new links
    set alllinks $link_list
    foreach newlink [.c find withtag "newlink"] {
    	set newlinkname [lindex [.c gettags $newlink] 1]
    	lappend alllinks $newlinkname
    }

    while {[lsearch $alllinks "$mark$id"] != -1 } {
	incr id
    }
    return $mark$id
}

# Boeing: helper fn to determine canvas size during load
proc checkMaxCoords { str maxXp maxYp } {
    upvar 1 $maxXp maxX
    upvar 1 $maxYp maxY
    set x [lindex $str 0]
    set y [lindex $str 1]
    if { $x > $maxX } {
	set maxX $x
    }
    if { $y > $maxY } {
	set maxY $y
    }
    if { [llength $str] == 4 } {
	set x [lindex $str 2]
	set y [lindex $str 3]
	if { $x > $maxX } {
	    set maxX $x
	}
	if { $y > $maxY } {
	    set maxY $y
	}
    }
}

# Boeing: pick a router for OSPF
proc newRouterId { type node } {
    set mark [string range [set type] 0 0]
    for { set id 0 } { $node != "$mark$id" } { incr id } {
    }
    return "0.0.0.${id}"
}
# end Boeing

# Boeing: load servers.conf file into exec_servers array
proc loadServersConf { } {
    global CONFDIR exec_servers DEFAULT_API_PORT
    set confname "$CONFDIR/servers.conf"
    if { [catch { set f [open "$confname" r] } ] } {
	puts "Creating a default $confname"
	if { [catch { set f [open "$confname" w+] } ] } {
	    puts "***Warning: could not create a default $confname file."
	    return
	}
	puts $f "core1 192.168.0.2 $DEFAULT_API_PORT"
	puts $f "core2 192.168.0.3 $DEFAULT_API_PORT"
	close $f
    	if { [catch { set f [open "$confname" r] } ] } {
	    return
	}
    }

    array unset exec_servers

    while { [ gets $f line ] >= 0 } {
	if { [string range $line 0 0] == "#" } { continue } ;# skip comments
	set l [split $line] ;# parse fields separated by whitespace
	set name [lindex $l 0]
	set ip   [lindex $l 1]
	set port [lindex $l 2]
	set sock -1
	if { $name == "" } { continue } ;# blank name
	# load array of servers
	array set exec_servers [list $name [list $ip $port $sock]]
    }
    close $f
}
# end Boeing

# Boeing: write servers.conf file from exec_servers array
proc writeServersConf { } {
    global CONFDIR exec_servers
    set confname "$CONFDIR/servers.conf"
    if { [catch { set f [open "$confname" w] } ] } {
	puts "***Warning: could not write servers file: $confname"
	return
    }

    set header "# servers.conf: list of CORE emulation servers for running"
    set header "$header remotely."
    puts $f $header
    foreach server [lsort -dictionary [array names exec_servers]] {
	set ip   [lindex $exec_servers($server) 0]
	set port [lindex $exec_servers($server) 1]
	puts $f "$server $ip $port"
    }
    close $f
}
# end Boeing

# display the preferences dialog
proc popupPrefs {} {
    global EDITORS TERMS

    set wi .core_prefs
    catch { destroy $wi }
    toplevel $wi

    wm transient $wi .
    wm resizable $wi 0 0
    wm title $wi "Preferences"

    global g_prefs g_prefs_old
    array set g_prefs_old [array get g_prefs]

    #
    # Paths
    #
    labelframe $wi.dirs -borderwidth 4 -text "Paths" -relief raised
    frame $wi.dirs.conf
    label $wi.dirs.conf.label -text "Default configuration file path:"
    entry $wi.dirs.conf.entry -bg white -width 40 \
	-textvariable g_prefs(default_conf_path)
    pack $wi.dirs.conf.label $wi.dirs.conf.entry -side left
    pack $wi.dirs.conf -side top -anchor w -padx 4 -pady 4

    frame $wi.dirs.mru
    label $wi.dirs.mru.label -text "Number of recent files to remember:"
    entry $wi.dirs.mru.num -bg white -width 3 \
	-textvariable g_prefs(num_recent)
    button $wi.dirs.mru.clear -text "Clear recent files" \
	-command "addFileToMrulist \"\""
    pack $wi.dirs.mru.label $wi.dirs.mru.num $wi.dirs.mru.clear -side left
    pack $wi.dirs.mru -side top -anchor w -padx 4 -pady 4

    pack $wi.dirs -side top -fill x

    #
    # Window
    #
    labelframe $wi.win -borderwidth 4 -text "GUI Window" -relief raised
    frame $wi.win.win
    checkbutton $wi.win.win.savepos -text "remember window position" \
	-variable g_prefs(gui_save_pos)
    checkbutton $wi.win.win.savesiz -text "remember window size" \
	-variable g_prefs(gui_save_size)
    pack $wi.win.win.savepos $wi.win.win.savesiz -side left -anchor w -padx 4
    pack $wi.win.win -side top -anchor w -padx 4 -pady 4

    frame $wi.win.a
    checkbutton $wi.win.a.snaptogrid -text "snap to grid" \
	-variable g_prefs(gui_snap_grid)
    checkbutton $wi.win.a.showtooltips -text "show tooltips" \
	-variable g_prefs(gui_show_tooltips)
    pack $wi.win.a.snaptogrid $wi.win.a.showtooltips \
	-side left -anchor w -padx 4
    pack $wi.win.a -side top -anchor w -padx 4 -pady 4

    frame $wi.win.canv
    label $wi.win.canv.label -text "Default canvas size:"
    entry $wi.win.canv.x -bg white -width 5 -textvariable g_prefs(gui_canvas_x)
    entry $wi.win.canv.y -bg white -width 5 -textvariable g_prefs(gui_canvas_y)
    label $wi.win.canv.label2 -text "Default # of canvases:"
    entry $wi.win.canv.num -bg white -width 5 \
	-textvariable g_prefs(gui_num_canvases)
    pack $wi.win.canv.label $wi.win.canv.x $wi.win.canv.y \
	$wi.win.canv.label2 $wi.win.canv.num \
	-side left -anchor w -padx 4
    pack $wi.win.canv -side top -anchor w -padx 4 -pady 4
    pack $wi.win -side top -fill x

    #
    # Programs
    #
    labelframe $wi.pr -borderwidth 4 -text "Programs" -relief raised

    frame $wi.pr.editor
    label $wi.pr.editor.label -text "Text editor:"
    set editors [linsert $EDITORS 0 "EDITOR"]
    ttk::combobox $wi.pr.editor.combo -width 10 -exportselection 0 \
    	-values $editors -textvariable g_prefs(gui_text_editor)
    label $wi.pr.editor.label2 -text "Terminal program:"
    set terms [linsert $TERMS 0 "TERM"]
    ttk::combobox $wi.pr.editor.combo2 -width 20 -exportselection 0 \
    	-values $terms -textvariable g_prefs(gui_term_prog)
    pack $wi.pr.editor.label $wi.pr.editor.combo -padx 4 -pady 4 -side left
    pack $wi.pr.editor.label2 $wi.pr.editor.combo2 -padx 4 -pady 4 -side left
    pack $wi.pr.editor -side top -anchor w -padx 4 -pady 4

    frame $wi.pr.3d
    label $wi.pr.3d.label -text "3D GUI command:"
    entry $wi.pr.3d.entry -bg white -width 40 -textvariable g_prefs(gui_3d_path)
    pack $wi.pr.3d.label $wi.pr.3d.entry -side left -padx 4 -pady 4
    pack $wi.pr.3d -side top -anchor w -padx 4 -pady 4

    pack $wi.pr -side top -fill x

    #
    # Buttons at the bottom
    #
    frame $wi.bot -borderwidth 0
    button $wi.bot.apply -text "Save" -command "savePrefsFile; destroy $wi"
    button $wi.bot.defaults -text "Load defaults" -command initDefaultPrefs
    button $wi.bot.cancel -text "Cancel" -command {
	global g_prefs g_prefs_old
	array set g_prefs [array get g_prefs_old]
	destroy .core_prefs
    }
    pack $wi.bot.cancel $wi.bot.defaults $wi.bot.apply -side right
    pack $wi.bot -side bottom -fill x
    after 100 {
    	catch { grab .core_prefs }
    }
}

# initialize preferences array with default values
proc initDefaultPrefs {} {
    global g_prefs CONFDIR SBINDIR DEFAULT_REFPT tcl_platform

    # variable expansions must be done here
    array set g_prefs [list default_conf_path	"$CONFDIR/configs"]
    array set g_prefs [list gui_canvas_refpt	"$DEFAULT_REFPT"]
    set shell "bash"
    array set g_prefs [list shell $shell]
    array set g_prefs [list gui_text_editor	[get_text_editor true]]
    array set g_prefs [list gui_term_prog	[get_term_prog true]]
    setDefaultAddrs ipv4
    setDefaultAddrs ipv6
    # preferences will be reordered alphabetically
    array set g_prefs {
	num_recent		4
	log_path		"/tmp/core_logs"
	gui_save_pos		0
	gui_save_size		0
	gui_snap_grid		0
	gui_show_tooltips	1
	gui_canvas_x		1000
	gui_canvas_y		750
	gui_canvas_scale	150.0
	gui_num_canvases	1
	gui_3d_path		"/usr/local/bin/sdt3d.sh"
    }
    # add new preferences above; keep this at the end of the file
}