#  Copyright (C) 1999-2018
#  Smithsonian Astrophysical Observatory, Cambridge, MA, USA
#  For conditions of distribution and use, see copyright notice in "copyright"

package provide DS9 1.0

proc SAMPDef {} {
    global isamp

    set isamp(timeout) 1000
}

proc InitSAMP {} {
    global pds9

    if {$pds9(samp)} {
	catch {SAMPConnect 0}
    }
}

# Cmds

proc SAMPConnect {{verbose 1}} {
    global ds9
    global isamp
    global samp
    global sampmap
    global sampmap2

    # connected?
    if {[info exists samp]} {
	if {$verbose} {
	    Error "SAMP: [msgcat::mc {already connected}]"
	}
	return
    }

    # reset samp array
    if {[info exists samp]} {
	unset samp
    }
    set samp(apps,image) {}
    set samp(apps,table) {}
    set samp(apps,votable) {}

    # these are to try to prevent feedback problems with 
    # other probgrams
    set samp(rcvd,lock) 0
    set samp(send,lock) 0

    # delete any old tmp files
    SAMPDelTmpFiles

    # can we find a hub?
    if {![SAMPParseHub]} {
 	if {$verbose} {
	    Error "SAMP: [msgcat::mc {unable to locate HUB}]"
	}
	if {[info exists samp]} {
	    unset samp
	}
	return
    }

    # register
    set params [list "string $samp(secret)"]
    set rr {}
    if {![SAMPSend {samp.hub.register} $params rr]} {
  	if {$verbose} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
	if {[info exists samp]} {
	    unset samp
	}
	return
    }
    set rr [lindex $rr 1]
    foreach ff $rr {
	foreach {key val} $ff {
	    switch -- $key {
		samp.hub-id {set samp(hub) $val}
		samp.self-id {set samp(self) $val}
		samp.private-key {set samp(private) $val}
	    }
	}
    }

    # declare metadata
    catch {unset sampmap}
    set sampmap(samp.name) {string "SAOImageDS9"}
    set sampmap(samp.description.text) {string "SAOImageDS9 is an astronomical visualization application"}
    set sampmap(samp.icon.url) {string "http://ds9.si.edu/doc/sun.gif"}
    set sampmap(samp.documentation.url) {string "http://ds9.si.edu/doc/ref/index.html"}

    set sampmap(home.page) {string "http://ds9.si.edu/"}
    set sampmap(author.name) {string "William Joye"}
    set sampmap(author.email) {string "ds9help@cfa.harvard.edu"}
    set sampmap(author.affiliation) {string "Smithsonian Astrophysical Observatory"}
    set sampmap(ds9.version) "string [lindex $ds9(version) 0]"

    set param1 [list "string $samp(private)"]
    set param2 [list "struct sampmap"]
    set params "$param1 $param2"
    set rr {}
    if {![SAMPSend {samp.hub.declareMetadata} $params rr]} {
  	if {$verbose} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
	if {[info exists samp]} {
	    unset samp
	}
	return
    }

    # who are we
    set samp(port) [lindex [fconfigure [xmlrpc::serve 0] -sockname] 2]
    set samp(home) "[info hostname]:$samp(port)"

    # callback
    set param1 [list "string $samp(private)"]
    set param2 [list "string http://$samp(home)"]
    set params "$param1 $param2"
    set rr {}
    if {![SAMPSend {samp.hub.setXmlrpcCallback} $params rr]} {
  	if {$verbose} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
	if {[info exists samp]} {
	    unset samp
	}
	return
    }

    # declare subscriptions
    catch {unset sampmap}
    catch {unset sampmap2}
    set sampmap(samp.app.ping) {struct mapPing}

    set sampmap(samp.hub.event.shutdown) {struct mapShutdown}
    set sampmap(samp.hub.event.register) {struct mapRegister}
    set sampmap(samp.hub.event.unregister) {struct mapUnregister}
    set sampmap(samp.hub.disconnect) {struct mapDisconnect}

    set sampmap(image.load.fits) {struct mapImageLoadFits}
    set sampmap(table.load.fits) {struct mapTableLoadFits}
    set sampmap(table.load.votable) {struct mapTableLoadVotable}
    set sampmap(table.highlight.row) {struct mapTableHighlightRow}
    set sampmap(table.select.rowList) {struct mapTableSelectRowList}
    set sampmap(coord.pointAt.sky) {struct mapCoordPointAtSky}
    set sampmap(client.env.get) {struct mapClientEnvGet}

    set sampmap(x-samp.affiliation.name) {struct mapAffiliationName}
    set sampmap(x-samp.affiliation.url) {struct mapAffiliationURL}
    set sampmap(x-samp.homepage.url) {struct mapHomepageURL}
    set sampmap(x-samp.releasenotes.url) {struct mapReleasenotesURL}
    set sampmap(x-samp.faq.url) {struct mapFAQURL}
    set sampmap(x-samp.authors) {struct mapAuthors}
    set sampmap(x-samp.release.version) {struct mapReleaseVersion}

    set sampmap(ds9.get) {struct mapDS9Get}
    set sampmap(ds9.set) {struct mapDS9Set}
    set sampmap(ds9.restricted-get) {struct sampmap2}
    set sampmap(ds9.restricted-set) {struct sampmap2}

    set sampmap2(x-samp.mostly-harmless) {string "1"}

    set param1 [list "string $samp(private)"]
    set param2 [list "struct sampmap"]
    set params "$param1 $param2" 
    set rr {}
    if {![SAMPSend {samp.hub.declareSubscriptions} $params rr]} {
  	if {$verbose} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
	if {[info exists samp]} {
	    unset samp
	}
	return
    }

    after $isamp(timeout) SAMPUpdate
}

