#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
#  Copyright (c) 2001 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: trtort.tcl
#  Last change:  1 September 2001
#
# Includes contributions by Brian Baulch (baulchb@onthenet.com.au)
#  marked "BSB contribution"
#

## map tags used, with $w a window path, $n a natural number:
#    lines in converted RT: exp=$w expRT=$w line
#    turnpoints in converted RT: exp=$w expRT=$w sq2 lab=$n.$n
#    TR elements: exp=$w expTR=$w (in addition to normal tags)
###

proc GMTRtoRT {window} {
    # create dialog to control conversion from track to route
    #  $window is the track edit/show window
    global DPOSX DPOSY COLOUR GMTRTPs GMTRDatum GMTRData TRTPoints TRDatum \
	    TRDispl TXT MESS Map EdWindow MAPSCLENGTH TRCvNPoints TRCvNPsOld\
	    TRCvRTDispl TRCvTRDispl TRCvRT TRCvTPs TRCvDatum TRCvNoTPs \
	    MouseOnTRCvScale

    if { [winfo exists $EdWindow(RT)] } { Raise $EdWindow(RT) ; bell ; return }
    set w ${window}.trtort
    if { [winfo exists $w] } { Raise $w ; bell ; return }

    if { "$window" == "$EdWindow(TR)" } {
	if { "[set TRCvTPs($w) $GMTRTPs]" == "" } {
	    GMMessage $MESS(voidTR)
	    return
	}
	set mapped [lindex $GMTRData end]
	set TRCvDatum($w) $GMTRDatum
    } else {
	set ixt [IndexNamed TR [$window.fr.fr1.id get]]
	set TRCvTPs($w) $TRTPoints($ixt) ; set TRCvDatum($w) $TRDatum($ixt)
	set mapped $TRDispl($ixt)
    }
    set trname [string trim [$window.fr.fr1.id get]]
    if { "$trname" == "" } {
	set trname $TXT(unnamed)
    }
    set ntps [set TRCvNoTPs($w) [llength $TRCvTPs($w)]]
    set TRCvNPoints($w) 2 ; set TRCvNPsOld($w) 1
    set TRCvRT($w) [TRCvRoute $w $TRCvNPoints($w)]
    set TRCvRTDispl($w) 0 ; set TRCvTRDispl($w) $mapped
    set MouseOnTRCvScale 0

    toplevel $w
    wm protocol $w WM_DELETE_WINDOW "TRCvCancel $w"
    wm title $w "GPS Manager: $TXT(TRtoRT)"
    wm transient $w
    set x [expr $DPOSX+100] ; set y [expr $DPOSY+100]
    wm geometry $w +$x+$y

    frame $w.fr -borderwidth 5 -bg $COLOUR(dialbg)
    label $w.fr.from -text [format $TXT(fromTR) $trname]
    frame $w.fr.sep -height 6 -bg $COLOUR(dialbg)
    label $w.fr.npts -text $TXT(TRRTnpoints)
    scale $w.fr.npscl -orient horizontal -from 2 -to $ntps -width 5 \
	    -length $MAPSCLENGTH -resolution 1 -showvalue 1 \
	    -variable TRCvNPoints($w)
    $w.fr.npscl set 2
    bind $w.fr.npscl <Leave> "TRCvNumberChange $w"
    bind $w.fr.npscl <Button-1> { set MouseOnTRCvScale 1 }
    bind $w.fr.npscl <ButtonRelease-1> { set MouseOnTRCvScale 0 }

    frame $w.fr.act
    checkbutton $w.fr.act.drt -text $TXT(TRRTdispl) -variable TRCvRTDispl($w) \
	    -onvalue 1 -offvalue 0 -command "TRCvDisplRT $w" \
	    -selectcolor $COLOUR(check)
    checkbutton $w.fr.act.dtr -text $TXT(TRTRdispl) -variable TRCvTRDispl($w) \
	    -onvalue 1 -offvalue 0 -command "TRCvDisplTR $w" \
	    -selectcolor $COLOUR(check)
    # if TR is mapped do not allow it to be unmapped
    if { $mapped } { $w.fr.act.dtr configure -state disabled }

    frame $w.fr.bts
    button $w.fr.bts.ok -text Ok -command "TRCvCommit $w"
    button $w.fr.bts.cnc -text $TXT(cancel) -command "TRCvCancel $w"

    pack $w.fr.from $w.fr.sep $w.fr.npts $w.fr.npscl -side top
    pack $w.fr.act.drt $w.fr.act.dtr -side left -padx 3
    pack $w.fr.bts.ok $w.fr.bts.cnc -side left -padx 3
    pack $w.fr.act $w.fr.bts -side top -pady 5
    pack $w.fr
    return
}

