#
#  gpsman --- GPS Manager: a manager for GPS receiver data
#
#  Copyright (c) 2000 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: lowrance_nmea.tcl - copyright (c) 2000 Brian Baulch - baulchb@hotkey.net.au
#  Last change:  26 Jan 2002
#

set LOGMENU {1s 2s 3s 4s 5s 10s 15s 20s 25s 30s 35s 40s 45s 50s 55s 1m 2m \
		3m 4m 5m 10m 15m 20m 25m 30m}
set LOGCMD {1 2 3 4 5 10 15 20 25 30 35 40 45 50 55 60 120 \
		180 240 300 600 900 1200 1500 1800}
set FirstFlag 1
set SecondsInDay 86400
set NMEATIMEOUT 2000
set NMEATrackName NMEA1
set NMEALogInterval 10
set NMEALastTick 0
array set TXT {
    NMEAauto "autoMapic plot"
    NMEApreload "Preload plot"
    GMNmea "GPS Manager - NMEA"
    Gman "GPS Manager"
    start START
    Interval Interval
    NMEAtitle "NMEA logging/plotting"
    Autoload "Load autoMAPic Image file"
    NMEASet "NMEA Settings"
    Newtrack "Enter name for new Track"
    Nmealogpgr "NMEA logging in progress"
    stop STOP
}
set MapName ""
set MESS(Noimg) "THERE IS NO MAP IMAGE\nAVAILABLE FOR THIS POSITION"
set NMEANoImage 0
set NMEADatum "Australian Geod `66"
# set NMEADatum "WGS 84"
set NMEAReadDone 1
# set NMEABaud 19200
set NMEABaud 4800
# Note - Lowrance always uses WGS 84 for NMEA data.
# The receiver's internal setting is ignored. 

proc StartNMEALog {type} {
    # Type 0 = autoMapic loading
    # Type 1 = Preloaded image

    global HaltNMEA NotLogging SERIALLOG NMEALogInterval NMEATrackName \
		NMEABulkData Rollover Datum Type

    set Type $type
    set Rollover 0
    set NMEABulkData ""
    if {! $Type} { raise . }
    if { [NMEAInProgrWindow] } {
	 return } 
    set HaltNMEA 0
    OpenNMEAPort
    NMEAInterface
    return
}

proc OpenNMEAPort {} {
    # Sets up serial port and fileevents.
    # Also re-opens serial port after map image loading.
   
    global SRLFILE NMEABaud NMEASecondLine NMEAFirstLine MessSent \
	    SERIALPORT

    set MessSent 0
    set NMEAFirstLine 1
    set NMEASecondLine 1
    set SRLFILE [open $SERIALPORT r]
    fconfigure $SRLFILE -blocking 0 -mode $NMEABaud,n,8,1
    fileevent $SRLFILE readable ReadNMEAInputLine
    return
}

proc NMEAErrorMess {mess} {

    global MessSent MESS NMEAReadState

    if { ! $MessSent} {
	set MessSent 1
	GMMessage $MESS($mess)
	set NMEAReadState done
	NMEAOff
    }
    return
}

proc NMEATimeOut {} {

    global Id NMEAReadState MessSent MESS

    if { [string compare $NMEAReadState done] } {
	after cancel $Id
	NMEAErrorMess {receiver}
    }
    return
}
	
proc NMEAInterface {} {


    global Id NotLogging NMEAReadState NMEATIMEOUT SRLFILE

    set NMEAReadState ""
    set Id [after 0 ReadNMEAInputLine]
    after $NMEATIMEOUT NMEATimeOut
    while { [ string compare $NMEAReadState "done"] } { update }
    return
}


