#!/usr/bin/wish
# Simple interface to start the ACM program.
#
# Requires the tk/tcl interpreter from http://www.tcl.tk/
# Under Windows, the recommended implementation I suggest is that by Magicsplat
# at http://www.magicsplat.com/tcl-installer/index.html
#
# Author: Umberto Salsi (salsi@icosaedro.it)
# Version: $Date: 2017/10/29 05:21:15 $

set padx 10
set pady 10

set butw 10

set font_norm "Helvetica 10"
set font_em "Helvetica 10 bold"
set font_but "Helvetica 10 bold"
set font_mono "Courier 9"


# Creates a button.
# path: tk path of the button.
# name: label.
# cmd: associated command.
#
proc StdButt { path name cmd } {
	global font_but
	button $path -text $name -width 10 -command $cmd -font $font_but
	#pack $path
}


# Text entry box.
# w: tk path.
# label: description before entry field.
# varname: bound variable.
# width: width of the text entry field (roughly a char).
# unit: description past the entry box, typically the unit of measurement.
#
proc StdEntry { w label varname width unit } {
	global font_norm
	frame $w
	#pack $w

	label $w.l -text $label -font $font_norm -width 18 -anchor w
	pack $w.l -side left

	if { [string length $unit] >= 1 } {
		label $w.u -text $unit -font $font_norm
		pack $w.u -side right
	}

	entry $w.v -textvariable $varname -background white -width $width
	pack $w.v -side right
}


# Button press feedback and associated command invocation.
#
proc Press { button } {
	$button configure -relief sunken
	update ;# update window
	after 500 ;# wait 0.5 s
	$button configure -relief raised ;# release button
	$button invoke ;# execute command bound to the button
}


# Returns the absolute path of the file using the current working directory for
# resolution. Returns the path itself if not relative.
#
proc abspath { file } {
	if { ! [file exists $file] } {
		return $file
	}
	if { [file pathtype $file] == "relative" } {
		set file [file join [pwd] $file]
	}
	return $file
}


# Tries to figure out where the objects directory is and return the best guess.
#
proc GuessObjectsDir { } {
	global program objects
	if { [string length $objects] > 0 } {
		return
	}
	if { [string length $program] == 0 } {
		return
	}
	set d [file dirname [file dirname $program]]
	set d "$d/objects"
	if { [file exists $d] } {
		set objects $d
	}
}


# Parse int number. Returns 0 on error.
proc parseInt { x } {
	if { [scan $x "%d" y] == 1 } {
		return $y
	} else {
		return 0
	}
}

# Par floating point number. Returns zero on error.
proc parseFloat { x } {
	if { [scan $x "%f" y] == 1 } {
		return $y
	} else {
		return 0.0
	}
}


set PI 3.141592653589793
set FEETtoMETERS 0.30480060960
set EARTH_MAJOR 6378137.0


# Parse latitude.
# lat_string: latitude as "DD-PP-SS.SSSH".
# Returns: RAD.
proc parseLatitude {lat_string} {
	global PI
	if { [scan $lat_string "%d-%d-%f%c" d p s e] != 4 } {
		error "Failed to parse latitude $lat_string."
		return 0
	}
	set lat [expr $d/180.0*$PI + ($p/60.0/180.0*$PI) + $s/3600.0/180.0*$PI]
	if { [string compare $e "S"] == 0 } {
		set lat [expr - $lat]
	}
	return $lat
}


# Parse longitude.
# s: latitude as "DD-PP-SS.SSSH".
# Returns: RAD.
proc parseLongitude {lon_string} {
	global PI
	if { [scan $lon_string "%d-%d-%f%c" d p s e] != 4 } {
		error "Failed to parse longitude $lon_string."
		return 0
	}
	set lon [expr $d/180.0*$PI + ($p/60.0/180*$PI) + $s/3600.0/180*$PI]
	if { [string compare $e "W"] == 0 } {
		set lat [expr - $lon]
	}
	return $lon
}


proc formatLatitude {lat} {
	global PI
	set e "N"
	if { $lat < 0 } {
		set lat [expr - $lat]
		set e "S"
	}
	set lat [expr $lat / $PI * 180.0]
	set d [expr floor($lat)]
	set lat [expr 60.0*($lat - $d)]
	set p [expr floor($lat)]
	set lat [expr 60.0*($lat - $p)]
	set s $lat
	return [format "%02.0f-%02.0f-%02.3f%s" $d $p $s $e]
}


proc formatLongitude {lon} {
	global PI
	set e "E"
	if { $lon < 0 } {
		set lon [expr - $lon]
		set e "W"
	}
	set lon [expr $lon / $PI * 180.0]
	set d [expr floor($lon)]
	set lon [expr 60.0*($lon - $d)]
	set p [expr floor($lon)]
	set lon [expr 60.0*($lon - $p)]
	set s $lon
	return [format "%03.0f-%02.0f-%02.3f%s" $d $p $s $e]
}