proc TRCvNumberChange {w} {
    global MouseOnTRCvScale TRCvNPoints TRCvNPsOld TRCvRT

    if { $MouseOnTRCvScale || $TRCvNPoints($w)==$TRCvNPsOld($w) } { return }
    SetCursor ". $w" watch
    set TRCvNPsOld($w) $TRCvNPoints($w)
    set TRCvRT($w) [TRCvRoute $w $TRCvNPoints($w)]
    ResetCursor ". $w"
    TRCvDisplRT $w
    return
}

proc TRCvCommit {w} {
    # define WPs from points in conversion result and open RT edit window
    # fail if RT edit window already exists
    global TRCvRT TRCvDatum EdWindow PositionFormat CREATIONDATE \
	    GRName DEFAULTSYMBOL DEFAULTDISPOPT TXT MYGPS

    if { [winfo exists $EdWindow(RT)] } { Raise $EdWindow(RT) ; bell ; return }
    SetCursor ". $w" watch
    set datum $TRCvDatum($w)
    set type [PosType $PositionFormat]
    # BSB contribution: empty date if Lowrance receiver
    if { $CREATIONDATE && "$MYGPS" != "Lowrance" } {
	set date [Now]
    } else { set date "" }
    set wps "" ; set n 0
    foreach p $TRCvRT($w) {
	set id [format "ZT%04d" $n]
	while { [IndexNamed WP $id] != -1 } {
	    incr n ; set id [format "ZT%04d" $n]
	}
	incr n
	set data [FormData WP "Name PFrmt Posn Datum Date Alt" \
		     [list $id $PositionFormat \
		       [CreatePos [lindex $p 0] [lindex $p 1] \
		                  $PositionFormat $type $datum] \
		       $datum $date [lindex $p 6]]]
	set ix [CreateItem WP $data]
	lappend wps $id
    }
    set grname [format "TR-RT %d" $n]
    while { [IndexNamed GR $grname] != -1 } {
	incr n ; set grname [format "TR-RT %d" $n]
    }
    set data [FormData GR "Name Obs Conts" \
	         [list $grname $TXT(obsTRToRT) [list [list WP $wps]]]]
    CreateItem GR $data
    ResetCursor ". $w"
    TRCvCancel $w
    if { [winfo exists $EdWindow(RT)] } { Raise $EdWindow(RT) ; bell ; return }
    GMRoute -1 {create revert cancel} \
	    [FormData RT "WPoints Displ" [list $wps 0]]
    return
}

proc TRCvCancel {w} {
    # finish off conversion from TR to RT on window $w
    global TRCvNPoints TRCvNPsOld TRCvRTDispl TRCvTRDispl TRCvRT TRCvTPs \
	    TRCvDatum TRCvNoTPs Map

    if { $TRCvRTDispl($w) || $TRCvTRDispl($w) } {
	$Map delete exp=$w
	SetMapBounds
    }
    destroy $w
    unset TRCvNPoints($w) ; unset TRCvRTDispl($w) ; unset TRCvTRDispl($w)
    unset TRCvRT($w) ; unset TRCvTPs($w) ; unset TRCvDatum($w)
    unset TRCvNPsOld($w) ; unset TRCvNoTPs($w)
    return
}

