#!/usr/bin/wishx
# Copyright (c) 1994, 1995, 1996 New York University
# File cgui
# "@(#) $Id: cgui,v 1.4 1997/03/05 04:45:53 baratloo Exp $"


# Global variables...
# state == 0 when nothing has started yet
# state == 1 when the user clicks on Go but
#            the execution has not started yet.
# state == 2 when a calypso program is running
set state -1
set progName ""
set addArgs ""
set displayName $env(DISPLAY)
if { $displayName == ":0.0" } {
    set displayName $env(HOSTDISPLAY)
}
set hostName [exec hostname]
set mgrName [exec hostname]
set home $env(HOME)
set path $env(PATH)
set portNumber 0
set guiPortNumber 0
set runXterm 1
set withTimer 1
set localWorker 1
set bunchJobs 1
set onlineUpdate 1
set smartSchedule 1
set logProfile 0
set progId 0
set rexecPid 0
set monitorPid 0

set NO_BUNCH "-nbunch"
set NO_UPDATE "-nupdate"
set NO_SSCHEDULE "-nsschedule"
set REPORT "-report"
set NO_LOCALWORKER "-nlw"
set LOG "-log"


# -------------------------------------------------------------------------- #
# -------------------------- signal handler routines ----------------------- #

proc sigSIGCHLDhandler sig {
    global rexecPid monitorPid state progId
    catch {
	if {$rexecPid != 0} {
	    if {[wait -nohang $rexecPid] != {}} {
		set rexecPid 0
		if {$state == 2} {
		    .fgoodies.brexe config -state normal
		}
	    }
	}
    }
    catch {
	if {$monitorPid != 0} {
	    if {[wait -nohang $monitorPid] != {}} {
		set monitorPid 0
		if {$state == 2} {
		    .fgoodies.bmonitor config -state normal
		}
	    }
	}
    }
    if {$state == 2} {
	catch {
	    if {[wait -nohang $progId] != {}} {
		alterState 0
		set progId 0
		if {$rexecPid != 0} {
		    killProcess $rexecPid
		}
		if {$monitorPid != 0} {
		    killProcess $monitorPid
		}    
	    }
	}
    }

    # This is very stupid but a non-posix systems require it. -ab.
    signal trap SIGCHLD {sigSIGCHLDhandler %S}
}



# ------------------------------------------------------------------------- #
# -------------------------- clock related routines ----------------------- #
set startingTime 0

proc tick {} {
    global clockStopped startingTime
    if $clockStopped return
    after 1000 tick
    set currentTime [getclock]
    set duration [expr $currentTime - $startingTime]
    set seconds [expr $duration % 60]
    set duration [expr $duration / 60]
    set minutes [expr $duration % 60]
    set hours [expr $duration / 60]
    .fstat.counter config -text [format "%2d:%02d:%02d" $hours $minutes $seconds]
}



proc switchOnTimer {} {
    global clockStopped startingTime
    .fstat.counter config -text 0:00:00
    set clockStopped 0
    set seconds 0
    set minutes 0
    set hours 0
    set startingTime [getclock]
    tick
}



proc switchOffTimer {} {
    global clockStopped
    set clockStopped 1
}



# -------------------- file I/O routines  --------------------------- #
proc readableP fd {
    if {[lindex [select $fd {} {} 10] 0] != {}} {
	return 1
    }
    return 0
}




# -------------------- remote execution routines --------------------------- #
proc killProcess pid {
    catch {exec kill $pid}
}



proc rexec {} {
    global progName displayName mgrName rexecPid portNumber
    set rexecPid [exec rexec_tool $progName $displayName $mgrName $portNumber &]
    .fgoodies.brexe config -state disabled
}



proc startRemoteExecution {} {
    global rexecPid
    if {$rexecPid != 0} {
	killProcess $rexecPid
    }
    rexec
}