# Returns runway ends gedetic coordinates given it center, length and bearing.
# Used to convert the RWY2 records.
# Return: list of lat1, lon1, lat2 and lon2.
proc computeRunwayEnds { center_lat_field center_lon_field len_field heading_field } {
	global PI FEETtoMETERS EARTH_MAJOR
	set center_lat [parseLatitude $center_lat_field]
	set center_lon [parseLongitude $center_lon_field]
	set half_len [expr 0.5 * [parseFloat $len_field] * $FEETtoMETERS]
	set heading [expr [parseFloat $heading_field] / 180.0 * $PI]
	# Normalize heading range to [0,180[ so runway ends can be listed in
	# the correct order.
	if { $heading >= $PI } {
		set heading [expr $heading - $PI]
	}
	set delta_lat [expr $half_len * cos($heading) / $EARTH_MAJOR]
	set delta_lon [expr $half_len * sin($heading) / ($EARTH_MAJOR * cos($center_lat))]
	return [list \
		[formatLatitude  [expr $center_lat - $delta_lat]] \
	    [formatLongitude [expr $center_lon - $delta_lon]] \
	    [formatLatitude  [expr $center_lat + $delta_lat]] \
		[formatLongitude [expr $center_lon + $delta_lon]] ]
}


set displayLongMessageCount 0


proc DisplayLongMessage { title content } {
	global displayLongMessageCount
	set displayLongMessageCount [expr $displayLongMessageCount + 1]
	set w .displayLongMessage_$displayLongMessageCount
	toplevel $w
	wm title $w $title
	wm minsize $w 400 300
	frame $w.f
	text $w.f.t -width 50 -height 10 -yscrollcommand "$w.f.sbar set"
	scrollbar $w.f.sbar -orient vertical -command "$w.f.t yview"
	$w.f.t insert 1.0 $content
	$w.f.t see end
	pack $w.f.t -side left -fill y -expand true -fill both
	pack $w.f.sbar -side left -fill y
	pack $w.f -expand true -fill both
	button $w.ok -text "  OK  " -command "destroy $w"
	pack $w.ok -side bottom -pady 5
	wm withdraw $w
	update
	wm deiconify $w
}