proc SAMPDisconnect {} {
    global ds9
    global samp

    # connected?
    if {![info exists samp]} {
	Error "SAMP: [msgcat::mc {not connected}]"
	return
    }

    # disconnect
    if {[info exists samp(private)]} {
	set params [list "string $samp(private)"]
	set rr {}
	if {![SAMPSend {samp.hub.unregister} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
	SAMPShutdown
    }

    UpdateFileMenu
    UpdateCATDialog
}

proc SAMPSendImageLoadFits {id} {
    global ds9
    global current
    global isamp
    global samp
    global sampmap
    global sampmap2

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPSendImageLoadFits"
    }

    # connected?
    if {![info exists samp]} {
	Error "SAMP: [msgcat::mc {not connected}]"
	return
    }

    # are we locked?
    if {$samp(rcvd,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Rcvd locked"
	}
	return
    }

    # got something to send?
    if {![$current(frame) has fits]} {
	return
    }

    # save current frame
    set fn [tmpnam {.samp}]
    catch {$current(frame) save fits image file "\{$fn\}"}

    # name to use
    set fnb [$current(frame) get fits file name root base]
    if {[regexp {(.*)\[.*\]} $fnb aa bb]} {
	set fnb $bb
    }

    # cmd
    catch {unset sampmap}
    set sampmap(samp.mtype) {string "image.load.fits"}
    set sampmap(samp.params) {struct sampmap2}

    catch {unset sampmap2}
    set sampmap2(url) "string \"[XMLQuote file://localhost/$fn]\""
    set sampmap2(name) "string \"[XMLQuote $fnb]\""

    set param1 [list "string $samp(private)"]
    if {$id != {}} {
	set param2 [list "string $id"]
    } else {
	set param2 {}
    }
    set param3 [list "struct sampmap"]
    set params "$param1 $param2 $param3" 

    set rr {}
    if {$id != {}} {
	if {![SAMPSend {samp.hub.notify} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    } else {
	if {![SAMPSend {samp.hub.notifyAll} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    }

    # set lock
    set samp(send,lock) 1
    after $isamp(timeout) SAMPClearSendLock
}

proc SAMPSendTableLoadFits {id} {
    global ds9
    global current
    global isamp
    global samp
    global sampmap
    global sampmap2

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPSendTableLoadFits"
    }

    # connected?
    if {![info exists samp]} {
	Error "SAMP: [msgcat::mc {not connected}]"
	return
    }

    # are we locked?
    if {$samp(rcvd,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Rcvd locked"
	}
	return
    }

    # got something to send?
    if {![$current(frame) has fits] && [$current(frame) has fits bin]} {
	return
    }

    # save current frame
    set fn [tmpnam {.samp}]
    catch {$current(frame) save fits table file "\{$fn\}"}

    # name to use
    set fnb [$current(frame) get fits file name root base]
    if {[regexp {(.*)\[.*\]} $fnb aa bb]} {
	set fnb $bb
    }

    # cmd
    catch {unset sampmap}
    set sampmap(samp.mtype) {string "table.load.fits"}
    set sampmap(samp.params) {struct sampmap2}

    catch {unset sampmap2}
    set sampmap2(url) "string \"[XMLQuote file://localhost/$fn]\""
    set sampmap2(name) "string \"[XMLQuote $fnb]\""

    set param1 [list "string $samp(private)"]
    if {$id != {}} {
	set param2 [list "string $id"]
    } else {
	set param2 {}
    }
    set param3 [list "struct sampmap"]
    set params "$param1 $param2 $param3" 

    set rr {}
    if {$id != {}} {
	if {![SAMPSend {samp.hub.notify} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    } else {
	if {![SAMPSend {samp.hub.notifyAll} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    }

    # set lock
    set samp(send,lock) 1
    after $isamp(timeout) SAMPClearSendLock
}

proc SAMPSendTableLoadVotable {id varname} {
    global ds9
    global isamp
    global samp
    global sampmap
    global sampmap2

    upvar #0 $varname var
    global $varname

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPSendTableLoadVotable $id $varname"
    }

    # connected?
    if {![info exists samp]} {
	Error "SAMP: [msgcat::mc {not connected}]"
	return
    }

    # are we locked?
    if {$samp(rcvd,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Rcvd locked"
	}
	return
    }

    # remember
    set samp(icat,$varname$samp(port)) $varname
    set samp(ocat,$varname) $varname$samp(port)

    # save votable
    set fn [tmpnam {.samp}]
    CATSaveFn $varname $fn VOTWrite

    # cmd
    catch {unset sampmap}
    set sampmap(samp.mtype) {string "table.load.votable"}
    set sampmap(samp.params) {struct sampmap2}

    catch {unset sampmap2}
    set sampmap2(url) "string \"[XMLQuote file://localhost/$fn]\""
    set sampmap2(table-id) "string [XMLQuote $varname$samp(port)]"
    set sampmap2(name) "string \"[XMLQuote $var(title)]\""

    set param1 [list "string $samp(private)"]
    if {$id != {}} {
	set param2 [list "string $id"]
    } else {
	set param2 {}
    }
    set param3 [list "struct sampmap"]
    set params "$param1 $param2 $param3" 

    set rr {}
    if {$id != {}} {
	if {![SAMPSend {samp.hub.notify} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    } else {
	if {![SAMPSend {samp.hub.notifyAll} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    }

    # set lock
    set samp(send,lock) 1
    after $isamp(timeout) SAMPClearSendLock
}

proc SAMPSendTableHighlightRow {id varname row} {
    global isamp
    global samp
    global sampmap
    global sampmap2

    # row starts at 1
    upvar #0 $varname var
    global $varname

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPSendTableHighlightRow $samp(ocat,$varname) $row"
    }

    catch {unset sampmap}
    set sampmap(samp.mtype) {string "table.highlight.row"}
    set sampmap(samp.params) {struct sampmap2}

    catch {unset sampmap2}
    set sampmap2(table-id) "string [XMLQuote $samp(ocat,$varname)]"
    set sampmap2(row) "string [XMLQuote [expr $row-1]]"

    set param1 [list "string $samp(private)"]
    if {$id != {}} {
	set param2 [list "string $id"]
    } else {
	set param2 {}
    }
    set param3 [list "struct sampmap"]
    set params "$param1 $param2 $param3" 

    set rr {}
    if {$id != {}} {
	if {![SAMPSend {samp.hub.notify} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    } else {
	if {![SAMPSend {samp.hub.notifyAll} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    }

    # set lock
    set samp(send,lock) 1
    after $isamp(timeout) SAMPClearSendLock
}

proc SAMPSendTableSelectRowList {id varname rows} {
    global isamp
    global samp
    global sampmap
    global sampmap2

    # rows start at 1
    upvar #0 $varname var
    global $varname

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPSendTableSelectRowList $samp(ocat,$varname) $rows"
    }

    catch {unset sampmap}
    set sampmap(samp.mtype) {string "table.select.rowList"}
    set sampmap(samp.params) {struct sampmap2}

    catch {unset sampmap2}
    set sampmap2(table-id) "string [XMLQuote $samp(ocat,$varname)]"
    set ss {}
    foreach rr $rows {
	lappend ss "string [expr $rr-1]"
    }
    set sampmap2(row-list) [list array $ss]

    set param1 [list "string $samp(private)"]
    if {$id != {}} {
	set param2 [list "string $id"]
    } else {
	set param2 {}
    }
    set param3 [list "struct sampmap"]
    set params "$param1 $param2 $param3" 

    set rr {}
    if {$id != {}} {
	if {![SAMPSend {samp.hub.notify} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    } else {
	if {![SAMPSend {samp.hub.notifyAll} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    }

    # set lock
    set samp(send,lock) 1
    after $isamp(timeout) SAMPClearSendLock
}

proc SAMPSendTableRowListCmd {varname rowlist} {
    global ds9
    global samp

    # connected?
    if {![info exists samp]} {
	return
    }

    if {$samp(apps,votable) == {}} {
	return
    }

    # are we good?
    if {![info exists samp(ocat,$varname)]} {
	return
    }

    # are we locked?
    if {$samp(rcvd,lock)} {
	global debug
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Rcvd locked"
	}
	return
    }

    switch -- [llength $rowlist] {
	0 {}
	1 {SAMPSendTableHighlightRow {} $varname $rowlist}
	default {SAMPSendTableSelectRowList {} $varname $rowlist}
    }
}

proc SAMPSendCoordPointAtSky {id coord} {
    global isamp
    global samp
    global sampmap
    global sampmap2

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPSendCoordPointAtSky $id $coord"
    }

    catch {unset sampmap}
    set sampmap(samp.mtype) {string "coord.pointAt.sky"}
    set sampmap(samp.params) {struct sampmap2}

    catch {unset sampmap2}
    set sampmap2(ra) "string [XMLQuote [lindex $coord 0]]"
    set sampmap2(dec) "string [XMLQuote [lindex $coord 1]]"

    set param1 [list "string $samp(private)"]
    if {$id != {}} {
	set param2 [list "string $id"]
    } else {
	set param2 {}
    }
    set param3 [list "struct sampmap"]
    set params "$param1 $param2 $param3" 

    set rr {}
    if {$id != {}} {
	if {![SAMPSend {samp.hub.notify} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    } else {
	if {![SAMPSend {samp.hub.notifyAll} $params rr]} {
	    Error "SAMP: [msgcat::mc {internal error}] $rr"
	}
    }

    # set lock
    set samp(send,lock) 1
    after $isamp(timeout) SAMPClearSendLock
}

proc SAMPSendCoordPointAtSkyCmd {which} {
    global ds9
    global samp

    # connected?
    if {![info exists samp]} {
	return
    }

    # are we locked?
    if {$samp(rcvd,lock)} {
	global debug
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Rcvd locked"
	}
	return
    }

    if {$samp(apps,image) == {} || $samp(apps,table) == {}} {
	return
    }

    if {[$which has wcs celestial wcs]} {
	set coord [$which get coordinates [$which get cursor canvas] wcs fk5 degrees]
	if {$coord != {}} {
	    SAMPSendCoordPointAtSky {} "$coord"
	}
    }
}

# Support

proc SAMPShutdown {} {
    global ds9
    global samp

    # delete any files
    SAMPDelTmpFiles

    # close the server socket if still up
    catch {close $xmlrpc::acceptfd}

    # update the menus
    set samp(apps,image) {}
    set samp(apps,table) {}
    set samp(apps,votable) {}
    UpdateFileMenu
    UpdateCATDialog

    # unset samp array
    if {[info exists samp]} {
	unset samp
    }
}

proc SAMPUpdate {} {
    # this routine is run after a delay since it needs to 
    # call the hub for metadata

    # connected? we might have already disconnected.
    global samp
    if {![info exists samp]} {
	return
    }

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPUpdate"
    }

    # image fits
    set param1 [list "string $samp(private)"]
    set param2 [list "string image.load.fits"]
    set params "$param1 $param2" 
    set rr {}
    if {![SAMPSend {samp.hub.getSubscribedClients} $params rr]} {
	Error "SAMP: [msgcat::mc {internal error}] $rr"
	return
    }
    
    set samp(apps,image) {}
    foreach arg [lindex $rr 1] {
	foreach {key val} $arg {
	    if {$key != {}} {
		lappend samp(apps,image) [list $key [SAMPGetAppName $key]]
	    }
	}
    }

    # table fits
    set param1 [list "string $samp(private)"]
    set param2 [list "string table.load.fits"]
    set params "$param1 $param2" 
    set rr {}
    if {![SAMPSend {samp.hub.getSubscribedClients} $params rr]} {
	Error "SAMP: [msgcat::mc {internal error}] $rr"
	return
    }
    
    set samp(apps,table) {}
    foreach arg [lindex $rr 1] {
	foreach {key val} $arg {
	    if {$key != {}} {
		lappend samp(apps,table) [list $key [SAMPGetAppName $key]]
	    }
	}
    }

    # votable
    set param1 [list "string $samp(private)"]
    set param2 [list "string table.load.votable"]
    set params "$param1 $param2" 
    set rr {}
    if {![SAMPSend {samp.hub.getSubscribedClients} $params rr]} {
	Error "SAMP: [msgcat::mc {internal error}] $rr"
	return
    }
    
    set samp(apps,votable) {}
    foreach arg [lindex $rr 1] {
	foreach {key val} $arg {
	    if {$key != {}} {
		lappend samp(apps,votable) [list $key [SAMPGetAppName $key]]
	    }
	}
    }

    if {$debug(tcl,samp)} {
	puts stderr "SAMPUpdate: image apps: $samp(apps,image)"
	puts stderr "SAMPUpdate: table apps: $samp(apps,table)"
	puts stderr "SAMPUpdate: votable apps: $samp(apps,votable)"
    }

    UpdateFileMenu
    UpdateCATDialog
}

proc SAMPSend {method params resultVar} {
    upvar $resultVar result

    global samp

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPSend: $method $params"
    }

    if {[catch {set result [xmlrpc::call $samp(url) $samp(method) $method $params]}]} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMPSend Error: $result"
	}
	return 0
    }

    # reset error if needed
    # xmlrpc leaves error msgs
    InitError samp

    if {$debug(tcl,samp)} {
	puts stderr "SAMPSend Result: $result"
    }
    return 1
}

proc SAMPReply {msgid status {result {}} {url {}} {error {}}} {
    global samp
    global sampmap
    global sampmap2
    global sampmap3

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPReply:$msgid:$status:$result:$url:$error:"
    }

    catch {unset sampmap}
    catch {unset sampmap2}
    catch {unset sampmap3}
    switch -- $status {
	OK {
	    set sampmap(samp.status) {string "samp.ok"}
	    set sampmap(samp.result) {struct sampmap2}
	    if {$result != {}} {
		set sampmap2(value) "string \"[XMLQuote $result]\""
	    }
	    if {$url != {}} {
		set sampmap2(url) "string \"[XMLQuote $url]\""
	    }
	}
	WARNING {
	    set sampmap(samp.status) {string "samp.warning"}
	    set sampmap(samp.result) {struct sampmap2}
	    set sampmap(samp.error)  {struct sampmap3}
	    if {$result != {}} {
		set sampmap2(value) "string \"[XMLQuote $result]\""
	    }
	    if {$url != {}} {
		set sampmap2(url) "string \"[XMLQuote $url]\""
	    }
	    set sampmap3(samp.errortxt) "string \"[XMLQuote $error]\""
	}
	ERROR {
	    set sampmap(samp.status) {string "samp.error"}
	    set sampmap(samp.error)  {struct sampmap3}
	    set sampmap3(samp.errortxt) "string \"[XMLQuote $error]\""
	}
    }
    set param1 [list "string $samp(private)"]
    set param2 [list "string $msgid"]
    set param3 [list "struct sampmap"]
    set params "$param1 $param2 $param3"
    set rr {}
    if {![SAMPSend {samp.hub.reply} $params rr]} {
	Error "SAMP: [msgcat::mc {internal error}] $rr"
	return
    }
}

proc SAMPReplySimple {msgid str} {
    upvar $varname args

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPReplySimple: $str"
    }

    global samp

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    SAMPReply $msgid OK "$str"
}

proc SAMPClearSendLock {} {
    global samp
    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPClearSendLock"
    }
    set samp(send,lock) 0
}

# receiveNotification(string sender-id, map message)
proc samp.client.receiveNotification {args} {
    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPReceivedNotification: $args"
    }
    set secret [lindex $args 0]
    set id [lindex $args 1]
    set map [lindex $args 2]

    set mtype {}
    set params {}
    foreach mm $map {
	foreach {key val} $mm {
	    switch -- $key {
		samp.mtype {set mtype $val}
		samp.params {set params $val}
	    }
	}
    }

    switch -- $mtype {
	samp.hub.event.shutdown {
	    SAMPRcvdEventShutdown params
	}
	samp.hub.event.register {
	    SAMPRcvdEventRegister params
	}
	samp.hub.event.unregister {
	    SAMPRcvdEventUnregister params
	}
	samp.hub.disconnect {
	    SAMPRcvdDisconnect params
	}
	image.load.fits {
	    SAMPRcvdImageLoadFits params
	}
	table.load.fits {
	    SAMPRcvdTableLoadFits params
	}
	table.load.votable {
	    SAMPRcvdTableLoadVotable params
	}
	table.highlight.row {
	    SAMPRcvdTableHighlightRow params
	}
	table.select.rowList {
	    SAMPRcvdTableSelectRowList params
	}
	coord.pointAt.sky {
	    SAMPRcvdCoordPointAtSky params
	}
	ds9.set {
	    SAMPRcvdDS9Set {} params 0
	}
	ds9.restricted-set {
	    SAMPRcvdDS9Set {} params 1
	}
	default {
	    if {$debug(tcl,samp)} {
		puts stderr "SAMP samp.client.receiveNotification: bad mtype $mtype"
	    }
	}
    }
    return {string OK}
}

# receiveCall(string sender-id, string msg-id, map message)
proc samp.client.receiveCall {args} {
    global ds9

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPReceivedCall: $args"
    }

    set secret [lindex $args 0]
    set id [lindex $args 1]
    set msgid [lindex $args 2]
    set map [lindex $args 3]

    set mtype {}
    set params {}
    foreach mm $map {
	foreach {key val} $mm {
	    switch -- $key {
		samp.mtype {set mtype $val}
		samp.params {set params $val}
	    }
	}
    }

    switch -- $mtype {
	samp.app.ping {
	    SAMPReply $msgid OK
	}
	image.load.fits {
	    SAMPRcvdImageLoadFits params
	    SAMPReply $msgid OK
	}
	table.load.fits {
	    SAMPRcvdTableLoadFits params
	    SAMPReply $msgid OK
	}
	table.load.votable {
	    SAMPRcvdTableLoadVotable params
	    SAMPReply $msgid OK
	}
	table.highlight.row {
	    SAMPRcvdTableHighlightRow params
	    SAMPReply $msgid OK
	}
	table.select.rowList {
	    SAMPRcvdTableSelectRowList params
	    SAMPReply $msgid OK
	}
	coord.pointAt.sky {
	    SAMPRcvdCoordPointAtSky params
	    SAMPReply $msgid OK
	}
	client.env.get {
	    SAMPRcvdClientEnvGet $msgid params
	}
	x-samp.affiliation.name {
	    SAMPReplySimple $msgid "SMITHSONIAN ASTROPHYSICAL OBSERVATORY"
	}
	x-samp.affiliation.url {
	    SAMPReplySimple $msgid "https://www.cfa.harvard.edu/sao"
	}
	x-samp.homepage.url {
	    SAMPReplySimple $msgid "http://ds9.si.edu"
	}
	x-samp.releasenotes.url {
	    SAMPReplySimple $msgid OK "http://ds9.si.edu/doc/release/r8.0.html"
	}
	x-samp.faq.url {
	    SAMPReplySimple $msgid OK "http://ds9.si.edu/doc/faq.html"
	}
	x-samp.authors {
	    global help
	    SAMPReplySimple $msgid OK "$help(authors)"
	}
	x-samp.release.version {
	    SAMPReplySimple $msgid OK "$ds9(version,display)"
	}
	ds9.get {
	    SAMPRcvdDS9Get $msgid params
	}
	ds9.set {
	    SAMPRcvdDS9Set $msgid params 0
	}
	ds9.restricted-get {
	    SAMPRcvdDS9Get $msgid params
	}
	ds9.restricted-set {
	    SAMPRcvdDS9Set $msgid params 1
	}
	default {
	    SAMPReply $msgid ERROR {} {} "[msgcat::mc {Unknown command}]: $mtype"
	    if {$debug(tcl,samp)} {
		puts stderr "SAMP samp.client.receiveCall: bad mtype $mtype"
	    }
	}
    }
    return {string OK}
}

# receiveResponse(string responder-id, string msg-tag, map response)
proc samp.client.receiveResponse {args} {
    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPReceivedResponse: $args"
    }

    set msgtag [lindex $args 0]
    set value [lindex $args 1]
    set map [lindex $args 2]

    return {string OK}
}

# Support

proc SAMPParseHub {} {
    global samp
    global env

    set fn {}

    if {[info exists env(SAMP_HUB)]} {
	if {$env(SAMP_HUB) != {}} {
	    set exp {std-lockurl:(.*)}
	    if {[regexp $exp $env(SAMP_HUB) dummy url]} {

		ParseURL $url rr
		switch -- $rr(scheme) {
		    ftp {
			set fn [tmpnam {.samp}]
			GetFileFTP $rr(authority) $rr(path) $fn
		    }
		    file {set fn $rr(path)}
		    http -
		    https -
		    default {
			set fn [tmpnam {.samp}]
			GetFileHTTP $url $fn
		    }
		}
	    }
	}
    }

    if {$fn == {}} {
	# look in home directory for .samp
	global tcl_platform
	switch $tcl_platform(platform) {
	    unix {
		set fn [file join [GetEnvHome] {.samp}]
	    }
	    windows {
		set fn [file join "$env(HOMEDRIVE)$env(HOMEPATH)" {.samp}]
	    }
	}
    }

    # no hub to be found
    if {![file exist $fn]} {
	return 0
    }

    set samp(secret) {}
    set samp(url) {}
    set samp(metod) {}
    set fp [open $fn r]
    while {1} {
	if {[gets $fp line] == -1} {
	    break
	}

	# skip any comments
	if {[string range $line 0 0] == "#"} {
	    continue;
	}

	if {[regexp -nocase {samp.secret=(.*)} $line foo ss]} {
	    set samp(secret) $ss
	}
	if {[regexp -nocase {samp.hub.xmlrpc.url=(.*)} $line foo url]} {
	    if {[ParseURL $url r]} {
		set samp(url) $r(scheme)://$r(authority)
		set samp(method) [string range $r(path) 1 end]
	    }
	}
    }
    catch {close $fp}

    if {$samp(secret) == {} || $samp(url) == {}} {
	SAMPDelTmpFiles
	return 0
    }

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPParseHub: $samp(secret) $samp(url) $samp(method)"
    }
    return 1
}

proc SAMPGetAppName {id} {
    global samp

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPGetAppName: $id"
    }

    set param1 [list "string $samp(private)"]
    set param2 [list "string $id"]
    set params "$param1 $param2" 
    set rr {}
    if {![SAMPSend {samp.hub.getMetadata} $params rr]} {
	Error "SAMP: [msgcat::mc {internal error}] $rr"
	return
    }

    set name {}
    foreach arg [lindex $rr 1] {
	foreach {key val} $arg {
	    switch -- $key {
		samp.name {set name [XMLUnQuote $val]}
	    }
	}
    }

    return $name
}

# CallBacks
# Hub

proc SAMPRcvdEventShutdown {varname} {
    upvar $varname args

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdEventShutdown: $args"
    }

    SAMPShutdown
}

proc SAMPRcvdEventRegister {varname} {
    upvar $varname args

    global isamp
    global samp

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdEventRegister: $args"
    }

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		id {
		    # check to see if its just us
		    if {$samp(self) == $val} {
			return
		    }
		}
	    }
	}
    }

    # wait
    after $isamp(timeout) SAMPUpdate
}

