#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
#  Copyright (c) 2004 Miguel Filgueiras (mig@ncc.up.pt) / Universidade do Porto
#
#    This program is free software; you can redistribute it and/or modify
#      it under the terms of the GNU General Public License as published by
#      the Free Software Foundation; either version 2 of the License, or
#      (at your option) any later version.
#
#      This program is distributed in the hope that it will be useful,
#      but WITHOUT ANY WARRANTY; without even the implied warranty of
#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#      GNU General Public License for more details.
#
#      You should have received a copy of the GNU General Public License
#      along with this program.
#
#  File: command.tcl
#  Last change:  15 July 2004
#

# this file is only source-ed in command-line mode (even if the graphical
#  interface is launched to obey the command)

### change some initial values if needs be

if { $COMMAND(rec) } {
    # use an "accept anything from receiver" setting
    RecCanSendAnything $MYGPS
}

if { $COMMAND(log) } {
    set NotLogging 0
}

##### executing command

proc ExecCommand {} {
    # execute command from command line
    # return 0 on success, 1 on error, "wait" to loop, "-0" to quit fast
    global COMMAND CMDLINE NoImgLib GSHPVersion RealTimeLogAnim PVTState MESS

    switch $COMMAND(name) {
	checkconn {
	    # "is connected" command
	    return [ExecGPSConnectFail]
	}
	show {
	    CmdShow $COMMAND(what)
	    return "-0"
	}
	haslib {
	    switch $COMMAND(what) {
		gpsmanshp {
		    if { $GSHPVersion == "" } { return 1 }
		}
		Img {
		    if { $NoImgLib } { return 1 }
		}
		default { return 1 }
	    }
	}
	getwrite {
	    if { [CmdGetFails $COMMAND(rectypes)] || \
		    [CmdWriteFails $COMMAND(format) $COMMAND(path) \
			 $COMMAND(out,params) $COMMAND(filetypes)] } {
		return 1
	    }
	}
	readput {
	    if { [CmdReadFails $COMMAND(format) $COMMAND(path) \
		               $COMMAND(in,params) $COMMAND(filetypes)] || \
		    [CmdPutFails $COMMAND(rectypes)] } {
		return 1
	    }
	}
	translate {
	    if { [CmdReadFails $COMMAND(infmt) $COMMAND(inpath) \
		               $COMMAND(in,params) $COMMAND(intypes)] || \
		    [CmdWriteFails $COMMAND(format) $COMMAND(path) \
			          $COMMAND(out,params) $COMMAND(filetypes)] } {
		return 1
	    }
	}
	getrtimelog {
	    if { [CmdCantStartRTimeLog Ignore Ignore] || \
		     [CmdRTimeDumpFails $COMMAND(path)] } { return 1 }
	    set PVTState on
	    return wait
	}
	starttravel {
	    # enter graphical mode
	    set CMDLINE 0
	    GMInit
	    if { ! [CmdCantStartRTimeLog EnableGPS \
			[list GMMessage $MESS(receiver)]] } {
		set RealTimeLogAnim 1
		ToTravel
	    }
	    return wait
	}
	project {
	    if { [CmdBadProjArgs] || [CmdBadProjSetup] } { return 1 }
	    set proj $COMMAND(proj)
	    foreach "x y" [Proj${proj}Point MPData \
		    $COMMAND(latd) $COMMAND(longd) $COMMAND(ptsdatum)] {}
	    puts "$x\t$y"
	    return "-0"
	}
	georef {
	    if { [CmdBadGeoRefArgs] || [CmdBadGeoRefSetup] } { return 1 }
	    CmdGenerateGeoRef
	    return "-0"
	}
	source {
	    if { [catch {source $COMMAND(path)}] } { return 1 }
	    return wait
	}
	exec {
	    if { [catch {set f [open $COMMAND(path) r]}] } { return 1 }
	    set COMMAND(script) 1
	    return [CmdInterpret $f]
	}
	read {
	    parray COMMAND
	    return [CmdReadFails $COMMAND(format) $COMMAND(path) \
		                 $COMMAND(in,params) $COMMAND(filetypes)]
	}
	write {
	    return [CmdWriteFails $COMMAND(format) $COMMAND(path) \
		                  $COMMAND(out,params) $COMMAND(filetypes)]
	}
	get {
	    return [CmdGetFails $COMMAND(rectypes)]
	}
	put {
	    return [CmdPutFails $COMMAND(rectypes)]
	}
    }
    return 0
}