proc remoteExecution {} {
    global state rexecPid
    if {$state != 2} {
	mkDialog .dialog {-text "You need to be execution a Calypso program\
		first.\n\n\Try start running a program first, or see\
		\"Steps to take...\" under the \"Help\" menu for further\
		information." -justify left -aspect 300} "{OK} {}"
	tkwait visibility .dialog
	grab .dialog
#	tkwait window .dialog
	return
    }
    if {$rexecPid != 0} {
	mkDialog .dialog {-text "You are already running this tool.\n\n\
		Do you want me to kill the first one and start another\
		instance?" -justify left -aspect 300} "{No, continue} {}" \
		"{Yes, restart} {startRemoteExecution}"
	tkwait visibility .dialog
	grab .dialog
#	tkwait window .dialog
	return
    }
    startRemoteExecution
}



# -------------------- monitor execution routines --------------------------- #
proc monitor {} {
    global progName mgrName monitorPid guiPortNumber
    set monitorPid [exec monitor_tool $progName $mgrName $guiPortNumber &]
    .fgoodies.bmonitor config -state disabled
}



proc startMonitorExecution {} {
    global monitorPid
    if {$monitorPid != 0} {
	killProcess $monitorPid
    }
    monitor
}



proc monitorExecution {} {
    global state monitorPid
    if {$state != 2} {
	mkDialog .dialog {-text "You need to be execution a Calypso program\
		first.\n\nTry start running a program first, or see\
		\"Steps to take...\" under the \"Help\" menu for further\
		information." -justify left -aspect 300} "{OK} {}"
	tkwait visibility .dialog
	grab .dialog
#	tkwait window .dialog
	return
    }
    if {$monitorPid != 0} {
	mkDialog .dialog {-text "You are already running this tool.\n\n\
		Do you want me to kill the first one and start another\
		instance?" -justify left -aspect 300} "{No, continue} {}" \
		"{Yes, restart} {startMonitorExecution}"
	tkwait visibility .dialog
	grab .dialog
#	tkwait window .dialog
	return
    }
    startMonitorExecution
}



# -------------------- execution termination routines -------------------------- #
proc removeFile fname {
    catch {exec rm -f $fname}
}



# Terminates the execution of the calypso program unconditionally
proc terminateExecution {} {
    global state rexecPid monitorPid progId
    switchOffTimer
    if {$rexecPid != 0} {
	killProcess $rexecPid
    }
    if {$monitorPid != 0} {
	killProcess $monitorPid
    }
    killProcess $progId
    alterState 0
}



# Terminates the execution of the calypso program conditionally by first
# asking the user, then...
proc terminateExecutionI {} {
    mkDialog .dialog {-text "Are you sure you want to interrupt the execution?"\
	    -justify left -aspect 300} \
	    "{Continue Execution} {}" "{Terminate Execution} terminateExecution"
    tkwait visibility .dialog
    grab .dialog
#    tkwait window .dialog
}



# -------------------- program terminaion routines --------------------------- #
# Terminates unconditionally
proc terminateProgram {} {
    global state
    if {$state == 2} {
	terminateExecution
    }
    exit
}



# Terminates conditionally.
# If a calypso program is running, it first asks the user, then...
proc terminateProgramI {} {
    global state progName
    if {$state == 2} {
	mkDialog .dialog "-text \"You are currently executing\
		$progName.  Exiting this program will terminate\
		its execution.\n\n\
		Do you really want to exit?\"\
		-justify left -aspect 300" "{No, Continue} {}" \
		"{Yes, Exit} {terminateProgram}"
	tkwait visibility .dialog
	grab .dialog
#	tkwait window .dialog
	return
    }
    terminateProgram
}



# --------------------- (calypso) program execution ------------------------ #
# sets the global variavle 'state' to 0 if 'prog' is not an executable program
# that exists in one of the directories specified by 'path'
proc validProgramP {prog path} {
    foreach p [split $path :] {
		if {$p != "."} {
			if [file executable $p/$prog] {
				return 1
			}
		}
	}
	return 0
}



