#
# This file is part of:
#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
# Copyright (c) 1998-2011 Miguel Filgueiras migf@portugalmail.pt
#
#    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 3 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_parse.tcl
#  Last change:  19 July 2011
#

# this file is only source-ed in command-line mode

##### parsing command line
###    after source-ing options file
###    before source-ing main.tcl and other files!

##### parse command line

set COMMAND(USAGE) {Usage: gpsman [PREFIXES] COMMAND [ARGS]
  PREFIXES
    -dev DEVICE   device for serial port
    -log          produce a log file of serial communication
    -rec Garmin | Lowrance | Magellan     brand of receiver (only Garmin works)
    -prefs PATH   path to preferences file
    -prot Garmin | Garmin_USB | NMEA | SText | simul   receiver protocol
  COMMANDS [ARGS]
    is available | connected
    show help | version | formats | protocols | projections | datums |
                transfs | symbols
    haslib gpsmanshp | Img | TclCurl
    getwrite IN-TYPES FORMAT [OUT-PARAMS] [OUT-TYPES] PATH
    readput FORMAT [IN-PARAMS] [IN-TYPES] PATH [OUT-TYPES]  (-rec not allowed!)
    translate IN-FORMAT [IN-PARAMS] [IN-TYPES] IN-PATH [HOW-PARAMS] \
	 OUT-FORMAT [OUT-PARAMS] [OUT-TYPES] OUT-PATH
           where HOW-PARAMS can be
            itoffset=DHOUR: time offset to use in input file in GPSMan format,
              defaults to time offset given in file
            otoffset=DHOUR: time offset to use in output file in GPSMan format
              defaults to time offset selected in the options
    getrtimelog PATH [INTERVAL]
    getfix PATH [TIMEOUT]
    getalmanac PATH
    project LATD LONGD DATUM PROJECTION [PARAMS]
            (no PREFIXES; DATUM used for projection if PARAMS do not give one)
    georef IMGPATH TRANSF LATD LONGD LATD LONGD [LATD LONGD] DATUM \
           X Y X Y [X Y] PROJECTION [PARAMS]
            (no PREFIXES; DATUM used for projection if PARAMS do not give one)
    geopicts IN-FORMAT [IN-PARAMS] IN-TYPE IN-PATH [PROC-PARAMS] \
	 [OUT-FORMAT] [OUT-PARAMS] OUT-PATH PICT-PATHS
	   where
	    IN-TYPE: TR | GR (for WPs in GR)
            PROC-PARAMS: [id=ID] [date=DMODE] [dh=DHOUR]
             ID: identifier of item in data file, default is last read
             DMODE: pict|exif|fmod   date from picture EXIF (default), EXIF
               file, or file last modified time
             DHOUR: difference in hours to apply to file dates to get TR dates
	    OUT-FORMAT: GPX
	    OUT-PARAMS may include: datum=DATUM sy=SYMBOL prefix=STRING
               STRING: is prefix for WP names, default is P
    source PATH
    read FORMAT [PARAMS] [TYPES] PATH    if not in script launches interface
    exec PATH
       for use in scripts only:
           write FORMAT [PARAMS] [TYPES] PATH
           get TYPES
           put TYPES
    start travel (only with Garmin receiver)
}

set COMMAND(script) 0