proc ReadNMEAInputLine {} {

# The "fileevents" handler.

    global NMEAFirstLine NMEAInputLine NMEAReadState NMEAReadDone \
		SRLFILE

    set ch [read $SRLFILE 1]
    if {$ch == ""} {return}
    if {$ch != "\n"} {
	append NMEAInputLine $ch
    } else {
	if { $NMEAInputLine != ""} {
	    if {! $NMEAFirstLine} {
		set NMEAReadDone 0
		set inputline $NMEAInputLine
		CheckNMEAInput $inputline
	    } else {
		set NMEAFirstLine 0
	    }
	    set NMEAInputLine ""
	}
    }
    update
    set NMEAReadState done
    return
}

proc CheckNMEAInput {inputline} {

    global NMEAInputList NMEAReadDone

    set NMEAInputList [split $inputline ","]
    set prefix [lindex $NMEAInputList 0]
    set prefix [string trimleft $prefix "$"] 
    switch $prefix {
	GPGGA {
	    if {! [CheckNMEASum $inputline]} {
		set NMEAReadDone 1
	    } else {
		ProcessNMEAInputList
	    }
	}
	default {
	}
    }
    return
}

proc ProcessNMEAInputList {} {

    global SRLFILE NMEALogInterval NMEASecondLine \
	NMEAReadDone NMEAInputList NMEALastTick NMEANextTick LogFile \
	SecondsInDay Previous

    set linelist $NMEAInputList
    set data [CreateNMEALists $linelist]
    if {$NMEASecondLine} {
	set NMEASecondLine 0
	set lastick [NMEATimeSeconds $linelist] 
	set Previous $lastick
	set NMEANextTick [expr $lastick + $NMEALogInterval]
	NMEAFormatInData $data
    } else {
	set lastick [NMEATimeSeconds $linelist] 
	if {$lastick < $Previous} {
	    set NMEANextTick [expr $NMEANextTick - $SecondsInDay]
	    set Previous [expr $Previous - $SecondsInDay]
	}
	if {$lastick >= $NMEANextTick} { 
	    set NMEANextTick [expr $lastick + $NMEALogInterval]
	    if { $NMEANextTick > $SecondsInDay } {
		set NMEANextTick [expr $NMEANextTick - $SecondsInDay]
	    }
	    NMEAFormatInData $data
	}
    }	
    NMEAPoint $data
    set NMEAReadDone 1
    return
}

proc CreateNMEALists {linelist} {

    set lat [lindex $linelist 2]
    set latsign [lindex $linelist 3]
    set long [lindex $linelist 4]
    set longsign [lindex $linelist 5]
    set quality [lindex $linelist 6]
    set satnum [lindex $linelist 7]
    set horizontal_dop [lindex $linelist 8]
    set alt [lindex $linelist 9]
    set alt_units [lindex $linelist 10]
    set height_geoid [lindex $linelist 12]
    set geo_units [lindex $linelist 13]
    set data [FormatNMEAPosn $lat $latsign $long $longsign]
    lappend data $alt $horizontal_dop
    return $data
}

proc NMEATimeSeconds {linelist} {

    set hms [lindex $linelist 1]
    set tick [split $hms ""]
    set hours [join [list [lindex $tick 0] [lindex $tick 1]] ""]
    set hours [string trimleft $hours "0"]
    if {$hours == ""} {set hours 0} 
    set minutes [join [list [lindex $tick 2] [lindex $tick 3]] ""]
    set minutes [string trimleft $minutes "0"]
    if {$minutes == ""} {set minutes 0} 
    set seconds [join [list [lindex $tick 4] [lindex $tick 5]] ""]
    set seconds [string trimleft $seconds "0"]
    if {$seconds == ""} {set seconds 0} 
    set tick [expr $seconds + ($minutes * 60) + ($hours * 3600)]
    return $tick
}

proc FormatNMEAPosn {lat latsgn long longsgn} {

    set lat [ConvNMEAToDegrees $lat]
    if {$latsgn == "S"} {
	set lat [expr -$lat]
    }    
    set long [ConvNMEAToDegrees $long]
    if {$longsgn == "W"} {
	set long [expr -$long]
    }
    return [list $lat $long]
}