proc startExecution {} {
    global state displayName progName mgrName hostName path withTimer portNumber guiPortNumber progId \
	    addArgs runXterm localWorker logProfile bunchJobs onlineUpdate smartSchedule \
	    REPORT NO_LOCALWORKER LOG NO_BUNCH NO_UPDATE NO_SSCHEDULE

    if {$state != 0} {
	return
    }

    if {$progName == ""} {
	mkDialog .dialog "-text {Error: Program not selected.\n\nYou must\
		select a program that exists on your search path before issuing\
		the \"Go\" command.\n\nSee \"Steps to take...\" under\
		the \"Help\" menu for further information.}\
		-justify left -aspect 300" "{OK} {}"
	tkwait visibility .dialog
	grab .dialog
#	tkwait window .dialog
	return
    }

    alterState 1
	scan $progName "%s" realProgName
    set validP [validProgramP $realProgName $path]

    if {$validP == 0} {
	alterState 0
	.fstat.mstat config -text "Waiting"
	mkDialog .dialog "-text \"Error: program $progName is not on\
		your search path.\n\nYou must either:
	(1) move it to a directory that is on your path, or
	(2) change your path and restart this program.\n\nExecution\
		did not occur.\" -justify left -aspect 300" "{OK} {}"
	tkwait visibility .dialog
	grab .dialog
#	tkwait window .dialog
	return
    }

    set rsd [server_create]
    set rsdPortNumber [lindex [fstat $rsd localhost] 2]

    if {$mgrName == $hostName} {
		set cmd [list exec]
    } else {
		set cmd [list exec rsh -n $mgrName]
    }
    if {$runXterm == 1} {
		lappend cmd xterm -geometry 80x55 -display $displayName -e
    }
    lappend cmd $progName "--" $REPORT $hostName $rsdPortNumber
    if {$localWorker == 0} {
		lappend cmd $NO_LOCALWORKER
    }
    if {$bunchJobs == 0} {
		lappend cmd $NO_BUNCH
    }
    if {$onlineUpdate == 0} {
		lappend cmd $NO_UPDATE
    }
    if {$smartSchedule == 0} {
		lappend cmd $NO_SSCHEDULE
    }
    if {$logProfile == 1} {
		lappend cmd $LOG
    }
    lappend cmd "--"
    if {$addArgs != ""} {
		set cmd [concat $cmd $addArgs]
    }
    lappend cmd {&}
    set progId [eval $cmd]

    if {[readableP $rsd] == 1} {
	set sd [server_accept $rsd]
	if {[readableP $sd] == 1} {
	    gets $sd bothPorts
	    set portNumber [lindex $bothPorts 0]
	    set guiPortNumber [lindex $bothPorts 1]
	    alterState 2
	    if {$withTimer} {
		switchOnTimer
	    }
	    close $rsd
	    close $sd
	    return
	}
	close $sd
    }
    close $rsd
    killProcess $progId
    alterState 0
    .fstat.mstat config -text "Waiting"
	mkDialog .dialog "-text \"Error: unable to execute $progName $addArgs\
		properly.\n\nYou should ensure:
    (1) that $progName is a valid Calypso program,
    (2) it is on your search path,
    (3) you have execution privilege for $progName, 
    (4) the arguments you gave it are correct.\n\nExecution\
	    did not occur.\" -justify left -aspect 300" "{OK} {}"
    tkwait visibility .dialog
    grab .dialog
    tkwait window .dialog
    return
}



# ---------------------- state manupulation routine --------------------------
proc alterState new_state {
	global state progName mgrName portNumber

	if {$state == $new_state} {
		return
	}

	if {$new_state == 0} {
	    set progId 0
	    set rexecPid 0
	    set monitorPid 0
	    .ftop.fdialogs.fents.eprog config -state normal
	    .ftop.fdialogs.fents.eargs config -state normal
	    .ftop.fdialogs.fents.emgr config -state normal
	    .ftop.fchecks.cxterm config -state normal
	    .ftop.fchecks.ctimer config -state normal
	    .ftop.fchecks.clw config -state normal
	    .ftop.fchecks.cbunch config -state normal
	    .ftop.fchecks.cupdate config -state normal
	    .ftop.fchecks.cschedule config -state normal
	    .ftop.fchecks.clog config -state normal
	    .fgoodies.brexe config -state disabled
	    .fgoodies.bmonitor config -state disabled
	    .fstat.mstat config -text "Waiting"
	    .fbutts.baction config -text "Go"
	    .fbutts.baction config -command startExecution
	} elseif {$new_state == 1} {
	    set progId 0
	    set rexecPid 0
	    set monitorPid 0
	    .fstat.mstat config -text "Checking and waiting for connection..."
	} elseif {$new_state == 2} {
	    .ftop.fdialogs.fents.eprog config -state disabled
	    .ftop.fdialogs.fents.eargs config -state disabled
	    .ftop.fdialogs.fents.emgr config -state disabled
	    .ftop.fchecks.cxterm config -state disabled
	    .ftop.fchecks.ctimer config -state disabled
	    .ftop.fchecks.clw config -state disabled
	    .ftop.fchecks.cbunch config -state disabled
	    .ftop.fchecks.cupdate config -state disabled
	    .ftop.fchecks.cschedule config -state disabled
	    .ftop.fchecks.clog config -state disabled
	    .fgoodies.brexe config -state normal
	    .fgoodies.bmonitor config -state normal
	    .fstat.mstat config -text "$progName is executing on $mgrName (port $portNumber) ..."
	    .fbutts.baction config -text "Stop Execution"
	    .fbutts.baction config -command terminateExecutionI
	}
	set state $new_state	
}