proc TRCvDisplRT {w} {
    global Map TRCvRTDispl TRCvRT TRCvDatum

    $Map delete expRT=$w
    if { $TRCvRTDispl($w) } {
	SetCursor ". $w" watch
	set tgspt [list exp=$w expRT=$w sq2]
	set tgsln [list exp=$w expRT=$w line]
	set ps $TRCvRT($w) ; set datum $TRCvDatum($w)
	set p1 [lindex $ps 0]
	set pp1 [MapFromPosn [lindex $p1 0] [lindex $p1 1] $datum]
	set x [lindex $pp1 0] ; set y [lindex $pp1 1]
	$Map create oval [expr $x-1] [expr $y-1] \
		[expr $x+1] [expr $y+1] -fill black -outline black \
		-tags [concat lab=$w.0 $tgspt]
	set i 1
	foreach p2 [lrange $ps 1 end] {
	    set pp2 [MapFromPosn [lindex $p2 0] [lindex $p2 1] $datum]
	    set x2 [lindex $pp2 0] ; set y2 [lindex $pp2 1]
	    $Map create oval [expr $x2-1] [expr $y2-1] \
		[expr $x2+1] [expr $y2+1] -fill black -outline black \
		-tags [concat lab=$w.$i $tgspt]
	    $Map create line $x $y $x2 $y2 -arrow last -smooth 0 \
		    -fill black -width 2 -tags $tgsln
	    set x $x2 ; set y $y2
	    incr i
	}
	ResetCursor ". $w"
    }
    SetMapBounds
    return
}

proc TRCvDisplTR {w} {
    # map/unmap TR during this conversion
    # TR was not mapped when the conversion started
    global Map TRCvTRDispl TRCvTPs TRCvDatum GMTRIndex

    if { $TRCvTRDispl($w) } {
	SetCursor $w watch
	PutMapTREls $GMTRIndex $TRCvTPs($w) $TRCvDatum($w) \
		[list exp=$w expTR=$w]
	ResetCursor $w
    } else {
	$Map delete expTR=$w
    }
    SetMapBounds
    return
}

proc TRCvRoute {w n} {
    # using information from window $w, convert list of "positions" in
    #  given datum to list of $n>=2 "positions"
    # "position" means list starting with lat long in degrees
    # procs SetDatumData and ComputeDistBearFD are called (indirectly)
    global TRCvTPs TRCvDatum TRCvNoTPs

    set ps $TRCvTPs($w)
    if { $n == $TRCvNoTPs($w) } { return $ps }
    if { $n<3 || [llength $ps]<3 } {
	return [list [lindex $ps 0] [lindex $ps end]]
    }
    SetDatumData $TRCvDatum($w)
    set ss [TRCvRouteSegms [SplitPositions $ps] [expr $n-1]]
    set r [list [lindex [lindex [lindex $ss 0] 2] 0]]
    foreach s $ss {
	lappend r [lindex [lindex $s 2] end]
    }
    return $r
}

proc TRCvRouteSegms {ss n} {
    # breaks a route segment into $n>=2 segments
    # a segment is a list of:
    #     maximum distance from a point to line between 1st and last points
    #     index of a point at that distance
    #     "positions" of points

    while { $n != 2 } {
	set mx 0 ; set im 0 ; set i 0
	foreach s $ss {
	    if { [set d [lindex $s 0]] > $mx } {
		set mx $d ; set im $i
	    }
	    incr i
	}
	if { $mx == 0 } { return $ss }
	set s [lindex $ss $im]
	set is [lindex $s 1] ; set ps [lindex $s 2]
	set ss [concat [lrange $ss 0 [expr $im-1]] \
		       [SplitPositionsAt $is $ps] \
		       [lrange $ss [expr $im+1] end]]
	incr n -1
    }
    return $ss
}

proc SplitPositions {ps} {
    # split "positions" into two segments at maximum distance to line between
    #  end points

    set dfp [FurthestPoint $ps]
    set im [lindex $dfp 1]
    return [SplitPositionsAt $im $ps]
}

proc SplitPositionsAt {im ps} {
    # split "positions" into two segments at given index $im

    set ps1 [lrange $ps 0 $im] ; set ps2 [lrange $ps $im end]
    return [list [linsert [FurthestPoint $ps1] end $ps1] \
	         [linsert [FurthestPoint $ps2] end $ps2]]
}

proc FurthestPoint {ps} {
    # find a point that is furthest from the line between 1st and last points
    # return list with maximum distance and index of point

    if { [set l [llength $ps]] < 3 } { return [list 0 0] }
    set mx 0 ; set i 1
    set pt0 [lindex $ps 0] ; set ptn [lindex $ps end]
    foreach pt [lrange $ps 1 [expr $l-2]] {
	set d [DistToLineFD $pt $pt0 $ptn]
	if { $d >= $mx } {
	    set im $i ; set mx $d
	}
	incr i
    }
    if { $mx == 0 } { set im [expr $l/2] }
    return [list $mx $im]
}