proc SAMPRcvdEventUnregister {varname} {
    upvar $varname args

    global isamp
    global samp

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdEventUnregister: $args"
    }

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		id {
		    # check to see if its just us
		    if {$samp(self) == $val} {
			return
		    }
		}
	    }
	}
    }

    # wait
    after $isamp(timeout) SAMPUpdate
}

proc SAMPRcvdDisconnect {varname} {
    upvar $varname args

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdDisconnect: $args"
    }

    set msg {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		reason {set msg [XMLUnQuote $val]}
	    }
	}
    }

    SAMPShutdown
}

# HTTPClient

proc SAMPRcvdImageLoadFits {varname} {
    upvar $varname args

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdImageLoadFits: $args"
    }

    global current
    global samp

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    set url {}
    set imageid {}
    set name {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		url {set url [XMLUnQuote $val]}
		image-id {set imageid [XMLUnQuote $val]}
		name {set name [XMLUnQuote $val]}
	    }
	}
    }

    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdImageLoadFits: $url $imageid $name"
    }

    if {$url != {}} {
	MultiLoad
	LoadURLFits $url {} {}
    }
}

proc SAMPRcvdTableLoadFits {varname} {
    upvar $varname args

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdTableLoadFits: $args"
    }

    global current
    global samp

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    set url {}
    set imageid {}
    set name {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		url {set url [XMLUnQuote $val]}
		image-id {set imageid [XMLUnQuote $val]}
		name {set name [XMLUnQuote $val]}
	    }
	}
    }

    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdTableLoadFits: $url $imageid $name"
    }

    if {$url != {}} {
	MultiLoad
	LoadURLFits $url {} {}
    }
}

