#
#  Copyright (c) 1997 by the University of Southern California
#  All rights reserved.
#
#  Permission to use, copy, modify, and distribute this software and its
#  documentation in source and binary forms for non-commercial purposes
#  and without fee is hereby granted, provided that the above copyright
#  notice appear in all copies and that both the copyright notice and
#  this permission notice appear in supporting documentation. and that
#  any documentation, advertising materials, and other materials related
#  to such distribution and use acknowledge that the software was
#  developed by the University of Southern California, Information
#  Sciences Institute.  The name of the University may not be used to
#  endorse or promote products derived from this software without
#  specific prior written permission.
#
#  THE UNIVERSITY OF SOUTHERN CALIFORNIA makes no representations about
#  the suitability of this software for any purpose.  THIS SOFTWARE IS
#  PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
#  INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
#  Other copyrights might apply to parts of this software and are so
#  noted when applicable.
#

#
# Maintainer: <kannan@isi.edu>.
#

# The API for this code is still somewhat fluid and subject to change.
# Check the documentation for details.

Class rtQueue

Simulator instproc rtmodel { dist parms args } {
	set ret ""
	if { [rtModel info subclass rtModel/$dist] != "" } {
		$self instvar  rtModel_
		set ret [eval new rtModel/$dist $self]
		eval $ret set-elements $args
		eval $ret set-parms $parms
		set trace [$self get-ns-traceall]
		if {$trace != ""} {
			$ret trace $self $trace
		}
		set trace [$self get-nam-traceall]
		if {$trace != ""} {
			$ret trace $self $trace "nam"
		}
		if [info exists rtModel_] {
			lappend rtModel_ $ret
		} else {
			set rtModel_ $ret
		}
	}
	return $ret
}

Simulator instproc rtmodel-configure {} {
    $self instvar rtq_ rtModel_
    if [info exists rtModel_] {
	set rtq_ [new rtQueue $self]
	foreach m $rtModel_ {
	    $m configure
	}
    }
}

Simulator instproc rtmodel-at {at op args} {
    set parms [list $op $at]
    eval $self rtmodel Manual [list $parms] $args
}

Simulator instproc rtmodel-delete model {
    $self instvar rtModel_
    set idx [lsearch -exact $rtModel_ $model]
    if { $idx != -1 } {
	delete $model
	set rtModel_ [lreplace $rtModel_ $idx $idx]
    }
}

#
rtQueue instproc init ns {
    $self next
    $self instvar ns_
    set ns_ $ns
}

rtQueue instproc insq-i { interval obj iproc args } {
    $self instvar rtq_ ns_
    set time [expr $interval + [$ns_ now]]
    if ![info exists rtq_($time)] {
	$ns_ at $time "$self runq $time"
    }
    lappend rtq_($time) "$obj $iproc $args"
    return $time
}

rtQueue instproc insq { at obj iproc args } {
    $self instvar rtq_ ns_
    if {[$ns_ now] >= $at} {
	puts stderr "$proc: Cannot set event in the past"
	set at ""
    } else {
	if ![info exists rtq_($at)] {
	    $ns_ at $at "$self runq $at"
	}
	lappend rtq_($at) "$obj $iproc $args"
    }
    return $at
}

rtQueue instproc delq { time obj } {
    $self instvar rtq_
    set ret ""
    set nevent ""
    if [info exists rtq_($time)] {
	foreach event $rtq_($time) {
	    if {[lindex $event 0] != $obj} {
		lappend nevent $event
	    } else {
		set ret $event
	    }
	}
	set rtq_($time) $nevent		;# XXX
    }
    return ret
}

rtQueue instproc runq { time } {
    $self instvar rtq_
    set objects ""
    foreach event $rtq_($time) {
	set obj   [lindex $event 0]
	set iproc [lindex $event 1]
	set args  [lrange $event 2 end]
	eval $obj $iproc $args
	lappend objects $obj
    }
    foreach obj $objects {
	$obj notify
    }
    unset rtq_($time)
}

