# -*- tcl -*- $

package provide xotcl::comm::connection 2.0

package require XOTcl 2.0

namespace eval ::xotcl::comm::connection {
    namespace import ::xotcl::*

    Class Connection -parameter {host port req socket handle}

    Connection proc make {r host port reuse reusedVar} {
	#my showCall
	my instvar openConnections
	upvar [self callinglevel] $reusedVar reused
	if {$reuse} {
	    set handle $host:$port-[$r set blocking]
	    #if {[array exists openConnections]} {parray openConnections}
	    if {![info exists openConnections($handle)]} {
		# there is no persistent connection, we create a new one
		set reused 0
		set openConnections($handle) \
		    [Connection new -host $host -port $port -req $r -handle $handle]
		#my showMsg "$openConnections($handle) CONNECTION add for $handle added"
	    } else {
		# there is a persistent connection
		set reused 1
		set c $openConnections($handle)
		$c instvar req
		#::puts stderr "$c CONNECTION reuse for $handle ($c) new req=$r"
		if {[info exists req]} {
		    # the persistent connection is active with some request $req
		    #::puts stderr "$c CONNECTION req $req already active"
		} else {
		    # the persistent connection is currently not active
		    $c set req $r
		}
	    }
	    return $openConnections($handle)
	} else {
	    set reused 0
	    return [Connection new -host $host -port $port -req $r]
	}
    }
    Connection proc removeHandle handle {
	#my showVars
	#puts stderr "***************** unsetting $handle ***************"
	if {[my exists openConnections($handle)]} {
	    my unset openConnections($handle)
	}
    }
    Connection instproc init args {  ;# the constructor creates the socket
	my set blocked {}
	next
	if {[my exists socket]} {
	    my set keepOpen 1
	} else {
	    my set keepOpen 0
	    if {[catch {my socket [socket -async [my host] [my port]]} msg]} {
		my set error $msg
		return
	    }
	}
	::fconfigure [my socket] -blocking false -buffersize 16384
    }
    #Connection instproc STATUS {ctx} {
    #  my instvar socket
    #  ::puts stderr "*** $ctx: $socket blocking=[::fconfigure $socket -blocking]"
    #}
    Connection instproc destroy {} { ;# the destructor closes the socket
	#my showCall
	if {[my exists handle]} {
	    #my showVars handle
	    # the connection was created via make
	    [self class] removeHandle [my set handle]
	    #::puts stderr "my CONNECTION close and destroy [my set handle]"
	} else {
	    #::puts stderr "my CONNECTION close and destroy"
	}
	# in cases of errors we might not have a socket yet
	if {[my exists socket]} {
	    close [my set socket]
	}
	next
    }
    Connection instproc translation {translation} {
	#showCall
	::fconfigure [my socket] -translation $translation
    }    
    Connection instproc importSSL args {
	#my showCall
	package require tls
	eval tls::import [my socket] $args
    }
    Connection instproc fconfigure args {
	#my showCall
	eval ::fconfigure [my socket] $args
    }    
    Connection instproc event {type r method} {
	#my showCall
	my instvar req blocked
	# is the request in the argument list the currently active request?
	if {[info exists req] && $r == $req} {
	    # a request can overwrite its active request
	    if {$method eq ""} {
		::fileevent [my socket] $type ""
		#my showMsg "CONNECTION clear for [my socket]"
	    } else {
		#my showMsg "CONNECTION register for [my socket]"
		::fileevent [my socket] $type [list $r $method]
	    }
	} else {
	    #my showMsg "event BLOCKING current request=$req, new=$r $method"
	    #my showMsg "event BLOCKING rd=[::fileevent [my socket] readable]"
	    #my showMsg "event BLOCKING wr=[::fileevent [my socket] writable]"
	    #my showMsg "event BLOCKING bl=$blocked"
	    ::lappend blocked $r $type $method
	}
    }
    Connection instproc hold {} {
	my set continueCmd [list ::fileevent [my socket] readable \
				[::fileevent [my socket] readable]]
	::fileevent $socket readable {}
	#my showVars continueCmd
    }
    Connection instproc resume {} {
	#my showCall
	if {[my exists continueCmd]} {
	    eval [my set continueCmd]
	    my unset continueCmd
	}
    }