proc RunACM {} {
	global \
		env \
		mouse_mode \
		airspeed \
		altitude \
		debug \
		dis_force \
		dis_protocol \
		dis_relay_name \
		dis_relay_port \
		dis_appl \
		dis_exercise \
		dis_site \
		drone_mode \
		drone_aggressiveness \
		framerate \
		fuel \
		payload \
		hud_mode \
		geometry_w geometry_h geometry_x geometry_y \
		heading \
		joystick \
		dept_descr \
		dept_time_usenow \
		dept_time \
		latitude \
		longitude \
		objects \
		plane \
		program \
		eye_to_screen_cm \
		downward_view_angle_deg \
		sound \
		visibility \
		clouds_base \
		clouds_top \
		wind_d \
		wind_v \
		gust \
		ground_mode \


	if { [file exists $program] } {
		set program [abspath $program]
	}

	if { [file exists $objects] } {
		set objects [abspath $objects]
	}

	set user "guest"
	if { [info exists env("USER")] } {
		# Typically available on *nix.
		set user $env("USER")
	} elseif { [info exists env("USERNAME")] } {
		# Available on Cygwin.
		set user $env("USERNAME")
	}

	set opt "-name \"$user\" -mouse-mode $mouse_mode -da 0.$drone_aggressiveness"

	if { [string compare $drone_mode "hunting" ] == 0 } {
		set opt "$opt -drone-mode HUNTING"
	} else {
		set opt "$opt -drone-mode DOG_FIGHT"
	}

	if { [string length $geometry_w] >= 1 && [string length $geometry_h] >=1 } {
		set geometry_w [parseInt $geometry_w]
		set geometry_h [parseInt $geometry_h]
		set opt "$opt -geometry ${geometry_w}x$geometry_h"
		if { [string length $geometry_x] >= 1 && [string length $geometry_y] >= 1 } {
			set geometry_x [parseInt $geometry_x]
			set geometry_y [parseInt $geometry_y]
			set opt "$opt+$geometry_x+$geometry_y"
		}
	}

	if { [string length $objects] >= 1 } {
		set opt "$opt -objects $objects"
	}

	if { [string length $eye_to_screen_cm] >= 1 } {
		set eye_to_screen_cm [expr floor(10.0 * [parseFloat $eye_to_screen_cm]) / 10]
		if { $eye_to_screen_cm < 5 } {
			set eye_to_screen_cm 5
		}
		if { $eye_to_screen_cm > 500 } {
			set eye_to_screen_cm 500
		}
		set opt "$opt -eye_to_screen_cm $eye_to_screen_cm"
	}

	if { [string length $downward_view_angle_deg] >= 1 } {
		set downward_view_angle_deg [expr floor(10.0 * [parseFloat $downward_view_angle_deg]) / 10]
		if { $downward_view_angle_deg < 0.0 } {
			set downward_view_angle_deg 0.0
		}
		if { $downward_view_angle_deg > 45.0 } {
			set downward_view_angle_deg 45.0
		}
		set opt "$opt -downward_view_angle_deg $downward_view_angle_deg"
	}

	if { $dis_protocol } {
		set opt "$opt -force $dis_force -dis-relay-name \"$dis_relay_name\" -dis-relay-port $dis_relay_port -dis-site \"$dis_site\" -dis-exercise \"$dis_exercise\" -dis-appl \"$dis_appl\""
	}

	if { ! $sound } {
		set opt "$opt -no-sound"
	}

	if { [string length $framerate] >= 1 } {
		set framerate [parseInt $framerate]
		set opt "$opt -frame-rate $framerate"
	}

	if { [string length $visibility] >= 1 } {
		set visibility [parseFloat $visibility]
		set opt "$opt -visibility $visibility"
	}

	set clouds_base [parseFloat $clouds_base]
	set clouds_top  [parseFloat $clouds_top]
	if { $clouds_base <= $clouds_top } {
		set opt "$opt -clouds-range $clouds_base $clouds_top"
	}

	if { [string length $clouds_base] >= 1 && [string length $clouds_top] >= 1 } {
		set clouds_base [parseFloat $clouds_base]
		set clouds_top  [parseFloat $clouds_top]
	}

	if { [string length $wind_d] >= 1 && [string length $wind_v] >= 1 } {
		set wind_d [parseInt $wind_d]
		set wind_v [parseInt $wind_v]
		set opt "$opt -wind $wind_d/$wind_v"
	}

	if { [string length $gust] >= 1 } {
		set gust [parseFloat $gust]
		set opt "$opt -gust $gust"
	}

	set opt "$opt -ground-mode $ground_mode"

	set opt "$opt -plane \"$plane\""

	if { [string length $fuel] > 0 } {
		set fuel [parseInt $fuel]
		set opt "$opt -fuel $fuel"
	}

	if { [string length $payload] > 0 } {
		set payload [parseInt $payload]
		set opt "$opt -payload $payload"
	}

	if { $hud_mode } {
		set opt "$opt -hud-mode"
	}

	if { ! $dept_time_usenow && [string length $dept_time] > 0 } {
		#set opt "$opt -departure-time \"$dept_time\""
		set opt "$opt -departure-time $dept_time"
	}

	if { [string length $latitude] >= 1 } {
		set opt "$opt -latitude $latitude"
	}

	if { [string length $longitude] >= 1 } {
		# FIXME: missing validation
		#scan $longitude "%d-%d-%f%s" d p s e
		#set lon [expr $d + ($p/60.0) + $s/3600.0]
		#if { [string compare $e "W"] == 0 } {
		#	set lon [expr - $lon]
		#}
		set opt "$opt -longitude $longitude"
	}

	if { [string length $altitude] >= 1 } {
		set altitude [parseInt $altitude]
		set opt "$opt -altitude $altitude"
	}

	if { [string length $heading] >= 1 } {
		set heading [parseInt $heading]
		set opt "$opt -heading $heading"
	}

	if { [string length $airspeed] >= 1 } {
		set airspeed [parseInt $airspeed]
		set opt "$opt -airspeed-kt $airspeed"
	}

	#if { [string length $joystick] >= 1 } {
	#	set opt "$opt -js $joystick"
	#}

	if { $debug } {
		set cmd "xterm -e gdb --args $program $opt"
		#puts "$cmd"
	} else {
		set cmd "$program $opt"
	}

	wm iconify .
	# Notes about the behavior of the tcl "exec" command.
	# $out collects both stdout and stderr.
	# If the invoked program sends anything to stderr, "exec" will
	# report error code 1 anyway, disregarding the actual exit status.
	# If the exit status of the program is error but no output sent to stderr,
	# tcl adds the line "child process exited abnormally" to $out.
	set status [catch {eval exec $cmd} out]
	wm deiconify .
	if { $status == 0 } {
		if { [string length $out] > 0 } {
			DisplayLongMessage "ACM message for you" $out
		}
	} else {
		DisplayLongMessage "ACM Error Report" \
			"ACM ERROR REPORT\n\nCommand\n--------------------\n$cmd\n\nOutput\n--------------------\n$out\n\nExit code\n--------------------\n$status"
	}
}