#
Class rtModel

rtModel set rtq_ ""

rtModel instproc init ns {
    $self next
    $self instvar ns_ startTime_ finishTime_
    set ns_ $ns
    set startTime_ [$class set startTime_]
    set finishTime_ [$class set finishTime_]
}

rtModel instproc set-elements args {
    $self instvar ns_ links_ nodes_
    if { [llength $args] == 2 } {
	set n0 [lindex $args 0]
	set n1 [lindex $args 1]
	set n0id [$n0 id]
	set n1id [$n1 id]
	
	set nodes_($n0id) $n0
	set nodes_($n1id) $n1
	set links_($n0id:$n1id) [$ns_ link $n0 $n1]
	set links_($n1id:$n0id) [$ns_ link $n1 $n0]
    } else {
	set n0 [lindex $args 0]
	set n0id [$n0 id]
	set nodes_($n0id) $n0
	foreach nbr [$n0 set neighbor_] {
	    set n1 $nbr
	    set n1id [$n1 id]
	    
	    set nodes_($n1id) $n1
	    set links_($n0id:$n1id) [$ns_ link $n0 $n1]
	    set links_($n1id:$n0id) [$ns_ link $n1 $n0]
	}
    }
}

rtModel instproc set-parms args {
    $self instvar startTime_ upInterval_ downInterval_ finishTime_

    set cls [$self info class]
    foreach i {startTime_ upInterval_ downInterval_ finishTime_} {
	if [catch "$cls set $i" $i] {
	    set $i [$class set $i]
	}
    }

    set off "-"
    set up  "-"
    set dn  "-"
    set fin "-"

    switch [llength $args] {
	4 {
	    set off [lindex $args 0]
	    set up  [lindex $args 1]
	    set dn  [lindex $args 2]
	    set fin [lindex $args 3]
	}
	3 {
	    set off [lindex $args 0]
	    set up  [lindex $args 1]
	    set dn  [lindex $args 2]
	}
	2 {
	    set up [lindex $args 0]
	    set dn [lindex $args 1]
	}
    }
    if {$off != "-" && $off != ""} {
	set startTime_ $off
    }
    if {$up != "-" && $up != ""} {
	set upInterval_ $up
    }
    if {$dn != "-" && $dn != ""} {
	set downInterval_ $dn
    }
    if {$fin != "-" && $fin != ""} {
	set finishTime_ $fin
    }
}

rtModel instproc configure {} {
    $self instvar ns_ links_
    if { [rtModel set rtq_] == "" } {
	rtModel set rtq_ [$ns_ set rtq_]
    }

    foreach l [array names links_] {
	$links_($l) dynamic
    }
    $self set-first-event
}

rtModel instproc set-event-exact {fireTime op} {
    $self instvar ns_ finishTime_
    if {$finishTime_ != "-" && $fireTime > $finishTime_} {
	if {$op == "up"} {
	    [rtModel set rtq_] insq $finishTime_ $self $op
	}
    } else {
	[rtModel set rtq_] insq $fireTime $self $op
    }
}

rtModel instproc set-event {interval op} {
    $self instvar ns_
    $self set-event-exact [expr [$ns_ now] + $interval] $op
}

rtModel instproc set-first-event {} {
    $self instvar startTime_ upInterval_
    $self set-event [expr $startTime_ + $upInterval_] down
}

rtModel instproc up {} {
    $self instvar links_
    foreach l [array names links_] {
	$links_($l) up
    }
}

rtModel instproc down {} {
    $self instvar links_
    foreach l [array names links_] {
	$links_($l) down
    }
}

rtModel instproc notify {} {
    $self instvar nodes_ ns_
    foreach n [array names nodes_] {
	$nodes_($n) intf-changed
    }
    [$ns_ get-routelogic] notify
}