proc BadCommandLine {} {
    # parse command line
    # this must be called before source-ing main.tcl and other files!
    # result will be stored on COMMAND array
    # changes in commands accepted must be reflected on $COMMAND(USAGE) above
    # return 1 on error
    global argv COMMAND SERIALPORT GPSREC MAPKNOWNTRANSFS MAPTRANSFNPTS

    if { ! $COMMAND(script) } {
	array set COMMAND {
	    prefsfile ""
	    rec 0
	    log 0
	    prot ""
	    command 1
	}
    }
    set pargs 0
    while 1 {
	set opt [lindex $argv 0]
	switch -- $opt {
	    -dev {
		set pos 1
		set SERIALPORT [lindex $argv 1]
		set argv [lreplace $argv 0 1]
	    }
	    -log {
		set pos 2
		set COMMAND(log) 1
		set argv [lreplace $argv 0 0]
	    }
	    -rec {
		# not to be used with either  readput  or  -prefs
		#  or after  -prot
		if { $COMMAND(prefsfile) != "" || \
			$COMMAND(prot) != "" } {
		    puts stderr "Cannot use -rec with readput, -prefs or -prot"
		    return 1
		}
		set pos 4
		set GPSREC [lindex $argv 1]
		set argv [lreplace $argv 0 1]
		if { [lsearch {Garmin Lowrance Magellan} $GPSREC] == -1 } {
		    puts stderr "Bad receiver brand $GPSREC"
		    return 1
		}
		set COMMAND(rec) 1
	    }
	    -prefs {
		# not to be used with  -rec
		if { $COMMAND(rec) } {
		    puts stderr "Cannot use -prefs with -rec"
		    return 1
		}
		set pos 8
		set COMMAND(prefsfile) [lindex $argv 1]
		set argv [lreplace $argv 0 1]
	    }
	    -prot {
		set pos 16
		set COMMAND(prot) [CmdParseProt [lindex $argv 1]]
		if { $COMMAND(prot) == "" } { return 1 }
		set argv [lreplace $argv 0 1]
	    }
	    default {
		break
	    }
	}
	# no repeated options
	if { $pargs & $pos } {
	    puts stderr "Repeated option $opt"
	    return 1
	}
	incr pargs $pos
    }

    set cmd [lindex $argv 0]
    if { $pargs != 0 && \
	    ( $COMMAND(script) || \
		  [lsearch -exact "project georef" $cmd] != -1 ) } {
	puts stderr "No prefix options allowed in this command"
	return 1
    }
    if { $cmd == "readput" && $COMMAND(rec) } {
	puts stderr "Option -rec cannot be used with readput"
	return 1
    }
    set argv [lreplace $argv 0 0]
    set nargs [llength $argv]
    switch -- $cmd {
	is {
	    set what [lindex $argv 0]
	    switch -- $what {
		available {
		    set COMMAND(name) available
		    return "-0"
		}
		connected {
		    # for the time being this is only supported for
		    #  Garmin receivers
		    if { $GPSREC != "Garmin" } {
			puts stderr "Not a Garmin receiver"
			return 1
		    }
		    set cmd checkconn
		}
		default {
		    puts stderr "Unknown argument $what; use \"show help\""
		    return 1
		}
	    }
	}
	show {
	    set showwhat \
	    {help version formats projections protocols datums transfs symbols}
	    set what [lindex $argv 0]
	    if { [lsearch $showwhat $what] == -1 } {
		puts stderr "Unknown argument $what; use \"show help\""
		return 1
	    }
	    set COMMAND(what) $what
	}
	haslib {
	    set what [lindex $argv 0]
	    if { [lsearch "gpsmanshp Img TclCurl" $what] == -1 } {
		puts stderr "Unknown argument $what; use \"show help\""
		return 1
	    }
	    set COMMAND(what) $what
	}
	getwrite {
	    # IN-TYPES FORMAT [PARAMS] [OUT-TYPES] PATH
	    # for the time being this is only supported for Garmin receivers
	    if { $GPSREC != "Garmin" } {
		puts stderr "Not a Garmin receiver"
		return 1
	    }
	    foreach "in_ts tail" [CmdParseTypes $argv] {}
	    if { [llength $tail] < 2 || \
		    [set in_ts [CmdRecTypes get $in_ts]] == "" } {
		puts stderr "Missing arguments"
		return 1
	    }
	    if { [set fmt [CmdFFormat [lindex $tail 0]]] == "" } { return 1 }
	    foreach "out_ps out_ts tail" \
		[CmdParseParamsTypes [lreplace $tail 0 0]] {}
	    if { $out_ts == "" } { set out_ts $in_ts }
	    if { [llength $tail] != 1 || [set COMMAND(path) $tail] == "" } {
		puts stderr "Bad output path: $tail"
		return 1
	    }
	    if { [CmdFileFmtParamsTypes out $fmt $out_ps $out_ts] == 0 || \
		     [CmdIncompatibleTypes $in_ts $COMMAND(filetypes)] } {
		return 1
	    }
	    set COMMAND(rectypes) $in_ts
	}
	readput {
	    # FORMAT [PARAMS] [IN-TYPES] PATH [OUT-TYPES]
	    # for the time being this is only supported for Garmin receivers
	    if { $GPSREC != "Garmin" } {
		puts stderr "Not a Garmin receiver"
		return 1
	    }
	    # no  -rec  option
	    if { $COMMAND(rec) } {
		puts stderr "Option -rec not allowed with readput"				return 1
	    }
	    if { [set fmt [CmdFFormat [lindex $argv 0]]] == "" } { return 1 }
	    foreach "in_ps in_ts tail" \
		[CmdParseParamsTypes [lreplace $argv 0 0]] {}
	    if { [set COMMAND(path) [lindex $tail 0]] == "" } {
		puts stderr "Missing output path"
		return 1
	    }
	    if { [CmdFileFmtParamsTypes in $fmt $in_ps $in_ts] == 0 } {
		return 1
	    }
	    foreach "out_ts tail" [CmdParseTypes [lreplace $tail 0 0]] {}
	    if { $out_ts == "" } { set out_ts $COMMAND(filetypes) }
	    if { $tail != "" } {
		puts stderr "Extra arguments after types"
		return 1
	    }
	    if { [set out_ts [CmdRecTypes put $out_ts]] == "" || \
		    [CmdIncompatibleTypes $COMMAND(filetypes) $out_ts] } {
		return 1
	    }
	    set COMMAND(rectypes) $out_ts
	}
	translate {
	    # IN-FORMAT [IN-PARAMS] [IN-TYPES] IN-PATH [HOW-PARAMS]
	    #  OUT-FORMAT [OUT-PARAMS] [OUT-TYPES] OUT-PATH
	    # see proc CmdBadTranslateHowParams for HOW-PARAMS
	    if { $nargs < 4 } {
		puts stderr "Missing arguments"
		return 1
	    }
	    if { [set infmt [CmdFFormat [lindex $argv 0]]] == "" } { return 1 }
	    foreach "in_ps in_ts tail" \
		[CmdParseParamsTypes [lreplace $argv 0 0]] {}
	    if { [set COMMAND(inpath) [lindex $tail 0]] == "" } {
		puts stderr "Missing input path"
		return 1
	    }
	    if { [CmdFileFmtParamsTypes in $infmt $in_ps $in_ts] == 0 } {
		return 1
	    }
	    set COMMAND(infmt) $COMMAND(format)
	    set tail [lreplace $tail 0 0]
	    foreach "params tail" [CmdParseParams $tail] { break }
	    if { [set outfmt [CmdFFormat [lindex $tail 0]]] == "" || \
		     [set params \
			  [CmdBadTranslateHowParams $params $infmt $outfmt]] \
		     == 0 } {
		return 1
	    }
	    set COMMAND(params) $params
	    foreach "out_ps out_ts tail" \
		[CmdParseParamsTypes [lreplace $tail 0 0]] {}
	    if { $out_ts == "" } { set out_ts $COMMAND(filetypes) }
	    set COMMAND(intypes) $COMMAND(filetypes)
	    if { [llength $tail] != 1 } {
		puts stderr "Extra/missing arguments after output types"
		return 1
	    }
	    if { [set COMMAND(path) $tail] == "" } {
		puts stderr "Missing output path"
		return 1
	    }
	    if { [CmdFileFmtParamsTypes out $outfmt $out_ps $out_ts] == 0 || \
		  [CmdIncompatibleTypes $COMMAND(intypes) \
		       $COMMAND(filetypes)] } {
	        return 1
	    }
	}
	getrtimelog {
	    # PATH [INTERVAL]
	    # for the time being this is only supported for Garmin receivers
	    if { $GPSREC != "Garmin" } {
		puts stderr "Not a Garmin receiver"
		return 1
	    }
	    if { [set COMMAND(path) [lindex $argv 0]] == "" } {
		puts stderr "Missing output path"
		return 1
	    }
	    if { $nargs > 2 || [set COMMAND(interv) \
		     [CmdParseInteger [lindex $argv 1] 2]] < 0 } {
		puts stderr "Expecting a number in seconds"
		return 1
	    }
	}
	getfix {
	    # PATH [TIMEOUT]
	    # for the time being this is only supported for Garmin receivers
	    if { $GPSREC != "Garmin" } {
		puts stderr "Not a Garmin receiver"
		return 1
	    }
	    if { [set COMMAND(path) [lindex $argv 0]] == "" } {
		puts stderr "Missing output path"
		return 1
	    }
	    if { $nargs > 2 || [set COMMAND(timeout) \
				 [CmdParseInteger [lindex $argv 1] 0]] < 0 } {
		puts stderr "Expecting a number in seconds"
		return 1
	    }
	}
	getalmanac {
	    # PATH
	    # for the time being this is only supported for Garmin receivers
	    if { $GPSREC != "Garmin" } {
		puts stderr "Not a Garmin receiver"
		return 1
	    }
	    if { $nargs != 1 } {
		puts stderr "Extra/missing arguments"
		return 1
	    }
	    set COMMAND(path) [lindex $argv 0]
	}
	start {
	    # travel [INTERVAL]
	    if { [lindex $argv 0] != "travel" } {
		puts stderr "Unknown argument [lindex $argv 0]"
		return 1
	    }
	    if { $GPSREC != "Garmin" } {
		puts stderr "Not a Garmin receiver"
		return 1
	    }
	    if { $nargs > 2 || [set COMMAND(interv) \
			  [CmdParseInteger [lindex $argv 1] 2]] < 0 } {
		puts stderr "Expecting a number in seconds"
		return 1
	    }
	    set cmd starttravel
	    set COMMAND(command) 0
	}
	project {
	    # LATD LONGD DATUM PROJECTION [PARAMS]
	    # making only a preliminary check on the correction of the
	    #  arguments as projections.tcl was not yet loaded
	    if { $nargs < 4 } {
		puts stderr "Missing arguments"
		return 1
	    }
	    foreach "latd longd" $argv { break }
	    foreach p "latd longd" {
		set x [set $p]
		if { ! [regexp {^-?[0-9]+(\.[0-9]+)?$} $x] } {
		    puts stderr "Bad coordinate $x"
		    return 1
		}
		set COMMAND($p) $x
	    }
	    set i 1
	    foreach p "ptsdatum proj" {
		if { [set COMMAND($p) [lindex $argv [incr i]]] == "" } {
		    puts stderr "Missing $p argument"
		    return 1
		}
	    }
	    foreach "params tail" \
		    [CmdParseParams [lrange $argv [incr i] end]] { break }
	    if { $tail != "" } {
		puts stderr "Extra arguments after attibute=value pairs"
		return 1
	    }
	    set COMMAND(params) $params
	}
	georef {
	    # IMGPATH TRANSF LATD LONGD LATD LONGD [LATD LONGD] DATUM
	    #   X Y X Y [X Y] PROJ [PARAMS]
	    # making only a preliminary check on the correction of the
	    #  arguments as projections.tcl is not yet loaded
	    set COMMAND(image) [lindex $argv 0]
	    set COMMAND(transf) [set tr [lindex $argv 1]]
	    if { $nargs < 12 } {
		puts stderr "Missing arguments"
		return 1
	    }
	    if { [lsearch -exact $MAPKNOWNTRANSFS $tr] == -1 } {
		puts stderr "Unknown transformation $tr"
		return 1
	    }
	    if { $tr == "NoRot" } {
		# not using 3 points as when doing this by hand
		set npts 2
	    } else { set npts $MAPTRANSFNPTS($tr) }
	    set COMMAND(npts) $npts
	    set i 1
	    for { set p 0 } { $p < $npts } { incr p } {
		foreach pp "lat long" {
		    set x [lindex $argv [incr i]]
		    if { ! [regexp {^-?[0-9]+(\.[0-9]+)?$} $x] } {
			puts stderr "Bad coordinate $x"
			return 1
		    }
		    set COMMAND($p,$pp) $x
		}
	    }
	    if { [set COMMAND(ptsdatum) [lindex $argv [incr i]]] == "" } {
		puts stderr "Missing ptsdatum argument"
		return 1
	    }
	    for { set p 0 } { $p < $npts } { incr p } {
		foreach pp "x y" {
		    set x [lindex $argv [incr i]]
		    if { ! [regexp {^-?[0-9]+(\.[0-9]+)?$} $x] } {
			puts stderr "Bad coordinate $x"
			return 1
		    }
		    set COMMAND($p,$pp) $x
		}
	    }
	    if { [set COMMAND(proj) [lindex $argv [incr i]]] == "" } {
		puts stderr "Missing projection"
		return 1
	    }
	    foreach "params tail" \
		    [CmdParseParams [lrange $argv [incr i] end]] { break }
	    if { $tail != "" } {
		puts stderr "Extra arguments after attibute=value pairs"
		return 1
	    }
	    set COMMAND(params) $params
	}
	geopicts {
	    # IN-FORMAT [IN-PARAMS] [IN-TYPE] IN-PATH [PROC-PARAMS] \
	    #   [OUT-FORMAT] [OUT-PARAMS] OUT-PATH PICT-PATHS
	    # see proc CmdBadGeoPictsParams for PROC-PARAMS and OUT-PARAMS
	    #     IN-TYPE: TR | GR (for WPs in GR)
	    #     OUT-FORMAT: GPX
	    if { $nargs < 4 } {
		puts stderr "Missing arguments"
		return 1
	    }
	    if { [set infmt [CmdFFormat [lindex $argv 0]]] == "" } { return 1 }
	    foreach "in_ps in_ts tail" \
		[CmdParseParamsTypes [lreplace $argv 0 0]] {}
	    if { [CmdFileFmtParamsTypes in $infmt $in_ps $in_ts TR GR] == 0 } {
		return 1
	    }
	    if { [set COMMAND(inpath) [lindex $tail 0]] == "" } {
		puts stderr "No input path"
		return 1
	    }
	    set COMMAND(infmt) $COMMAND(format)
	    set COMMAND(intypes) $COMMAND(filetypes)
	    set tail [lreplace $tail 0 0]
	    foreach "params tail" [CmdParseParams $tail] { break }
	    if { [set outfmt [CmdFFormat [lindex $tail 0]]] != "GPX" } {
		puts stderr "Output format for geopicts must be GPX"
		return 1
	    }
	    foreach "out_ps tail" [CmdParseParams [lreplace $tail 0 0]] {}
	    foreach "out_ps params" \
		     [CmdBadGeoPictsParams $params $out_ps] { break }
	    if { $out_ps == "error" || \
		     [CmdFileFmtParamsTypes out $outfmt $out_ps ""] == 0 } {
		return 1
	    }
	    if { [set COMMAND(path) [lindex $tail 0]] == "" } {
		puts stderr "No output path"
		return 1
	    }
	    if { [set picts [lreplace $tail 0 0]] == "" } {
		puts stderr "No picture files given"
		return 1
	    }
	    lappend params __picts $picts
	    set COMMAND(params) $params
	}
	source {
	    # PATH
	    if { $nargs != 1 } {
		puts stderr "Missing/extra arguments"
		return 1
	    }
	    set COMMAND(path) $argv
	}
	read {
	    # FORMAT [PARAMS] [TYPES] PATH
	    # if not in a script, lauches graphical interface after reading
	    if { [set fmt [CmdFFormat [lindex $argv 0]]] == "" } {
		puts stderr "Missing file format"
		return 1
	    }
	    foreach "in_ps in_ts tail" \
		[CmdParseParamsTypes [lreplace $argv 0 0]] {}
	    if { [set COMMAND(path) [lindex $tail 0]] == "" } {
		puts stderr "Missing input path"
		return 1
	    }
	    if { [CmdFileFmtParamsTypes in $fmt $in_ps $in_ts] == 0 } {
		return 1
	    }
	    if { ! $COMMAND(script) } {
		set COMMAND(command) 0
		# make path absolute to avoid problems with cd's
		set COMMAND(path) [file normalize $COMMAND(path)]
	    }
	}
	exec {
	    # PATH
	    if { $nargs != 1 } {
		puts stderr "Missing/extra arguments"
		return 1
	    }
	    set COMMAND(path) $argv
	}
	write {
	    # FORMAT [PARAMS] [TYPES] PATH
	    # for use in scripts only
	    if { ! $COMMAND(script) } {
		puts stderr "Command to be used only in a script"
		return 1
	    }
	    if { [set fmt [CmdFFormat [lindex $argv 0]]] == "" } { return 1 }
	    foreach "out_ps out_ts tail" \
		    [CmdParseParamsTypes [lreplace $argv 0 0]] {}
	    if { [llength $tail] != 1 } {
		puts stderr "Missing/extra arguments before path"
		return 1
	    }
	    if { [set COMMAND(path) $tail] == "" } {
		puts stderr "Missing output path"
		return 1
	    }
	    if { [CmdFileFmtParamsTypes out $fmt $out_ps $out_ts] == 0 } {
		return 1
	    }
	}
	get {
	    # TYPES
	    # for use in scripts only
	    # for the time being this is only supported for Garmin receivers
	    if { ! $COMMAND(script) } {
		puts stderr "Command to be used only in a script"
		return 1
	    }
	    if { $GPSREC != "Garmin" } {
		puts stderr "Not a Garmin receiver"
		return 1
	    }
	    foreach "in_ts tail" [CmdParseTypes $argv] {}
	    if { $tail != "" } {
		puts stderr "Extra arguments after types"
		return 1
	    }
	    set COMMAND(rectypes) $in_ts
	}
	put {
	    # TYPES
	    # for use in scripts only
	    # for the time being this is only supported for Garmin receivers
	    if { ! $COMMAND(script) } {
		puts stderr "Command to be used only in a script"
		return 1
	    }
	    if { $GPSREC != "Garmin" } {
		puts stderr "Not a Garmin receiver"
		return 1
	    }
	    # no  -rec  option
	    if { $COMMAND(rec) } {
		puts stderr "Option -rec not allowed with put"
		return 1
	    }
	    foreach "out_ts tail" [CmdParseTypes $argv] {}
	    if { $tail != "" } {
		puts stderr "Extra arguments after types"
		return 1
	    }
	    set COMMAND(rectypes) $out_ts
	}
	default {
	    puts stderr "Bad command name $cmd; use \"show help\""
	    return 1
	}
    }
    set COMMAND(name) $cmd
    return 0
}