proc SetDefault { } {
	global \
		rcfile \
		mouse_mode \
		airspeed \
		altitude \
		debug \
		dis_force \
		dis_protocol \
		dis_relay_name \
		dis_relay_port \
		dis_appl \
		dis_exercise \
		dis_site \
		drone_mode \
		drone_aggressiveness \
		framerate \
		fuel \
		payload \
		hud_mode \
		geometry_w geometry_h geometry_x geometry_y \
		heading \
		joystick \
		dept_descr \
		dept_time_usenow \
		dept_time \
		latitude \
		longitude \
		objects \
		plane \
		program \
		eye_to_screen_cm \
		downward_view_angle_deg \
		sound \
		visibility \
		clouds_base \
		clouds_top \
		wind_d \
		wind_v \
		gust \
		ground_mode \

	set mouse_mode "normal"
	set airspeed ""
	set altitude ""
	set debug 0
	set dis_force "Other"
	set dis_protocol 0
	set dis_relay_name ""
	set dis_relay_port 3000
	set dis_appl -1
	set dis_exercise 1
	set dis_site -1
	set drone_mode "dog fight"
	set drone_aggressiveness 50
	set framerate 20
	set fuel ""
	set payload 150
	set hud_mode 0
	set geometry_w "800"
	set geometry_h "600"
	set geometry_x "0"
	set geometry_y "0"
	set heading ""
	set joystick ""
	set dept_descr ""
	set dept_time_usenow "1"
	set dept_time ""
	set latitude ""
	set longitude ""
	set objects ""
	if { [file exists "objects"] } {
		set objects "objects"
	} else {
		# keep curr value
	}
	set plane "C-172"
	if { [file exists "acm.exe"] } {
		set program "acm.exe"
	} elseif { [file exists "src/acm/acm.exe"] } {
		set program "src/acm/acm.exe"
	} else {
		# keep curr value
	}
	set eye_to_screen_cm 50.0
	set downward_view_angle_deg 10.0
	set sound 0
	set visibility 20
	set clouds_base 0
	set clouds_top  0
	set wind_d "0"
	set wind_v "0"
	set gust 0
	set ground_mode flat
}


proc Save { } {
	global \
		rcfile \
		mouse_mode \
		airspeed \
		altitude \
		debug \
		dis_force \
		dis_protocol \
		dis_relay_name \
		dis_relay_port \
		dis_appl \
		dis_exercise \
		dis_site \
		display \
		drone_mode \
		drone_aggressiveness \
		framerate \
		fuel \
		payload \
		hud_mode \
		geometry_w geometry_h geometry_x geometry_y \
		heading \
		joystick \
		dept_descr \
		dept_time_usenow \
		dept_time \
		latitude \
		longitude \
		objects \
		plane \
		program \
		eye_to_screen_cm \
		downward_view_angle_deg \
		sound \
		visibility \
		clouds_base \
		clouds_top \
		wind_d \
		wind_v \
		gust \
		ground_mode \

	set fid [open $rcfile w]
	puts $fid "set mouse_mode \"$mouse_mode\""
	puts $fid "set airspeed \"$airspeed\""
	puts $fid "set altitude \"$altitude\""
	puts $fid "set debug \"$debug\""
	puts $fid "set dis_protocol \"$dis_protocol\""
	puts $fid "set dis_relay_name \"$dis_relay_name\""
	puts $fid "set dis_relay_port $dis_relay_port"
	puts $fid "set dis_appl $dis_appl"
	puts $fid "set dis_exercise $dis_exercise"
	puts $fid "set dis_site $dis_site"
	puts $fid "set drone_mode \"$drone_mode\""
	puts $fid "set drone_aggressiveness $drone_aggressiveness"
	puts $fid "set framerate \"$framerate\""
	puts $fid "set fuel \"$fuel\""
	puts $fid "set payload \"$payload\""
	puts $fid "set hud_mode $hud_mode"
	puts $fid "set geometry_w \"$geometry_w\""
	puts $fid "set geometry_h \"$geometry_h\""
	puts $fid "set geometry_x \"$geometry_x\""
	puts $fid "set geometry_y \"$geometry_y\""
	puts $fid "set heading \"$heading\""
	puts $fid "set joystick \"$joystick\""
	puts $fid "set dept_descr \"$dept_descr\""
	puts $fid "set dept_time_usenow \"$dept_time_usenow\""
	puts $fid "set dept_time \"$dept_time\""
	puts $fid "set latitude \"$latitude\""
	puts $fid "set longitude \"$longitude\""
	puts $fid "set objects \"$objects\""
	puts $fid "set plane \"$plane\""
	puts $fid "set program \"$program\""
	puts $fid "set eye_to_screen_cm \"$eye_to_screen_cm\""
	puts $fid "set downward_view_angle_deg \"$downward_view_angle_deg\""
	puts $fid "set sound \"$sound\""
	puts $fid "set dis_force $dis_force"
	puts $fid "set visibility \"$visibility\""
	puts $fid "set clouds_base \"$clouds_base\""
	puts $fid "set clouds_top \"$clouds_top\""
	puts $fid "set wind_d \"$wind_d\""
	puts $fid "set wind_v \"$wind_v\""
	puts $fid "set gust \"$gust\""
	puts $fid "set ground_mode \"$ground_mode\""
	close $fid
}