proc ConvNMEAToDegrees {val} {

    set indx [string first "." $val]
    set d [expr $indx - 3]
    set degrees [string range $val 0 $d]
    set degrees [string trimleft $degrees "0"]
    if { "$degrees" == "" } { set degrees 0 }
    set minutes [string range $val [expr $d+1] end]
    set minutes [string trimleft $minutes "0"]
    if { [string first "." $minutes] == 0 } { set minutes "0$minutes" }
    set degrees [expr $degrees + $minutes/60.0]
    return $degrees
} 
	
proc NMEAFormatInData {posn} {

    global NMEATrackName NMEABulkData LogFile

    set secs [clock seconds]
    set time [TimeStamp $secs]
    set data $NMEATrackName
    lappend data $time $secs
    set points [concat $data $posn]
    lappend NMEABulkData $points
    return
}  


proc NMEAInDataTR {} {
    # add TRs data from NMEA receiver to database

    global Index Number GetDispl NMEABulkData

    set p [lindex NMEABulkData 0]
    set tps ""
    set id [lindex $p 0]
    set ix [IndexNamed TR $id]
    if {$ix == -1} then {
	foreach d $NMEABulkData {
	    set t [CreatePos [lindex $d 3] [lindex $d 4] \
			DMS latlong "WGS 84"]
	    lappend t [lindex $d 1] [lindex $d 2]
	    lappend t [lindex $d 5] [lindex $d 6]
	    lappend tps $t
	}
	set trdata [list [lindex $d 0] $tps]
	set all [list Name TPoints]
	set r [FormData TR $all $trdata]
	StoreTR $ix $id $r $GetDispl
    }
    return
}

proc NMEAPoint {data} {

    global NMEADatum LogFile Type

    if {! $Type } {
	MapControl [lindex $data 0] [lindex $data 1]
    }
    set p [MapFromPosn [lindex $data 0] [lindex $data 1] $NMEADatum]
    set no -1
    PutMapAnimPoint $p $no 1
    return
}

proc NMEAOff {} {

    global SRLFILE LogFile HaltNMEA NMEABulkData

    set HaltNMEA 1
    if {[llength $NMEABulkData] > 0} {NMEAInDataTR}
    close $SRLFILE
    CloseNMEAProgrWindow
    return
}

proc CloseNMEAProgrWindow {} {

    destroy .2inprogr
    return
}

proc NMEANoImageMess {} {

    global NMEANoImage TXT MESS
    
    if { [winfo exists .noimg] } { Raise .noimg ; bell ; return 1 }
    toplevel .noimg
    wm protocol .noimg WM_DELETE_WINDOW { bell }
    wm title .noimg $TXT(GMNmea)
    wm transient .noimg
    wm geometry .noimg +300+100
    label .noimg.lbl -font {helvetica 24 bold} \
	-text $MESS(Noimg)
    pack .noimg.lbl -side top -pady 2
    set NMEANoImage 1
    return
}

proc NMEAMapNameWindow {} {

    global COLOUR EPOSX EPOSY TXT MapName

    if { [winfo exists .mapname] } { Raise .mapname ; bell ; return 1 }
    toplevel .mapname
    wm protocol .mapname WM_DELETE_WINDOW { bell }
    wm title .mapname "Map name"
    wm transient .mapname
    wm geometry .mapname -10+0

    frame .mapname.fr -relief flat -borderwidth 0 -bg $COLOUR(messbg)
#    tk scaling -displayof .mapname 1
    label .mapname.fr.txt -text $MapName -font {charter 24 bold}
    pack .mapname.fr -side right -pady 0
    pack .mapname.fr.txt -side left -pady 0
    update idletasks
    RaiseWindow .mapname
    return 0
}