proc CmdParseProt {prot} {
    # translate external form of protocol
    # return "" on error
    global RECPROTS GPSREC

    if { [catch {set p $RECPROTS($GPSREC,$prot)}] } {
	puts stderr "Bad receiver protocol $prot"
	return ""
    }
    return $p
}

proc CmdParseTypes {lst} {
    # find element(s) at the beginning of $lst corresponding to item types:
    #   either a single "all", or a subset of {WP, RT, TR, LN, LAP, GR}
    #   LAP is accepted only if $SUPPORTLAPS is set
    #   GR will be accepted later only if the format is "GPSMan"
    #   (note that $TYPES is not defined yet!)
    # return pair with list of types and rest of list

    set pstslst [CmdParseParamsTypes $lst]
    if { [lindex $pstslst 0] != "" } { return [list "" $lst] }
    return [lreplace $pstslst 0 0]
}

proc CmdParseParams {lst} {
    # find element(s) at the beginning of $lst corresponding to
    #  parameters in the form NAME=VALUE
    # return pair with list of parameters (with alternating lhs and rhs),
    #  and rest of list

    set ps ""
    while { [regexp {^(.+)=(.+)$} [lindex $lst 0] x lhs rhs] } {
	lappend ps $lhs $rhs
	set lst [lreplace $lst 0 0]
    }
    return [list $ps $lst]
}