proc SAMPRcvdTableLoadVotable {varname} {
    upvar $varname args

    global samp
    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdTableLoadVotable: $args"
    }

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    set url {}
    set tabid {}
    set name {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		url {set url [XMLUnQuote $val]}
		table-id {set tabid [XMLUnQuote $val]}
		name {set name [XMLUnQuote $val]}
	    }
	}
    }

    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdTableLoadVotable: $url $tabid $name"
    }

    global icat
    if {$url != {}} {
	CATVOTURL $url $name $tabid
	if {$tabid != {}} {
	    set catid [lindex $icat(cats) end]
	    set samp(icat,$tabid) $catid
	    set samp(ocat,$catid) $tabid
	}
    }
}

proc SAMPRcvdTableHighlightRow {varname} {
    upvar $varname args

    global samp
    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdTableHighlightRow: $args"
    }

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    set url {}
    set tabid {}
    set row {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		url {set url [XMLUnQuote $val]}
		table-id {set tabid [XMLUnQuote $val]}
		row {set row [XMLUnQuote $val]}
	    }
	}
    }

    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdTableHighlightRow: $url $tabid $row"
    }

    if {$tabid != {} && $row != {}} {
	if {[info exists samp(icat,$tabid)]} {
	    CATSelectRows $samp(icat,$tabid) samp [expr $row+1]
	}
    }
}