# --------------------- Help information -------------------------------------
#set helpTitles() ""
#set helpTopics() ""

set helpTitles(about) "Calypso"
set helpTopics(about) "
Calypso is a novel software system that helps you in\
	developing a parallel program, and then in executing\
	it on multiple computers connected by a network.  It\
	is unique in that it keeps all the nuences away from\
	the programmer and does it in its runtime system.\n
This is a graphical user interface\
	for running Calypso (version 1.0) programs.  You can\
	think of this tool as Calypso's Command and Control\
	Center.\n
With this tool you can:\n(1) start running a calypso\
	program\n(2) monitor its progress\n(3) see the\
	utilization of other computers on your network\n(4)\
	use them in your computation"

set helpTitles(version) "the current version number"
set helpTopics(version) "
This is Calypso version 1.0"

set helpTitles(steps) "steps to take, to start running a program"
set helpTopics(steps) "
You need to do three things to start executing a program:
1. First fill in the entries that\
	corespond to the program name, additional arguments (if any),\
	and select a host machine to run your program\
	(the default host is a good choice in many cases).
2. Check the radial button if the program should run in an Xterminal.
3. Click on the \"Go\" button to start execution.\n
It is that easy!"

set helpTitles(rxterm) "running a program in an Xterminal"
set helpTopics(rxterm) "
Some programs require a terminal environment to run\
	in, some don't.  An example of the former is a program that\
	reads or wirtes to the standard input or output.  Examples of\
	the latter programs are batch jobs and X clients.\n
If your program requires a terminal\
	to run in, make sure the \"run in xterm\" button is selected."

set helpTitles(timer) "timing an execution"
set helpTopics(timer) "
Timer is a little clock that appears on\
	the lower-left corner of the main window, to show\
	the execution time of a running program.\n\nNotice that it\
	might degrade the performance a bit.
If you like to see it, clicking on the \"timer\" button."

set helpTitles(rexec) "how to remotely execute your program"
set helpTopics(rexec) "
With the \"Remote Execution Tool\" you can view the average\
	load on remote machines on your network, and start a\
	worker process on them.\n
Notice that this tool reads\
	the .calypsorc file in your home directory\
	to create a list of remote machines.  Thus, please make\
	sure that file exists.\n
To start the remote execution\
	tool, you must be firest running a Calypso program, then\
	just click on the button titled \"Remote Execution Tool.\""

set helpTitles(monitor) "how to monitor an execution"
set helpTopics(monitor) "
With the \"Execution Monitoring Tool\" you can monitor\
	the execution state of your program.\n
Notice that there will be a small performance degradation\
	when using this tool.\n
To start the monitoring tool, after starting a Calypso program, \
	just click on the button titled \"Execution Monitoring Tool.\""