proc CmdParseParamsTypes {lst} {
    # find element(s) at the beginning of $lst corresponding to
    #  parameters in the form NAME=VALUE, followed by item types:
    #  either a single "all", or a subset of {WP, RT, TR, LAP, LN, GR}
    #   LAP is accepted here only if $SUPPORTLAPS is set
    #   GR will be accepted later only if the format is "GPSMan"
    #  (note that $TYPES is not defined yet!)
    # return triple with list of parameters (with alternating lhs and rhs),
    #  list of types and rest of list
    global SUPPORTLAPS

    set ps ""
    while { [regexp {^(.+)=(.+)$} [lindex $lst 0] x lhs rhs] } {
	lappend ps $lhs $rhs
	set lst [lreplace $lst 0 0]
    }
    if { [lindex $lst 0] == "all" } {
	set ts all ; set lst [lreplace $lst 0 0]
    } else {
	set ts ""
	if { $SUPPORTLAPS } {
	    set ats "WP RT TR LN LAP GR"
	} else { set ats "WP RT TR LN GR" }
	while { [lsearch -exact $ats [lindex $lst 0]] != -1 } {
	    lappend ts [lindex $lst 0] ; set lst [lreplace $lst 0 0]
	}
    }
    return [list $ps $ts $lst]
}