proc SAMPRcvdTableSelectRowList {varname} {
    upvar $varname args

    global samp
    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdTableSelectRowList: $args"
    }

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    set url {}
    set tabid {}
    set rowlist {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		url {set url [XMLUnQuote $val]}
		table-id {set tabid [XMLUnQuote $val]}
		row-list {
		    foreach rr [XMLUnQuote $val] {
			lappend rowlist [expr $rr+1]
		    }
		}
	    }
	}
    }

    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdTableSelectRowList: $url $tabid $rowlist"
    }

    if {$tabid != {} && [llength $rowlist] != 0} {
	if {[info exists samp(icat,$tabid)]} {
	    CATSelectRows $samp(icat,$tabid) samp $rowlist
	}
    }
}

proc SAMPRcvdCoordPointAtSky {varname} {
    upvar $varname args

    global samp
    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdCoordPointAtSky: $args"
    }

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    set ra {}
    set dec {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		ra {set ra [XMLUnQuote $val]}
		dec {set dec [XMLUnQuote $val]}
	    }
	}
    }

    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdCoordPointAtSky: $ra $dec"
    }

    global current
    if {$ra != {} && $dec != {} && [$current(frame) has wcs celestial wcs]} {
	set samp(rcvd,lock) 1
	PanTo $ra $dec wcs fk5
	set samp(rcvd,lock) 0
    }
}