# mkDialog w msgArgs list list ...
#
# Create a dialog box with a message and any number of buttons at
# the bottom.
#
# Arguments:
#    w -	Name to use for new top-level window.
#    msgArgs -	List of arguments to use when creating the message of the
#		dialog box (e.g. text, justifcation, etc.)
#    list -	A two-element list that describes one of the buttons that
#		will appear at the bottom of the dialog.  The first element
#		gives the text to be displayed in the button and the second
#		gives the command to be invoked when the button is invoked.
proc mkDialog {w msgArgs args} {
    catch {destroy $w}
    toplevel $w -class Dialog
    wm title $w "Dialog box"
    wm iconname $w "Dialog"
    set oldFocus [focus]

    # Create two frames in the main window. The top frame will hold the
    # message and the bottom one will hold the buttons.  Arrange them
    # one above the other, with any extra vertical space split between
    # them.

    frame $w.top -relief raised -border 1
    frame $w.bot -relief raised -border 1
    pack $w.top $w.bot -side top -fill both -expand yes

    # Create the message widget and arrange for it to be centered in the
    # top frame.
    
    eval message $w.top.msg -justify center \
	    -font -Adobe-times-medium-r-normal--*-180* $msgArgs
    pack $w.top.msg -side top -expand yes -padx 3 -pady 3

    # Create as many buttons as needed and arrange them from left to right
    # in the bottom frame.  Embed the left button in an additional sunken
    # frame to indicate that it is the default button, and arrange for that
    # button to be invoked as the default action for clicks and returns in
    # the dialog.

    if {[llength $args] > 0} {
	set arg [lindex $args 0]
	frame $w.bot.0 -relief sunken -border 1
	pack $w.bot.0 -side left -expand yes -padx 10 -pady 10
	button $w.bot.0.button -text [lindex $arg 0] \
		-command "[lindex $arg 1]; destroy $w; focus $oldFocus"
	pack $w.bot.0.button -expand yes -padx 6 -pady 6
	bind $w <Return> "[lindex $arg 1]; destroy $w; focus $oldFocus"
	focus $w

	set i 1
	foreach arg [lrange $args 1 end] {
	    button $w.bot.$i -text [lindex $arg 0] \
		    -command "[lindex $arg 1]; destroy $w; focus $oldFocus"
	    pack $w.bot.$i -side left -expand yes -padx 10
	    set i [expr $i+1]
	}
    }
    bind $w <Any-Enter> [list focus $w]
    focus $w
    wm geometry $w +300+350
}



proc Help {topic {x 0} {y 0}} {
    global helpTopics helpTitles rexecPid
    if {$topic == ""} return
    if [info exists helpTitles($topic)] {
	set title $helpTitles($topic)
    } else {
	set title $topic
    }
    if [info exists helpTopics($topic)] {
	set msg $helpTopics($topic)
    } else {
	set msg "Sorry, but no help is available for this topic"
    }
    mkDialog .dialog "-text {Information on $title:\n\n$msg} -justify left -aspect 300" "{OK} {}"
}


# ---------------------------------------------------------------------- #
# ---------------------------------------------------------------------- #
foreach i [winfo child .] {
    catch {destroy $i}
}



wm title . "Calypso"
tk_bindForTraversal .
focus .
wm resizable . 0 0



if {$argc >= 1} {
    set progName [lindex $argv 0]
}



# create the menu title bar
frame .menu -relief raised -borderwidth 2
pack .menu -side top -fill x
# Options menu...
menubutton .menu.options -text "Options" -menu .menu.options.m -underline 0
menu .menu.options.m
.menu.options.m add command -label "Exit program" -underline 0 -command "terminateProgramI"
pack .menu.options -side left
tk_menuBar .menu .menu.options

# Help menu...
menubutton .menu.help -text "Help" -menu .menu.help.m -underline 0
menu .menu.help.m
.menu.help.m add command -label "About Calypso..." -underline 0 -command {Help about}
.menu.help.m add command -label "On Version" -underline 3 -command {Help version}
.menu.help.m add command -label "On Steps to take..." -underline 3 -command {Help steps}
.menu.help.m add command -label "On Running in Xterm..." -underline 3 -command {Help rxterm}
.menu.help.m add command -label "On Timer..." -underline 3 -command {Help timer}
.menu.help.m add command -label "On Remote Execution..." -underline 3 -command {Help rexec}
.menu.help.m add command -label "On Execution Monitoring..." -underline 3 -command {Help monitor}
pack .menu.help -side right
tk_menuBar .menu .menu.help



# First a dummy frame to determine the width of the window...
frame .fdummy -width 600
pack .fdummy -side top -fill x