proc CmdParseInteger {val default} {
    # parse non-negative integer in $val or if it is "" return $default
    # return -1 on error

    if { $val == "" } { return $default }
    if { ! [regexp {^[1-9][0-9]*$} $val] } {
	return -1
    }
    return $val
}

proc CmdRecTypes {how lst} {
    # interpret list of item types to be get/put from/into receiver
    #  $how in {get, put}
    #  $lst may be either "all", or non-empty subset of $TYPES-{LN, GR}
    #  LAP is accepted only if $SUPPORTLAPS is set and $how=="get"
    #  (note that $TYPES is not defined yet!)
    # return "" on error, otherwise list of types as non-empty subset
    #  of $TYPES-{LN, GR}
    global SUPPORTLAPS

    if { $lst == "all" } {
	if { $SUPPORTLAPS && $how == "get" } {
	    return "WP RT TR LAP"
	} else { return "WP RT TR" }
    } else {
	foreach t $lst {
	    switch -- $t {
		LN -  GR {
		    puts stderr "Cannot get/put LNs or GRs"
		    return ""
		}
		LAP {
		    if { ! $SUPPORTLAPS || $how == "put" } {
			puts stderr "LAP not supported; check the preferences!"
			return ""
		    }
		}
	    }
	}
    }
    return $lst
}