proc NMEAInProgrWindow {} {

    # create dialog for signaling operation in progress
    # fail if window already exists
    #  single button: Abort; no bindings

    global COLOUR EPOSX EPOSY TXT Type

    if { [winfo exists .2inprogr] } { Raise .2inprogr ; bell ; return 1 }
    toplevel .2inprogr
    wm protocol .2inprogr WM_DELETE_WINDOW { bell }
    wm title .2inprogr $TXT(Gman)
    wm transient .2inprogr
    wm geometry .2inprogr +0+0

    frame .2inprogr.fr -relief flat -borderwidth 1 -bg $COLOUR(messbg)
    label .2inprogr.fr.txt -text $TXT(Nmealogpgr)
    button .2inprogr.fr.stop -text $TXT(stop) -default active -command "NMEAOff"
    pack .2inprogr.fr -side left -pady 0
    pack .2inprogr.fr.txt .2inprogr.fr.stop -side left -pady 2
    update idletasks
    if {! $Type} {grab .2inprogr.fr.stop}
    RaiseWindow .2inprogr
    return 0
}

proc NMEASetupWindow {} {

    global TXT LOGMENU LOGCMD NMEATrackName

    if { [winfo exists .nmea] } { Raise .nmea ; bell ; return 1 }
    toplevel .nmea
    wm protocol .nmea WM_DELETE_WINDOW { bell }
    wm title .nmea "GPS Manager - NMEA"
    wm geometry .nmea +200+200

    frame .nmea.fr0 -relief flat -width 175 -borderwidth 5 
    frame .nmea.fr1 -relief flat -width 175 -borderwidth 5 
    frame .nmea.fr2 -relief flat -width 175 -borderwidth 5  
    frame .nmea.fr3 -relief flat -width 175 -borderwidth 5
    frame .nmea.fr4 -relief flat -width 175 -borderwidth 5
    label .nmea.fr0.title -text $TXT(NMEAauto)
    label .nmea.fr1.title -text $TXT(NMEApreload)
    label .nmea.fr2.title -text $TXT(Autoload)
    label .nmea.fr3.title -text $TXT(NMEASet)
    label .nmea.fr4.title -text $TXT(Newtrack)
    button .nmea.fr2.auto -text $TXT(load) -relief raised -default normal \
	-command LoadAutoMapInfo
    button .nmea.fr1.logr -text $TXT(start) -relief raised -default normal \
	-command "StartNMEALog 1"
    button .nmea.fr0.logr -text $TXT(start) -relief raised -default normal \
	-command "StartNMEALog 0"
    menubutton .nmea.fr3.interval -relief raised -width 10 \
	-text $TXT(Interval) -direction left -textvariable LogInterval \
	-menu .nmea.fr3.interval.m
    menu .nmea.fr3.interval.m -tearoff 0
    wm transient .nmea.fr3.interval.m .nmea.fr3.interval
    foreach t $LOGMENU c $LOGCMD {
	.nmea.fr3.interval.m add command -label $t \
	-command "set NMEALogInterval $c"
    }
    entry .nmea.fr4.track -relief sunken -bd 2 -state normal -width 10 \
	-textvariable NMEATrackName
#    .nmea.fr4.track insert 0 $NMEATrackName
    bind .nmea.fr4.track <Return> NMEAUpdate    
    pack .nmea.fr0 -side top -pady 2
    pack .nmea.fr1 -side top -pady 2
    pack .nmea.fr2 -side top -pady 2
    pack .nmea.fr3 -side top -pady 2
    pack .nmea.fr4 -side top -pady 2
    pack .nmea.fr0.title -side top -pady 2
    pack .nmea.fr1.title -side top -pady 2
    pack .nmea.fr2.title -side top -pady 2
    pack .nmea.fr3.title -side top -pady 2
    pack .nmea.fr4.title -side top -pady 2
    pack .nmea.fr2.auto -side bottom -pady 2
    pack .nmea.fr1.logr -side bottom -pady 2
    pack .nmea.fr0.logr -side bottom -pady 2
    pack .nmea.fr3.interval -side bottom -pady 2
    pack .nmea.fr4.track -side bottom -pady 2
    return 0
}