proc readPlanesList { } {
	global program objects

	if { ! [file exists "$program"] } {
		return ERROR_missing_program
	}

	if { ! [file exists "$objects"] } {
		return ERROR_missing_objects_dir
	}

	catch {exec $program -objects $objects -plane xxx} err
	set planes [split $err "\n"]
	set planes [lrange $planes 1 [expr [llength $planes] - 2]]
	set planes [lsort $planes]
	return $planes
}


proc updatePlanesMenu { } {
# Populates the planes menu
	global planes_menu

	set planes [readPlanesList]
	$planes_menu delete 0 9999
	foreach p $planes {
		$planes_menu add command -label $p -command "set plane \"$p\""
	}
}


proc show_panel { panel } {
	# Raise all the tab button:
	.tab.plane configure -relief raised
	.tab.departure configure -relief raised
	.tab.environment configure -relief raised
	.tab.drones configure -relief raised
	.tab.configure configure -relief raised
	.tab.dis configure -relief raised

	# Hide all the panels:
	pack forget .plane .departure .environment .drones .configure .dis

	# Sunk selected button and show selected panel:
	.tab.$panel configure -relief sunken
	pack .$panel -anchor nw -expand 1 -fill both -padx 30 -pady 15
}


SetDefault


#
# Load saved preferences:
#

set home $env(HOME)
set rcfile "$home/.acmtk.rc"
if { [file exists $rcfile] } {
	source $rcfile
}



wm title . "Air Combat Maneuvering"

#
# Layout:
#

# Tab buttons:
pack [frame .tab] -side top -anchor nw

# Buttons:
pack [frame .buttons] -side bottom -anchor se
pack [frame .space -width 20] -side bottom

# Panels:
pack \
	[frame .plane] \
	[frame .departure] \
	[frame .environment] \
	[frame .drones] \
	[frame .configure] \
	[frame .dis] \
	 -anchor nw


#
# Tab buttons:
#

set f .tab
button $f.plane -text Plane -command {show_panel plane}
button $f.departure -text Departure -command {show_panel departure}
button $f.environment -text Environment -command {show_panel environment}
button $f.drones -text Drones -command {show_panel drones}
button $f.dis -text DIS -command {show_panel dis}
button $f.configure -text Configure -command {show_panel configure}
pack $f.plane $f.departure $f.environment $f.drones $f.dis $f.configure -side left


#
# "Plane" panel:
#

set f .plane.model
pack [frame $f] -anchor nw
pack [label $f.l -text "Model:" -font $font_norm] -side left
set planes_menu [tk_optionMenu $f.v plane ???]
pack $f.v -side left

set f .plane
StdEntry $f.payload "Payload:" payload 8 "lb"
StdEntry $f.fuel "Fuel:" fuel 8 "lb"
pack $f.payload $f.fuel -anchor nw

set g $f.hud_mode
pack [frame $g] -anchor nw

set g $f.panel
pack [frame $g] -anchor nw
pack [label $g.l -text "Panel:" -font $font_norm] -side left
pack [radiobutton $g.classic -text "Classic" \
	-variable hud_mode -value 0 -font $font_norm] -side left
pack [radiobutton $g.hud -text "HUD" \
	-variable hud_mode -value 1 -font $font_norm] -side left

StdEntry $f.eye_to_screen_cm "Eye dist. from screen:" \
	eye_to_screen_cm 5 "cm"
pack $f.eye_to_screen_cm -anchor nw

StdEntry $f.downward_view_angle_deg "Downward view angle:" \
	downward_view_angle_deg 5 "DEG"
pack $f.downward_view_angle_deg -anchor nw


#
# "Departure" panel:
#


proc error {msg} {
	tk_messageBox -type ok -icon error -message $msg -parent . -title Error
}


# Trim and split the string at spaces. Return list of fields.
# Empy lines and lines beginning with '#' contains zero fields.
proc mysplit {s} {
	set res {}
	set s [string trim $s]
	if { [string length $s] == 0
	|| [string compare [string index $s 0] "#"] == 0 } {
		return $res
	}
	set l [split $s]
	foreach e $l {
		set x [string trim $e]
		if {[string length $x] > 0} {
			lappend res $x
		}
	}
	return $res
}