rtModel instproc trace { ns f {op ""} } {
    $self instvar links_
    foreach l [array names links_] {
	$links_($l) trace-dynamics $ns $f $op
    }
}

#
# Exponential link failure/recovery models
#

Class rtModel/Exponential -superclass rtModel

rtModel/Exponential instproc set-first-event {} {
	global rtglibRNG

	$self instvar startTime_ upInterval_
	$self set-event [expr $startTime_ + [$rtglibRNG exponential] * $upInterval_] down
}

rtModel/Exponential instproc up { } {
	global rtglibRNG

	$self next
	$self instvar upInterval_
	$self set-event [expr [$rtglibRNG exponential] * $upInterval_] down
}

rtModel/Exponential instproc down { } {
	global rtglibRNG

	$self next
	$self instvar downInterval_
	$self set-event [expr [$rtglibRNG exponential] * $downInterval_] up
}

#
# Deterministic link failure/recovery models
#

Class rtModel/Deterministic -superclass rtModel

rtModel/Deterministic instproc up { } {
	$self next
	$self instvar upInterval_
	$self set-event $upInterval_ down
}

rtModel/Deterministic instproc down { } {
	$self next
	$self instvar downInterval_
	$self set-event $downInterval_ up
}

#
# Route Dynamics instantiated through a trace file.
# Invoked through:
#
#    $ns_ rtmodel Trace $traceFile $node1 [$node2 ... ]
#

Class rtModel/Trace -superclass rtModel

rtModel/Trace instproc get-next-event {} {
    $self instvar tracef_ links_
    while {[gets $tracef_ event] >= 0} {
	set toks [split $event]
	if [info exists links_([lindex $toks 3]:[lindex $toks 4])] {
	    return $toks
	}
    }
    return ""
}

rtModel/Trace instproc set-trace-events {} {
    $self instvar ns_ nextEvent_ evq_
    
    set time [lindex $nextEvent_ 1]
    while {$nextEvent_ != ""} {
	set nextTime [lindex $nextEvent_ 1]
	if {$nextTime < $time} {
	    puts stderr "event $nextEvent_  is before current time $time. ignored."
	    continue
	}
	if {$nextTime > $time} break
	if ![info exists evq_($time)] {
	    set op [string range [lindex $nextEvent_ 2] 5 end]
	    $self set-event-exact $time $op
	    set evq_($time) 1
	}
	set nextEvent_ [$self get-next-event]
    }
}


rtModel/Trace instproc set-parms traceFile {
    $self instvar tracef_ nextEvent_
    if [catch "open $traceFile r" tracef_] {
	puts stderr "cannot open $traceFile"
    } else {
	set nextEvent_ [$self get-next-event]
	if {$nextEvent_ == ""} {
	    puts stderr "no relevant events in $traceFile"
	}
    }
}

rtModel/Trace instproc set-first-event {} {
    $self set-trace-events
}

rtModel/Trace instproc up {} {
    $self next
    $self set-trace-events
}

rtModel/Trace instproc down {} {
    $self next
    $self set-trace-events
}

#
# One-shot route dynamics events
# Invoked through:
#
#	$ns_ link-op $op $at $node1 [$node2 ...]
# or
#	$ns_ rtmodel Manual {$op $at} $node1 [$node2 ...]
#
Class rtModel/Manual -superclass rtModel

rtModel/Manual instproc set-first-event {} {
    $self instvar op_ at_
    $self set-event-exact $at_ $op_ ;# you could concievably set a finishTime_?
}

rtModel/Manual instproc set-parms {op at} {
    $self instvar op_ at_
    set op_ $op
    set at_ $at
}

rtModel/Manual instproc notify {} {
    $self next
    delete $self		;# XXX wierd code alert.
	# If needed, this could be commented out, on the assumption that
	# manual settings will be very limited, and hence not a sufficient
	# drag on memory resources.  For now, play it safe (or is it risky?)
}