proc CmdProcessLine {line} {
    # split $line into command name and arguments taking care of quotes
    # return list similar to argv or ERROR on error

    set line [string trim $line]
    regsub -all "\t" $line " " line
    while { [regsub -all "  " $line " " line] } { continue }
    set l ""
    while { [string first "\"" $line] != -1 } {
	if { ! [regexp {^([^"]*)"([^"]*)"(.*)$} $line m pre in rest] } {
	    return ERROR
	}
	if { $pre != "" } {
	    set l [concat $l [split [string trim $pre] " "]]
	}
	lappend l $in
	set line [string trim $rest]
    }
    if { $line != "" } { set l [concat $l [split $line " "]] }
    return $l
}

proc CmdInterpret {file} {
    # interpret commands from $file
    # return 1 on error, 0 on success
    global argv

    set prev ""
    while { ! [eof $file] } {
	gets $file line
	set line [string trim $line " \t"]
	# discards comment lines whose first non-blank is "#"
	if { $line == "" || [regexp {^#} $line] } { continue }
	# check continuation line indicator (backslash as last char)
	#  if there is a unmatched quote this will cause error!
	if { [string index $line end] == "\\" } {
	    set prev "$prev$line "
	    continue
	}
	set line [string trim "$prev$line" " \t"]
	set argv [CmdProcessLine $line]
	if { [BadCommandLine] } {
	    puts stderr "bad command: $line"
	    return 1
	}
	if { [ExecCommand] == 1 } {
	    puts stderr "command failed: $line"
	    return 1
	}
	set prev ""
    }
    # continuation line not found
    if { $prev != "" } { return 1 }
    return 0
}

proc ExecGPSConnectFail {args} {
    #  $args is list with 1st and 2nd argument to proc GPSConnection
    # return 1 on error, 0 on success
    global GPSOpResult

    if { $args == 0 } {
       if { [GPSConnection] == 0 } { return 1 }
    } elseif { [GPSConnection [lindex $args 0] [lindex $args 1]] == 0 } {
	return 1
    }
    vwait GPSOpResult
    return $GPSOpResult
}

proc CmdCantStartRTimeLog {connproc errorproc} {
    # return 0 on success, 1 on error
    global COMMAND RECPROTS GPSREC GPSProtocol

    if { [set prot $COMMAND(prot)] == "" } {
	set prot $GPSProtocol
    } else {
	set GPSProtocol $prot
    }
    # this depends on the Garmin variant...
    if { $prot == "garmin" && \
	     [ExecGPSConnectFail $connproc $errorproc] } { return 1 }
    if { ! [StartRealTimeLog $COMMAND(interv)] } { return 1 }
    return 0
}

proc CmdGetFails {types} {
    # get items of given $types
    # return 0 on success
    global GPSOpResult

    if { [ExecGPSConnectFail] } { return 1 }
    foreach t $types {
	GetGPS $t
	vwait GPSOpResult
	if { $GPSOpResult } { return 1 }
    }
    return 0
}

proc CmdPutFails {types} {
    # put items of given $types
    # return 0 on success
    global GPSOpResult Storage

    if { [ExecGPSConnectFail] } { return 1 }
    foreach wh $types {
	set ids [lindex $Storage($wh) 0]
	global $ids

	PutGPS $wh [array names $ids]
	vwait GPSOpResult
	if { $GPSOpResult } { return 1 }
    }
    return 0
}

proc CmdOpenFile {path mode} {
    # open file
    #   $mode in {in, out, app}
    # if $path in {stdin, stdout} for suitable $mode return $path
    #  otherwise return file descriptor or -1 on error

    if { $mode == "in" } {
	if { $path == "stdin" } { return stdin }
	set mode r
    } elseif { $path == "stdout" } {
	set mode w
	return stdout
    } elseif { $mode == "out" } {
	set mode w
    } else { set mode a }
    if { [catch {set f [open $path $mode]}] } { return -1 }
    return $f
}

proc CmdReadFails {fmt path pars types} {
    # read file in format $fmt
    # if  $path==stdin  use standard input unless $fmt is Shapefile in which
    #  case return failure
    # return 0 on success
    global FILEFORMAT

    if { [CmdProcParamsFails $fmt $pars] } { return 1 }
    switch -glob $fmt {
	GPSMan {
	    if { [set f [CmdOpenFile $path in]] == -1 || \
		    [catch {set r [LoadFileFrom $f Data]}] || \
		    $r == 0 } { return 1 }
	}
	Shapefile {
	    if { $path == "stdin" } { return 1 }
	    # a single type per file
	    return [ImportShapefileFrom $path $types]
	}
	default {
	    if { $FILEFORMAT($fmt,filetype) != "data" } {
		# a single type per file
		set wh $types
	    } else { set wh Data }
	    if { $fmt == "GPStrans" } {
		set cr 1
	    } else { set cr 0 }
	    if { [set f [CmdOpenFile $path in]] == -1 } { return 1 }
	    InitInputFileVars $f $wh $cr 0
	    if { [catch {set r [ImportFileFrom $f $wh $fmt]}] || $r == 1 } {
		return 1
	    }
	}
    }
    return 0
}	    

proc CmdWriteFails {fmt path pars types} {
    # write data to file in format $fmt
    # if  $path==stdout  use standard output unless $fmt is Shapefile
    #  in which case return failure
    # return 0 on success
    global FILEFORMAT

    if { [CmdProcParamsFails $fmt $pars] } { return 1 }
    switch -glob $fmt {
	GPSMan {
	    if { [set f [CmdOpenFile $path out]] == -1 } { return 1 }
	    if { [llength $FILEFORMAT(GPSMan,types)] == [llength $types] } {
		if { [catch {SaveFileTo $f all Data}] } { return 1 }
	    } else {
		foreach t $types {
		    if { [catch {SaveFileTo $f all $t}] || \
			    [set f [CmdOpenFile $path app]] == -1 } {
			return 1
		    }
		}
		if { $path != "stdout" } { close $f }
	    }
	}
	Shapefile {
	    if { $path == "stdout" } { return 1 }
	    # a single type per file
	    return [ExportFileTo $path all $fmt $types]
	}
	default {
	    if { [set f [CmdOpenFile $path out]] == -1 } { return 1 }
	    if { $FILEFORMAT($fmt,filetype) != "data" } {
		# a single type per file
		if { [catch {set r [ExportFileTo $f all $fmt $types]}] || \
			$r == 1 } { return 1 }
	    } else {
		# more than 1 type per file
		# assume that file can be written in append mode a type at
		#  a time
		foreach t $types {
		    if { [catch {set r [ExportFileTo $f all $fmt $t]}] || \
			    $r == 1  || \
			    [set f [CmdOpenFile $path app]] == -1 } {
			return 1
		    }
		}
	    }
	    if { $path != "stdout" } { close $f }
	}
    }
    return 0
}

proc CmdProcParamsFails {fmt pars} {
    # process parameters: check the given/default values, convert them, and
    #  use them in defining global variables
    global FILEFORMAT MESS

    if { $pars == "" } { return 0 }
    if { [llength $pars] & 1 == 1 } { BUG Bad parameters list }
    set gvcsvl ""
    foreach "p val" $pars {
	foreach "gvar conversion constraint" $FILEFORMAT($fmt,param,$p) {
	    break
	}
	if { [catch {eval $conversion}] } {
	    GMMessage [format $MESS(badconv) $p $val]
	    return 1
	}
	lappend gvcsvl $p $gvar $constraint $val
    }
    foreach "p gvar constraint val" $gvcsvl {
	if { [catch {set r [eval $constraint]}] || $r == 0 } {
	    GMMessage [format $MESS(badprmval) $p $val]
	    return 1
	}
	global $gvar
	set $gvar $val
    }
    return 0
}

proc CmdRTimeDumpFails {path} {
    # start dumping real-time log continuously
    # return 0 on success
    global PVTDFile PVTDumping

    if { [set PVTDFile [CmdOpenFile $path out]] == -1 } { return 1 }
    set PVTDumping 1
    # SavePVTData $PVTDFile
    return 0
}

proc CmdShow {what} {
    # print information to stdout
    global VERSION FILEFORMAT RECPROTS TXT MESS MAPKNOWNPROJS MAPPROJDATA \
	    MAPPROJDTYPE MAPPARTPDATA MAPKNOWNTRANSFS GDATUM COMMAND

    switch $what {
	help { puts $COMMAND(USAGE) }
	version { puts $VERSION }
	formats {
	    array set mode [list \
		    in  $TXT(fread) \
		    out $TXT(fwrite) \
		    {in out} $TXT(fread)/$TXT(fwrite)]
	    foreach p [lsort -dictionary [array names FILEFORMAT *,mode]] {
		regsub {,mode$} $p "" f
		if { ! [catch {set dep $FILEFORMAT($f,depends)}] && \
			! [eval $dep] } {
		    puts "$f:\t$MESS(shpwrongfile)"
		    continue
		}
		set m $mode($FILEFORMAT($p))
		puts -nonewline "$f:\t$m, $FILEFORMAT($f,types)"
		if { [set t $FILEFORMAT($f,filetype)] == "data" } {
		    puts ", $TXT(nameData)"
		} else { puts "" }
		set t 1
		foreach how "in out" {
		    if { ! [catch {set params $FILEFORMAT($f,$how,params)}] } {
			if { $t } {
			    puts " $TXT(params)"
			    set t 0
			}
			puts "\t$mode($how):"
			if { [catch {set pds $FILEFORMAT($f,$how,pdefts)}] } {
			    set pds ""
			}
			foreach p $params dv $pds {
			    if { [regexp {^global:(.+)$} $dv m dvv] } {
				global $dvv
				puts "\t\t$p\t[set $dvv]"
			    } else { puts "\t\t$p\t$dv" }
			}
		    }
		}
	    }
	}
	projections {
	    set pp [array names MAPPARTPDATA]
	    foreach p [lsort -dictionary $MAPKNOWNPROJS] {
		if { $p == "Schreiber" } {
		    puts "$p:\t$TXT(PRJ$p)"
		} else {
		    puts "$p:\t$TXT(PRJ$p)\n  $TXT(params)"
		    puts "\t?datum"
		    if { [lsearch -exact $pp $p] == -1 } {
			if { $p == "UTM" } {
			    puts "\t?UTMzone"
			    continue
			}
			foreach pm $MAPPROJDATA($p) {
			    if { [set t $MAPPROJDTYPE($pm)] != "reserved" } {
				regsub {^(list=)|(list:)} $t "" t
				puts "\t$pm\t($t)\t$TXT($pm)"
			    }
			}
		    }
		}
	    }
	}
	transfs {
	    foreach t [lsort -dictionary $MAPKNOWNTRANSFS] {
		puts "$t:\t$TXT(TRNSF$t)"
	    }
	}
	datums {
	    foreach d [lsort -dictionary [array names GDATUM]] {
		if { [set cmt [lindex $GDATUM($d) 5]] != "" } {
		    puts "$d\t:$cmt"
		} else { puts $d }
	    }
	}
	protocols {
	    foreach p [lsort -dictionary [array names RECPROTS *,*]] {
		regexp {^(.+),(.+)$} $p x rec prot
		puts "$prot ($rec)"
	    }
	}
    }
    return
}

proc CmdBadProjArgs {} {
    # check all the arguments of the project command
    # return 0 on success, 1 on error
    global COMMAND MAPKNOWNPROJS MAPPROJDATA MAPPROJDTYPE MAPPARTPROJ \
	    MAPPARTPDATA MPData

    foreach a "latd longd ptsdatum proj params" {
	set $a $COMMAND($a)
    }
    if { ! [CheckCoord GMMessage DDD $latd N 90] || \
	    ! [CheckCoord GMMessage DDD $longd E 180] || \
	    [EllipsdOf $ptsdatum] == "" || \
	    [lsearch -exact $MAPKNOWNPROJS $proj] == -1 } {
	return 1
    }
    set missdat 1
    if { [set pars $COMMAND(params)] != "" } {
	if { [llength $pars] & 1 == 1 } { BUG Bad parameters list }
	foreach "p val" $pars {
	    if { $p == "datum" } {
		if { [EllipsdOf $val] == "" } { return 1 }
		set MPData(datum) $val
		set missdat 0
	    } else {
		if { [catch {set type MAPPROJDTYPE($p)}] || \
			$type == "reserved" } { return 1 }
		set CMD_$p $val
	    }
	}
    }
    if { $missdat } { set MPData(datum) $ptsdatum }
    # UTM has an optional parameter
    if { $proj == "UTM" && [catch {set CMD_UTMzone}] } { return 0 }
    if { [catch {set mp $MAPPARTPROJ($proj)}] } {
	foreach p $MAPPROJDATA($proj) {
	    if { [set type $MAPPROJDTYPE($p)] != "reserved" } {
		if { [catch {set val [set CMD_$p]}] || \
			[BadParam "" $type $val] } {
		    return 1
		}
		set MPData($p) $val
	    }
	}
    } else {
	foreach e $MAPPROJDATA($mp) v $MAPPARTPDATA($proj) {
	    set MPData($e) $v
	}
	set COMMAND(proj) $mp
    }
    return 0
}

proc CmdBadProjSetup {} {
    # initialize projection
    # return 0 on success, 1 on error
    global COMMAND

    foreach a "latd longd ptsdatum proj" {
	set $a $COMMAND($a)
    }
    if { [BadProjSetup MPData $proj $latd $longd $ptsdatum] } { return 1 }
    return 0
}

proc CmdBadGeoRefArgs {} {
    # check the arguments of the georef command and prepare data
    # return 0 on success, 1 on error
    global COMMAND MapLoadPos

    foreach a "transf ptsdatum proj params" {
	set $a $COMMAND($a)
    }
    # used by proc CmdBadProjArgs
    set COMMAND(latd) $COMMAND(0,lat)
    set COMMAND(longd) $COMMAND(0,long)
    if { [CmdBadProjArgs] } { return 1 }
    set npts $COMMAND(npts)
    for { set p 0 } { $p < $npts } { incr p } {
	foreach pp "lat long x y" {
	    set $pp $COMMAND($p,$pp)
	}
        if { ! [CheckCoord GMMessage DDD $lat N 90] || \
		! [CheckCoord GMMessage DDD $long E 180] || \
		! [CheckFloat GMMessage $x] || ! [CheckFloat GMMessage $y] } {
	    return 1
	}
	set MapLoadPos($p,x) $x ; set MapLoadPos($p,y) $y
    }
    return 0
}

proc CmdProjCoords {npts} {
    # return list of projected coordinates for points given in command line
    #  $npts is number of points
    # assumes that the following elements are defined in array COMMAND
    #   ptsdatum
    #   $n,lat   for $n in 0..$npts-1
    #   $n,long
    #   proj     projection
    # assumes that the projection parameters were initialized in array MPData
    global COMMAND

    set ptsdatum $COMMAND(ptsdatum) ; set proj $COMMAND(proj)
    set tcs ""
    for { set p 0 } { $p < $npts } { incr p } {
	lappend tcs [Proj${proj}Point MPData \
		$COMMAND($p,lat) $COMMAND($p,long) $ptsdatum]
    }
    return $tcs
}

proc CmdBadGeoRefSetup {} {
    # initialize projection and transformation
    # return 0 on success, 1 on error
    global COMMAND

    if { [CmdBadProjSetup] } { return 1 }
    MapProjectionIs $COMMAND(proj)
    if { [set transf $COMMAND(transf)] == "NoRot" } {
	set proc InitNoRotTransf
    } else { set proc MapInit${transf}Transf }
    if { ! [$proc] } { return 1 }
    return 0
}

proc CmdGenerateGeoRef {} {
    # output geo-ref information file
    global COMMAND

    SaveMapBack $COMMAND(image) stdout
    return
}

##### logo window

proc ShowGPSMan {} {
    # create widgets for displaying information about GPSMan in root window
    global SmallLogo VERSION COLOUR EPOSX EPOSY TXT

    wm protocol . WM_DELETE_WINDOW { exit 1 }
    frame .fr -relief flat -borderwidth 5 -bg $COLOUR(messbg)
    if { $SmallLogo != "" } {
	label .fr.im -image $SmallLogo -relief sunken
	pack .fr.im
    }
    label .fr.title -text "$TXT(GMtit) $VERSION" -relief sunken
    bind . <Button-1> About

    pack .fr.title -side top -pady 5
    pack .fr -side top
    update idletasks
    return
}