proc CheckNMEASum {inputline} {

    set line [split $inputline ","]
    set hex [lindex $line end]
    set hex [string trimleft $hex "*"]
    set checksum [Hexcon $hex]
    set str [string trimleft $inputline "$"]
    set str [string trimright $str 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ]
    set str [string trimright $str "*"]
    set strlist [split $str ""]
    set start [lindex $strlist 0]
    binary scan $start c first 
    set strlist [lreplace $strlist 0 0]
    foreach ch $strlist {
	binary scan $ch c next
	set first [expr $first ^ $next]
    }

    if {$checksum != $first} {return 0}
    return 1
}

proc Hexcon {hex} {

    set lookup {0 1 2 3 4 5 6 7 8 9 A B C D E F}
    set majmin [split $hex ""]
    set major [lindex $majmin 0]
    set minor [lindex $majmin 1]
    set decimal [expr $major * 16 ]
    set decimal [expr $decimal + [lsearch $lookup $minor] ]
    return $decimal 
}

proc TimeStamp {time} {

    global Time DateFormat INTERVAL

    if { ! [string compare $DateFormat "DDMMMYYYY"] } {
	set postime [clock format $time -format "%d-%b-%Y %H:%M:%S"]
    }
    if { ! [string compare $DateFormat "MMDDYYYY"] } {
	set postime [clock format $time -format "%m:%d:%Y %H:%M:%S"]
    }
    if { ! [string compare $DateFormat "YYYYMMDD"] } {
	set postime [clock format $time -format "%Y.%m.%d %H:%M:%S"]
    }
    if { ! [string compare $DateFormat "YYYY-MM-DD"] } {
	set postime [clock format $time -format "%Y.%m.%d %H:%M:%S"]
    }
    return $postime
}

proc MapControl {lat long} {

    global LogFile SRLFILE NMEANoImage FirstFlag Top Bottom Right Left

    set within 1
    if {$FirstFlag} {
	set path [FindMapPath $lat $long]
	if {$path == ""} {
	    if {! $NMEANoImage} {
		NMEANoImageMess
	    }
	} else {
	    close $SRLFILE
	    LoadIndexedMap $path
	    OpenNMEAPort
	    set FirstFlag 0
	}
    } else { 
	if {$lat > $Top} {set within 0}
	if {$lat < $Bottom} {set within 0}
	if {$long > $Right} {set within 0}
	if {$long < $Left} {set within 0}
	if {! $within} {
	    set path [FindMapPath $lat $long]
	    if {$path == ""} {
		if {! $NMEANoImage} {NMEANoImageMess}
#		set FirstFlag 1
	    } else {
		close $SRLFILE
		LoadIndexedMap $path
		set NMEANoImage 0
		destroy .noimg
		OpenNMEAPort
	    }
	}
    }
    return
}

proc FindMapPath {lat long} {

    global ImageIndex Top Bottom Right Left MapName

    destroy .mapname
    set max [array size ImageIndex]
    for {set i 0} {$i < $max} {incr i} {
	set Top [lindex $ImageIndex($i) 2]
	set Bottom [lindex $ImageIndex($i) 1]
	set Right [lindex $ImageIndex($i) 4]
	set Left [lindex $ImageIndex($i) 3]
	if {$lat < $Top} {
	    if {$lat > $Bottom} {
		if {$long < $Right} {
		    if {$long > $Left} {
			set t [lindex $ImageIndex($i) 0]
			if { [llength $ImageIndex($i)] > 5 } {
			    set MapName [lindex $ImageIndex($i) 5]
			    NMEAMapNameWindow
			}
			return $t
		    }
		}
	    }
	}
    }
    return
}