    Connection instproc puts {string} {
	#my showCall
	if {[catch {::puts [my socket] $string} msg]} {
	    ::puts stderr message=$msg
	}
    }
    Connection instproc puts-nonewline {string} {
	#my showCall
	if {[catch {::puts -nonewline [my socket] $string} msg]} {
	    ::puts stderr message=$msg
	}
    }

    Connection instproc gets {var} {
	#my showCall
	upvar [self callinglevel] $var result
	if {[catch {set n [::gets [my socket] result]} msg]} {
	    my set error $msg 
            #my showMsg "CONNECTION error: $msg"
	    return 0
	}
	#my showMsg "n=$n, result=<$result>"
	return $n
    }
    Connection instproc read {} {
      # my showCall
      my instvar socket
      # Prior to the fix for Tcl bug #3401422, there was a regression
      # in Tcl 8.6 beta versions, causing a previously nonblocking
      # socket created using [socket -async] to return to a blocking
      # state. This required a manual overwrite (see below). Starting
      # with the 8.6 release candidates, this is obsolete.
      #
      # ::fconfigure $socket -blocking 0
      if {[catch {set result [::read $socket [::fconfigure $socket -buffersize]]} msg]} {
	    my set error $msg 
	    return ""
	}
      return $result
    }
    Connection instproc readSize {length} {
	if {[catch {set result [::read [my socket] $length]} msg]} {
	    my set error $msg 
	    return 0
	}
	return $result
    }
    Connection instproc flush {} {
	#my showCall
	if {[catch {::flush [my socket]} msg]} {
	    my set error $msg 
	}
    }
    Connection instproc eof {} {
	#my showCall
	if {[my exists error]} {
	    return 1
	} else {
	    return [::eof [my socket]]
	}
    }
    Connection instproc close {} {
	#my showCall
	my instvar req socket blocked
	if {![info exists socket]} return ;# error during connection open
	::fileevent $socket readable ""
	::fileevent $socket writable ""
	$req freeConnection
	if {[my exists persistent]} {
	    my flush
	    #::puts stderr "[self] PERSISTENT CONNECTION wants close"
	    if {$blocked eq ""} {
		::fileevent $socket readable [list [self] destroy]
		unset req
	    } else {
		#my showVars blocked
		set req [lindex $blocked 0]
		set type [lindex $blocked 1]
		set method [lindex $blocked 2]
		set blocked [lrange $blocked 3 end]
		#my showMsg "in persistent connection unblock $type [list $req $method]"
		::fileevent $socket $type [list $req $method]
	    }
	} else {
	    #my showMsg "in nonpersistent connection blocked=$blocked"
	    if {$blocked ne ""} {
		set req [lindex $blocked 0]
		set type [lindex $blocked 1]
		set method [lindex $blocked 2]
		set nblocked [lrange $blocked 3 end]
		close $socket
		unset socket
		if {[my exists handle]} {
		    [self class] removeHandle [my set handle]
		}
		if {[my exists error]} {
		    #my showMsg "UNSETTING ERROR -----------"
		    my unset error
		}
		my init
		set blocked $nblocked
		::fileevent $socket $type [list $req $method]
		#my showMsg "REANIMATE $socket $type [list $req $method]"
		#my showVars
	    } else {
		#my showMsg "Nothing blocked: readable=[::fileevent $socket readable]"

		my destroy
	    }
	}
    }
    Connection instproc makePersistent {p} {
	if {$p} {
	    my set persistent 1
	} else {
	    if {[my exists persistent]} {
		my unset persistent
		#my showMsg "no longer persistent"
	    }
	}
    }

    namespace export Connection
}

namespace import ::xotcl::comm::connection::*

if {[info command bgerror] eq ""} {
    proc bgerror {msg} { puts stderr "******* bgerror $msg $::errorInfo*****"}
}