# Zone selected from zones menu. Parses the scenery file and collects runways,
# then populates the runways list box.
# zones_path: path of the zones file needed for relative path resolution.
# zone: path of the scenery; if relative, join directory of zones file.
proc selectZone {zones_path zone} {
	global curr_zone runways_listbox
	$runways_listbox delete 0 end
	set curr_zone $zone
	set zone_path $zone
	if {[file pathtype $zone_path] == "relative"} {
		set zones_dir [file dirname $zones_path]
		set zone_path "$zones_dir/$zone_path"
	}
	if [catch {open $zone_path r} f] {
		error "Failed opening $zone_path."
		return
	}
	set entries {}
	while {[gets $f line] >= 0} {
		set args [mysplit $line]
		if {[llength $args] == 10 && [lindex $args 0] == "RWY"} {
			set lat1 [lindex $args 6]
			set lon1 [lindex $args 7]
			set lat2 [lindex $args 8]
			set lon2 [lindex $args 9]

		} elseif {[llength $args] == 9 && [lindex $args 0] == "RWY2"} {
			set ends [computeRunwayEnds [lindex $args 6] [lindex $args 7] \
				[lindex $args 4] [lindex $args 8]]
			set lat1 [lindex $ends 0]
			set lon1 [lindex $ends 1]
			set lat2 [lindex $ends 2]
			set lon2 [lindex $ends 3]

		} else {
			continue
		}
		set ends [split [lindex $args 2] "/"]
		lappend entries [format " %-4s %-3s  %s  %s" [lindex $args 1] [lindex $ends 0]   $lat1 $lon1]
		lappend entries [format " %-4s %-3s  %s  %s" [lindex $args 1] [lindex $ends 1]   $lat2 $lon2]
	}
	set entries [lsort -dictionary $entries]
	foreach e $entries {
		$runways_listbox insert end $e
	}
	close $f
}
	

# Populates the zones menu reading the specified zones file.
#
proc updateZonesMenu {zones_path} {
	global curr_zone zones_menu
	set curr_zone ""
	if [catch {open $zones_path r} f] {
		error "Failed opening $zones_path."
		return
	}
	$zones_menu delete 0 end
	set curr_zone ""
	while {[gets $f line] >= 0} {
		set args [mysplit $line]
		if {[llength $args] != 5} {
			continue
		}
		set zone [lindex $args 4]
		if {[string length $curr_zone] == 0} {
			set curr_zone $zone
		}
		$zones_menu add command -label $zone -command "selectZone {$zones_path} {$zone}"
	}
	close $f
}


# Runway selection callback bind to the <<ListboxSelect>> event.
# Retrieve the runway just selected, parse its content and set the
# departure fields accordingly: description, lat, lon, heading.
#
proc selectRunway {} {
	global runways_listbox curr_zone dept_descr latitude longitude heading altitude airspeed
	set i [lindex [$runways_listbox curselection] 0]
	if { [string length $i] == 0 } {
		return
	}
	set l [mysplit [$runways_listbox get $i]]
	if { [llength $l] != 4 } {
		return
	}
	set airport [lindex $l 0]
	set rwyend [lindex $l 1]
	set latitude [lindex $l 2]
	set longitude [lindex $l 3]
	set dept_descr "[file tail $curr_zone] $airport $rwyend"
	set heading [expr [scan $rwyend "%d"] * 10]
	set altitude ""
	set airspeed ""
}


# Load available zones and initialize zones menu and runways list box.
#
proc readZonesFile {} {
	global objects curr_zone
	set zones_path "$objects/zones.txt"
	if { [file exists $zones_path] } {
		updateZonesMenu $zones_path
		if {[string length $curr_zone] > 0} {
			selectZone $zones_path $curr_zone
		}
	} else {
		error "Zones file $zones_path not found."
	}
}

set f .departure

# Zones menu from the zones file:
set curr_zone ""
set g $f.zones
pack [frame $g] -anchor nw
pack [label $g.l -text "Zone:"] -side left
set zones_menu [tk_optionMenu $g.zones_menu curr_zone ???]
pack $g.zones_menu -side left

# Runways from the selected zone:
set g $f.runways_listbox
pack [frame $g] -expand 1 -fill both -anchor nw -pady $pady
pack [label $g.l -text "Runway:"] -anchor nw
pack [listbox $g.lb -height 6 -yscrollcommand [list $g.sb set] -font $font_mono] -expand 1 -fill both -side left
pack [scrollbar $g.sb -orient vertical -command [list $g.lb yview] ] -fill y -side left
set runways_listbox $g.lb
# Invoke selectRunway on selection from this listbox:
bind $runways_listbox <<ListboxSelect>> selectRunway
readZonesFile

# Departure the user may choose from the widgets above or edit by hand:
StdEntry $f.dept_descr "Description:" dept_descr 30 ""
StdEntry $f.latitude "Latitude:" latitude 20 ""
StdEntry $f.longitude "Longitude:" longitude 20 ""
StdEntry $f.altitude "Altitude:" altitude 8 "ft"
StdEntry $f.heading "Magnetic Heading:" heading 4 "DEG"
StdEntry $f.airspeed "True Air Speed (TAS):" airspeed 4 "kt"
pack $f.dept_descr $f.latitude $f.longitude $f.altitude $f.heading $f.airspeed \
	-anchor nw