proc CmdFFormat {fmt} {
    # check that $fmt is a valid name for a file format, as
    #  used in the FILEFORMAT array or all in lowercase
    # return name as used in FILEFORMAT or "" on error
    global FILEFORMAT

    foreach n $FILEFORMAT(names) {
	if { $fmt == $n || $fmt == [string tolower $n] } {
	    return $n
	}
    }
    puts stderr "Unknown file format name: $fmt"
    return ""
}

proc CmdFileFmtParamsTypes {how fmt pars ts args} {
    # check that $fmt is a valid file format for I/O operation on file
    #  with given parameters (values not checked here) and item types
    #  and, if $args!="", that item types are in $args or default to a
    #  subset of them
    #  $how in {in, out}
    #  $pars is list with each parameter name followed by corresponding value
    #  $ts is either "all", or subset of $TYPES
    #     LAP is only accepted if $SUPPORTLAPS is set
    # set COMMAND(filetypes), COMMAND(format), COMMAND($how,params)
    #  the latter in the same format as $pars but with all parameters in
    #  the order given by $FILEFORMAT($fmt,$how,params)
    # return 0 on error
    global FILEFORMAT COMMAND SUPPORTLAPS

    # compatibility with older versions
    if { [regexp {^Shapefile_([23])D$} $fmt x dim] } {
	set fmt Shapefile
	lappend pars dim $dim
    }
    # check mode and dependencies
    if { [catch {set mode $FILEFORMAT($fmt,mode)}] || \
	    ! [regexp $how $mode] || \
	    ! ( [catch {set depok $FILEFORMAT($fmt,depends)}] || \
	        [eval $depok] ) } {
	puts stderr "Bad/unvailable format: $fmt"
	return 0
    }
    # check parameter names (values only checked before execution)
    #  set default values for missing parameters, return error if
    #  there is no default for a missing parameter
    if { ! [catch {set pnames $FILEFORMAT($fmt,$how,params)}] } {
	if { [catch {set pds $FILEFORMAT($fmt,$how,pdefts)}] } {
	    set pds ""
	}
	set ps ""
	foreach n $pnames dv $pds {
	    if { [set ix [lsearch -exact $pars $n]] == -1 || \
		     $ix & 1 != 0 } {
		if { $dv == "ERROR" } {
		    puts stderr "Missing value for $n"
		    return 0
		}
		if { [regexp {^global:(.+)$} $dv m dvv] } {
		    global $dvv
		    set dv [set $dvv]
		}
		lappend ps $n $dv
	    } else {
		lappend ps $n [lindex $pars [expr $ix+1]]
		set pars [lreplace $pars $ix [expr $ix+1]]
	    }
	}
	if { $pars != "" } {
	    puts stderr "Unknown parameters: $pars"
	    return 0
	}
	set pars $ps
    } elseif { $pars != "" } {
	puts stderr "No parameters allowed for this format"
	return 0
    }
    # check types
    if { [catch {set fts $FILEFORMAT($fmt,types)}] } {
	if { $how == "in" } {
	    set e 0
	} else { set e 1 }
	set fts [lindex $FILEFORMAT($fmt,io_types) $e]
    }
    switch $FILEFORMAT($fmt,filetype) {
	unique {
	    if { $ts != "" && $ts != "all" && $ts != $fts } {
		puts stderr "Bad type(s) $ts; should be $fts"
		return 0
	    }
	}
	single {
	    if { $ts == "all" || [llength $ts] != 1 || \
		    [lsearch -exact $fts $ts] == -1 } {
		puts stderr "Bad type(s) $ts; should be one of $fts"
		return 0
	    }
	    set fts $ts
	}
	data {
	    if { $ts != "" && $ts != "all" } {
		set fs ""
		foreach f $ts {
		    if { [lsearch -exact $fts $f] == -1 } {
			puts stderr "Bad type(s) $ts; cannot contain $f"
			return 0
		    }
		    lappend fs $f
		}
		set fts $fs
	    }
	}
    }
    if { [set ix [lsearch -exact $fts LAP]] != -1 && ! $SUPPORTLAPS } {
	set fts [lreplace $fts $ix $ix]
    }
    if { $args != "" } {
	foreach t $fts {
	    if { [lsearch $args $t] == -1 } {
		puts stderr "Bad type $t; should be one of $args"
		return 0
	    }
	}
    }
    set COMMAND(format) $fmt
    set COMMAND($how,params) $pars
    set COMMAND(filetypes) $fts
    return 1
}