proc SAMPRcvdClientEnvGet {msgid varname} {
    upvar $varname args

    global samp
    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdClientEnvGet: $msgid $args"
    }

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    set name {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		name {set name [XMLUnQuote $val]}
	    }
	}
    }

    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdClientEnvGet: $name"
    }

    global env
    if {[catch {set rr $env($name)}]} {
	SAMPReply $msgid ERROR {} {} [lindex [split $errorInfo "\n"] 0]
	global errorInfo
	set errorInfo {}
    } else {
	SAMPReply $msgid OK $rr
    }
}

proc SAMPRcvdDS9Set {msgid varname safemode} {
    upvar $varname args

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdDS9Set: $msgid $args $safemode"
    }

    global current
    global samp

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    set url {}
    set cmd {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		url {set url [XMLUnQuote $val]}
		cmd {set cmd [XMLUnQuote $val]}
	    }
	}
    }

    set fn {}

    InitError samp
    if {$url != {}} {
	set fn [tmpnam {.samp}]
	GetFileURL $url fn
    }
    CommSet $fn $cmd $safemode
    if {$msgid != {}} {
	SAMPRcvdDS9SetReply $msgid
    }
}

proc SAMPRcvdDS9SetReply {msgid} {
    global ds9
    global icursor

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdDS9SetReply: $msgid"
    }

    global errorInfo
    if {$errorInfo != {} || $ds9(msg) != {}} {
	if {$ds9(msg) != {}} {
	    switch $ds9(msg,level) {
		info -
		warning {SAMPReply $msgid OK $ds9(msg)}
		error -
		fatal {SAMPReply $msgid ERROR {} {} $ds9(msg)}
	    }
	} else {
	    SAMPReply $msgid ERROR {} {} [lindex [split $errorInfo "\n"] 0]
	}
	InitError samp
    } else {
	SAMPReply $msgid OK
    }
}