set g $f.dept_time
pack [frame $g] -anchor nw
pack [label $g.l -text "Time:" -font $font_norm] -side left
set dept_time_entry $g.t
pack [entry $dept_time_entry -textvariable dept_time -width 30 -font $font_mono] -side left
# If using "now time", this proc disables the time entry box.
proc onUseNowCheck {} {
	global dept_time_entry dept_time_usenow
	if { $dept_time_usenow } {
		$dept_time_entry configure -state disable
	} else {
		$dept_time_entry configure -state normal
	}
}
pack [checkbutton $g.usenow -text "now" -variable dept_time_usenow \
	-font $font_norm -command onUseNowCheck ] -side left
onUseNowCheck


#
# "Environment" panel:
#

set f .environment.environment
pack [frame $f] -anchor nw

# Terrain rendering mode:
set g $f.ground
pack [frame $g] -anchor nw
pack [label $g.l -text "Terrain: " -font $font_norm] -side left
pack [radiobutton $g.flat -text "flat" \
	-variable ground_mode -value flat -font $font_norm] -side left
pack [radiobutton $g.tiled -text "tiled" \
	-variable ground_mode -value tiled -font $font_norm] -side left

set slider_len 10

# Visibility:
scale $f.visibility -from 0 -to 20 -length 300 -variable visibility \
	-orient horizontal -label "Visibility (NM):" \
	-tickinterval 5 -showvalue false -sliderlength $slider_len \
	-font $font_norm -width 10
pack $f.visibility

# Clouds layer:
set g $f.clouds
pack [frame $g] -anchor nw
pack [label $g.l1 -text "Clouds base: " -font $font_norm] -side left
pack [entry $g.base -textvariable clouds_base -background white -width 6] -side left
pack [label $g.l2, -text "ft,   top: " -font $font_norm] -side left
pack [entry $g.top -textvariable clouds_top -background white -width 6] -side left
pack [label $g.l3 -text "ft" -font $font_norm] -side left
pack [label $f.clouds_hint -text "(set both to zero for no clouds)" -font $font_norm] -anchor nw

# Wind:
scale $f.wind_d -from 0 -to 360 -length 300 -variable wind_d \
	-orient horizontal -label "Wind direction (DEG):" \
	-tickinterval 45 -showvalue false -sliderlength $slider_len \
	-font $font_norm -width 10
pack $f.wind_d

scale $f.wind_v -from 0 -to 50 -length 300 -variable wind_v \
	-orient horizontal -label "Wind velocity (kt):" \
	-tickinterval 10 -showvalue false -sliderlength $slider_len \
	-font $font_norm -width 10
pack $f.wind_v

scale $f.gust -from 0 -to 20 -length 300 -variable gust \
	-orient horizontal -label "Wind gust max intensity (ft/s):" \
	-tickinterval 5 -showvalue false -sliderlength $slider_len \
	-font $font_norm -width 10
pack $f.gust


#
# "Drone" panel
#

set g .drones.drone_mode
frame $g -pady 20
label $g.l -text "Mode:" -font $font_norm -width 20 -anchor w
tk_optionMenu $g.v drone_mode "dog fight" "hunting"
pack $g.l $g.v -side left
pack $g -anchor nw

set g .drones.drone_aggressiveness
frame $g -pady 20
label $g.note -text "Max allowed vertical load over structural limits:"
label $g.l -text "Aggressiveness:" -font $font_norm -width 20 -anchor w
tk_optionMenu $g.v drone_aggressiveness 30 40 50 60 70 80 90
label $g.u -text "%" -font $font_em
pack $g.note -anchor nw
pack $g.l $g.v $g.u -side left
pack $g -anchor nw


#
# "Configure" panel
#

set f .configure.program
frame $f -pady $pady
pack $f -side top -anchor nw
label $f.l -text "Program:" -font $font_norm
pack $f.l -side left -anchor nw
entry $f.v -textvariable program -background white -width 30
pack $f.v -side left
button $f.program_sel -text "..." -command {
	global program
	set new $program
	set new [tk_getOpenFile \
		-initialfile $program \
		-initialdir [file dirname "$program"] \
		-parent . \
		-filetypes {
			{{ACM Program} {.exe}}
			{{All Files} *} \
		} \
		-title "Select the ACM program" ]
	if { [string length $new] > 0 } {
		set program $new
		updatePlanesMenu
	}
}
pack $f.program_sel -side right