proc CmdIncompatibleTypes {in_ts out_ts} {
    # check compatibility of input and output types
    # the only possible "conversion" is from RT to WP, and GR to anything
    # return 1 on error

    if { $in_ts == $out_ts || [lsearch $in_ts GR] != -1 } { return 0 }
    foreach o $out_ts {
	if { [lsearch $in_ts $o] == -1 && \
		( $o != "WP" || [lsearch $in_ts RT] == -1 ) } {
	    puts stderr "Output type $o incompatible with input type(s) $in_ts"
	    return 1
	}
    }
    return 0
}

proc CmdBadTranslateHowParams {pps infmt outfmt} {
    # check the transformation parameters of the translate command that may
    #  impose constraints on the input- and output-formats
    # HOW-PARAMS can be
    #   itoffset=DHOUR: time offset to use in input file in GPSMan format,
    #      defaults to time offset given in file
    #   otoffset=DHOUR: time offset to use in output file in GPSMan format
    #      defaults to time offset selected in the options
    # return 0 on error or list with transformation parameters if any

    set tprs ""
    foreach "lhs rhs" $pps {
	switch -- $lhs {
	    itoffset -  otoffset {
		if { [catch {set bad [expr abs($rhs) > 14]}] } {
		    puts stderr "Bad time offset in: $lhs=$rhs"
		    return 0
		}
		if { $infmt != "GPSMan" || $outfmt != "GPSMan" } {
		    puts stderr "Time offset changes only in GPSMan files"
		    return 0
		}
	    }
	}
	lappend tprs $lhs $rhs
    }
    return $tprs
}