proc SAMPRcvdDS9Get {msgid varname} {
    upvar $varname args

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdDS9Get: $args"
    }

    global current
    global samp

    # are we locked?
    if {$samp(send,lock)} {
	if {$debug(tcl,samp)} {
	    puts stderr "SAMP: ABORT Send locked"
	}
	return
    }

    set url {}
    set cmd {}

    foreach arg $args {
	foreach {key val} $arg {
	    switch -- $key {
		url {set url [XMLUnQuote $val]}
		cmd {set cmd [XMLUnQuote $val]}
	    }
	}
    }

    set fn [tmpnam {.samp}]
    InitError samp
    CommGet SAMPRcvdDS9GetReply $msgid $cmd $fn
}

proc SAMPRcvdDS9GetReply {msgid msg {fn {}}} {
    global ds9
    global icursor

    global debug
    if {$debug(tcl,samp)} {
	puts stderr "SAMPRcvdDS9GetReply: $msgid $msg $fn"
    }

    global errorInfo
    if {$errorInfo != {} || $ds9(msg) != {}} {
	if {$ds9(msg) != {}} {
	    switch $ds9(msg,level) {
		info -
		warning {SAMPReply $msgid OK $ds9(msg)}
		error -
		fatal {SAMPReply $msgid ERROR {} {} $ds9(msg)}
	    }
	} else {
	    SAMPReply $msgid ERROR {} {} [lindex [split $errorInfo "\n"] 0]
	}
	InitError samp
    } else {
	# be sure to white space any newlines, backslashes, and trim
	set value [string trim [string map {\n { } \\ {}} $msg]]

	# create url
	set url {}
	if {$fn != {}} {
	    set url "file://localhost/$fn"
	}

	SAMPReply $msgid OK $value $url
    }
}