set f .configure.objects
frame $f -pady $pady
pack $f -side top -anchor nw
label $f.l -text "Objects directory:" -font $font_norm
pack $f.l -side left -anchor nw
entry $f.v -textvariable objects -background white -width 30
pack $f.v -side left
button $f.objects_sel -text "..." -command {
	global program
	set new $program
	set new [tk_chooseDirectory \
		-initialdir $objects \
		-parent . \
		-title "Select the ACM objects directory" ]
	if { [string length $new] > 0 } {
		set objects $new
		readZonesFile
	}
}
pack $f.objects_sel -side top -anchor nw

set f .configure

StdEntry $f.framerate "Frame rate:" framerate 3 "Hz"
pack $f.framerate -side top -anchor nw

set g $f.mouse_mode
frame $g
label $g.l -text "Mouse mode:" -font $font_norm -width 20 -anchor w
tk_optionMenu $g.v mouse_mode fast normal precise
pack $g.l $g.v -side left
pack $g -anchor nw

#StdEntry $f.joystick "Joystick serial port:" joystick 15 ""
#pack $f.joystick -anchor nw

set g $f.geometry_size
frame $g -pady $pady
label $g.l -text "Window size and position:" -font $font_norm -anchor w
pack $g.l -anchor nw

entry $g.geometry_w -textvariable geometry_w -background white -width 4
pack $g.geometry_w -side left

label $g.x -text "x" -font $font_norm
pack $g.x -side left

entry $g.geometry_h -textvariable geometry_h -background white -width 4
pack $g.geometry_h -side left

label $g.p1 -text "+" -font $font_norm
pack $g.p1 -side left

entry $g.geometry_x -textvariable geometry_x -background white -width 4
pack $g.geometry_x -side left

label $g.p2 -text "+" -font $font_norm
pack $g.p2 -side left

entry $g.geometry_y -textvariable geometry_y -background white -width 4
pack $g.geometry_y -side left

pack $g -anchor nw


checkbutton $f.sound -text "Enable sound effects" -variable sound -font $font_norm
pack $f.sound -anchor nw

checkbutton $f.debug -text "Debug through GDB" -variable debug -font $font_norm
pack $f.debug -anchor nw


#
# "DIS" panel
#

set f ".dis"

# Set parameters panel visibility.
proc setDisParamsVisibility {} {
	global dis_protocol
	if { $dis_protocol } {
		pack .dis.panel -anchor nw
	} else {
		pack forget .dis.panel
	}
}

checkbutton $f.enable -text "DIS protocol" \
	-variable dis_protocol \
	-command setDisParamsVisibility \
	-font $font_norm
pack $f.enable -anchor nw

# Create the DIS parameters panel:
set f $f.panel
pack [frame $f] -anchor nw
setDisParamsVisibility

set g $f.force
pack [frame $g] -anchor nw
pack [label $g.l -text "Force:" -font $font_norm] -side left
tk_optionMenu $g.v dis_force Other Friendly Opposing Neutral
pack $g.v -side left

label $f.relay_l -text "Empty relay server for broadcast:" -font $font_norm
pack $f.relay_l -anchor nw
StdEntry $f.relay_name "Relay server:" dis_relay_name 25 ""
pack $f.relay_name -anchor nw
label $f.relay_l1 -text "Shared broadcast port or relay port:" -font $font_norm
pack $f.relay_l1 -anchor nw
StdEntry $f.relay_port "Port:" dis_relay_port 5 "   (3000 std.)"
pack $f.relay_port -anchor nw

frame $f.vspace2 -height 10
pack $f.vspace2 -anchor nw
label $f.relay_l3 -text "DIS connection parameters. Site ID and appl. ID" \
	-font $font_norm
label $f.relay_l4 -text "should be set to -1 for automatic assignment." \
	-font $font_norm
pack $f.relay_l3 $f.relay_l4 -anchor nw
StdEntry $f.exercise "Exercise ID:" dis_exercise 3 "   \[0,255\]"
pack $f.exercise -anchor nw
StdEntry $f.site "Site ID:" dis_site 5 "   \[-1,65535\]"
pack $f.site -anchor nw
StdEntry $f.appl "Application ID:" dis_appl 5 "   \[-1,65535\]"
pack $f.appl -anchor nw

#
# Buttons:
#
set padx 10
set pady 10

set f ".buttons"

StdButt $f.run Run RunACM
pack $f.run -padx $padx -pady $pady -side right

StdButt $f.default Default SetDefault
pack $f.default -padx $padx -pady $pady -side right

pack [frame $f.space -width 30] -side right

StdButt $f.quit Quit { Save; exit }
pack $f.quit -padx $padx -pady $pady -side left

$f.run configure -default active
bind . <Return> "Press $f.run"
bind . <KP_Enter> "Press $f.run"
bind . <Escape> "Press $f.quit"

GuessObjectsDir
updatePlanesMenu
show_panel plane

# THE END!