proc CmdBadGeoPictsParams {pps outps} {
    # check the processing- and output-parameters of the geopict command
    # the output-parameters may contain paramaters needed by the output format
    #  PROC-PARAMS: [id=ID] [date=DMODE] [dh=DHOUR]
    #     ID: identifier of item in data file, default is last read in
    #     DMODE: pict|exif|fmod   date from picture EXIF (default), EXIF
    #         file, or file last modified time
    #     DHOUR: difference in hours to apply to file dates to get TR dates
    #  OUT-PARAMS: apart from those needed by $fmt
    #              [datum=DATUM] [sy=SYMBOL] [prefix=STRING]
    #               STRING: defaults to "P"
    # return "error" or pair with list of format parameters and
    #  list with all other parameters
    global COMMAND

    set ops ""
    foreach "lhs rhs" $pps {
	switch -- $lhs {
	    id {
		if { $rhs == "" } {
		    puts stderr "Empty item identifier"
		    return error
		}
	    }
	    date {
		if { [lsearch -exact {exif fmod pict} $rhs] == -1 } {
		    puts stderr "Bad mode for date; must be fmod, exif or pict"
		    return error
		}
	    }
	    dh {
		if { ! [regexp {^-?[1-9][0-9]?$} $rhs] || abs($rhs) > 23 } {
		    puts stderr "Bad hour difference; must be integer -23..23"
		    return error
		}
		if { $COMMAND(intypes) != "TR" } {
		    puts stderr "Hour difference only meaningful with a track"
		}
	    }
	    default {
		puts stderr "Unknown parameter $lhs"
		return error
	    }
	}
	lappend ops $lhs $rhs
    }
    set fps ""
    foreach "lhs rhs" $outps {
	# all unrecognised parameters are left as format paramters
	switch -- $lhs {
	    datum -  sy -  prefix {
		# only possible check at this stage
		if { $rhs == "" } {
		    puts stderr "Empty argument to $lhs"
		    return error
		}
	    }
	    default {
		lappend fps $lhs $rhs
		continue
	    }
	}
	lappend ops $lhs $rhs
    }
    return [list $fps $ops]
}