proc SAMPDelTmpFiles {} {
    global ds9

    # delete any previous files
    foreach fn [glob -directory $ds9(tmpdir) -nocomplain {ds9*samp*}] {
	catch {file delete -force "$fn"}
    }
}

# Cmds

proc ProcessSAMPCmd {varname iname} {
    upvar $varname var
    upvar $iname i

    # we need to be realized
    ProcessRealizeDS9
    SAMPUpdate

    samp::YY_FLUSH_BUFFER
    samp::yy_scan_string [lrange $var $i end]
    samp::yyparse
    incr i [expr $samp::yycnt-1]
}

proc SAMPCmdSendImage {name} {
    global samp

    if {[info exists samp]} {
	foreach arg $samp(apps,image) {
	    foreach {key val} $arg {
		if {[string tolower $val] == $name} {
		    SAMPSendImageLoadFits $key
		    break
		}
	    }
	}
    } else {
	Error "SAMP: [msgcat::mc {not connected}]"
    }
}

proc SAMPCmdSendTable {name} {
    global samp

    if {[info exists samp]} {
	foreach arg $samp(apps,table) {
	    foreach {key val} $arg {
		if {[string tolower $val] == $name} {
		    SAMPSendTableLoadFits $key
		    break
		}
	    }
	}
    } else {
	Error "SAMP: [msgcat::mc {not connected}]"
    }
}    