proc DistToLineFD {p p1 p2} {
    # find distance from point $p to line between $p1 and $p2
    #  $p, $p1 and $p2 are "positions"

    set db [ComputeDistBearFD $p1 $p]
    set a [expr [lindex [ComputeDistBearFD $p1 $p2] 1]-[lindex $db 1]]
    return [expr abs(sin($a*0.01745329251994329576)*[lindex $db 0])]
}

###### average WP from TR

proc GMTRtoWP {window} {
    # make average WP of all TPs in $window
    global GMTRTPs GMTRDatum TRTPoints TRDatum EdWindow MESS CREATIONDATE \
	    PositionFormat

    if { [winfo exists $EdWindow(WP)] } { Raise $EdWindow(WP) ; bell ; return }

    if { "$window" == "$EdWindow(TR)" } {
	if { "[set tps $GMTRTPs]" == "" } {
	    GMMessage $MESS(voidTR)
	    return
	}
	set datum $GMTRDatum
    } else {
	set ixt [IndexNamed TR [$window.fr.fr1.id get]]
	set tps $TRTPoints($ixt) ; set datum $TRDatum($ixt)
    }
    set sla 0 ; set slo 0 ; set sal 0 ; set noalt 1 ; set n 0
    foreach tp $tps {
	set sla [expr $sla+[lindex $tp 0]] ; set slo [expr $slo+[lindex $tp 1]]
	if { [regexp {[0-9\.]+} [set a [lindex $tp 6]]] } {
	    set sal [expr $sal+$a]
	    set noalt 0
	}
	incr n
    }
    if { $noalt } {
	set sal ""
    } else { set sal [format %.1f [expr 1.0*$sal/$n]] }
    set p [CreatePos [expr 1.0*$sla/$n] [expr 1.0*$slo/$n] $PositionFormat \
	    [PosType $PositionFormat] $datum]
    set opts {create revert cancel}
    if { $CREATIONDATE } {
	GMWPoint -1 $opts [FormData WP "PFrmt Posn Datum Alt Date" \
		[list $PositionFormat $p $datum $sal [Now]]]
    } else {
	GMWPoint -1 $opts [FormData WP "PFrmt Posn Datum Alt Commt" \
		[list $PositionFormat $p $datum $sal [DateCommt [Now]]]]
    }
    return
}

proc GMGRtoWP {window} {
    # make average WP of all WPs in GR shown in $window
    global MESS EdWindow WPPosn WPDatum WPAlt CREATIONDATE PositionFormat

    if { [winfo exists $EdWindow(WP)] } { Raise $EdWindow(WP) ; bell ; return }

    switch "[set ixs [GMGRCollectWPs $window]]" {
	void {
	    GMMessage $MESS(voidGR) ; return
	}
	error {
	    return
	}
    }
    if { "$ixs" == "" } { bell ; return }
    set sla 0 ; set slo 0 ; set sal 0 ; set noalt 1 ; set n 0
    set datum ""
    foreach ix $ixs {
	set p $WPPosn($ix)
	if { "$datum" == "" } {
	    set datum $WPDatum($ix)
	} elseif { "$WPDatum($ix)" != "$datum" } {
	    set p [ConvertDatum [lindex $p 0] [lindex $p 1] \
		    $WPDatum($ix) $datum DDD]
	}
	set sla [expr $sla+[lindex $p 0]] ; set slo [expr $slo+[lindex $p 1]]
	if { [regexp {[0-9\.]+} [set a $WPAlt($ix)]] } {
	    set sal [expr $sal+$a]
	    set noalt 0
	}
	incr n
    }
    if { $noalt } {
	set sal ""
    } else { set sal [format %.1f [expr 1.0*$sal/$n]] }
    set p [CreatePos [expr 1.0*$sla/$n] [expr 1.0*$slo/$n] $PositionFormat \
	    [PosType $PositionFormat] $datum]
    set opts {create revert cancel}
    if { $CREATIONDATE } {
	GMWPoint -1 $opts [FormData WP "PFrmt Posn Datum Alt Date" \
		[list $PositionFormat $p $datum $sal [Now]]]
    } else {
	GMWPoint -1 $opts [FormData WP "PFrmt Posn Datum Alt Commt" \
		[list $PositionFormat $p $datum $sal [DateCommt [Now]]]]
    }
    return
}