# the top frame used for the dialog boxes and buttons...
frame .ftop
pack .ftop -fill x

# Dialog frame at the top...
frame .ftop.fdialogs -relief ridge -borderwidth 3
# -width 30
frame .ftop.fdialogs.flabs -relief raised -borderwidth 0
frame .ftop.fdialogs.fents -relief raised -borderwidth 0
label .ftop.fdialogs.flabs.lprog -text "Calypso program:"
label .ftop.fdialogs.flabs.largs -text "Additional arguments:"
label .ftop.fdialogs.flabs.lmgr -text "Manager on:"
entry .ftop.fdialogs.fents.eprog -relief sunken -bd 2 -textvariable progName
entry .ftop.fdialogs.fents.eargs -relief sunken -bd 2 -textvariable addArgs
entry .ftop.fdialogs.fents.emgr -relief sunken -bd 2 -textvariable mgrName
#.ftop.fdialogs.fents.emgr config -state disabled

# Checkbutton frame to the right of the dialog frame...
frame .ftop.fchecks -relief ridge -borderwidth 3
# -relief flat -borderwidth 3
checkbutton .ftop.fchecks.cxterm -text "run in xterm" -relief flat -variable runXterm
checkbutton .ftop.fchecks.ctimer -text "timer" -relief flat -variable withTimer
checkbutton .ftop.fchecks.clw -text "local worker" -relief flat -variable localWorker
checkbutton .ftop.fchecks.cbunch -text "bunch jobs" -relief flat -variable bunchJobs
checkbutton .ftop.fchecks.cupdate -text "online update" -relief flat -variable onlineUpdate
checkbutton .ftop.fchecks.cschedule -text "smart scheduling" -relief flat -variable smartSchedule
checkbutton .ftop.fchecks.clog -text "log profile" -relief flat -variable logProfile

pack .ftop.fdialogs .ftop.fchecks -side left -expand 1 -fill both -padx 3m
pack .ftop.fdialogs.flabs -side left
pack .ftop.fdialogs.fents -side left
pack .ftop.fdialogs.flabs.lprog .ftop.fdialogs.flabs.largs .ftop.fdialogs.flabs.lmgr \
	-side top -anchor e -pady 1m
pack .ftop.fdialogs.fents.eprog .ftop.fdialogs.fents.eargs .ftop.fdialogs.fents.emgr \
	-side top -anchor w -pady 1m
pack .ftop.fchecks.cxterm .ftop.fchecks.ctimer .ftop.fchecks.clw .ftop.fchecks.cbunch \
	.ftop.fchecks.cschedule .ftop.fchecks.cupdate -side top -anchor w
#	.ftop.fchecks.cschedule .ftop.fchecks.clog -side top -anchor w
#	.ftop.fchecks.cupdate .ftop.fchecks.cschedule .ftop.fchecks.clog -side top -anchor w



# Button frame right below the checkbutton frame...
frame .fbutts -relief flat -borderwidth 0
button .fbutts.baction -text "Go" -width 20 -command {startExecution}
pack .fbutts -side top -pady 3m
pack .fbutts.baction



# Goodies (toolbox) frame righ below the button frame...
frame .fgoodies -height 3c -relief ridge -borderwidth 3
label .fgoodies.ltools -text "Tool Box"
button .fgoodies.brexe -text "Remote Execution Tool" -command remoteExecution -height 3
button .fgoodies.bmonitor -text "Execution Monitoring Tool" -command monitorExecution -height 3
pack .fgoodies -side top -expand 1 -fill both
pack .fgoodies.ltools -fill x -side top
pack .fgoodies.brexe .fgoodies.bmonitor -side left -expand 1 -fill both



# A status line frame right at the bottom...
frame .fstat -borderwidth 1
message .fstat.mtitle -text "Status:" -justify left -width 30c
message .fstat.mstat -text "Waiting" -justify left -bd 2 -width 30c
label .fstat.counter -text 0:00:00 -width 10
pack .fstat -side left -fill x -expand 1
pack .fstat.mtitle -side left
pack .fstat.mstat -side left
pack .fstat.counter -side right


signal trap SIGCHLD {sigSIGCHLDhandler %S}

alterState 0

