# -*- tcl -*-
# ### ### ### ######### ######### #########
##
# Transfer class built on top of the basic facilities. Accepts many
# transfer requests, any time, and executes them serially. Each
# request has its own progress and completion commands.
#
# Note: The output channel used is part of the queue, and not
#       contained in the transfer requests themselves. Otherwise
#       we would not need a queue and serialized execution.
#
# Instances also have a general callback to report the instance status
# (#pending transfer requests, busy).

# ### ### ### ######### ######### #########
## Requirements

package require transfer::copy ; # Basic transfer facilities
package require struct::queue  ; # Request queue
package require snit           ; # OO system
package require Tcl 8.5 9

namespace eval ::transfer::copy::queue {
    namespace import ::transfer::copy::options
    namespace import ::transfer::copy::doChan
    namespace import ::transfer::copy::doString
}

# ### ### ### ######### ######### #########
## Implementation

snit::type ::transfer::copy::queue {
    # ### ### ### ######### ######### #########
    ## API

    option -on-status-change {}

    constructor {thechan args} {}
    method put     {request} {}
    method busy    {} {}
    method pending {} {}

    # ### ### ### ######### ######### #########
    ## Implementation

    constructor {thechan args} {
	if {![llength [file channels $chan]]} {
	    return -code error "Channel \"$chan\" does not exist"
	}

	set chan  $thechan
	set queue [struct::queue ${selfns}::queue]
	set busy  0

	$self configurelist $args
	return
    }

    destructor {
	if {$queue eq ""} return
	$queue destroy
	return
    }

    method put {request} {
	# Request syntax: type dataref ?options?
	# Accepted options are those of 'transfer::transmit::copy',
	# etc.

	# We parse out the completion callback so that we can use it
	# directly. This also checks the request for basic validity.

	if {[llength $request] < 2} {
	    return -code error "Bad request: Not enough elements"
	}

	set type [lindex $request 0]
	switch -exact -- $type {
	    chan - string {}
	    default {
		return -code error "Bad request: Unknown type \"$type\", expected chan, or string"
	    }
	}

	set options [lrange $request 2 end]
	if {[catch {
	    options $chan $options opts
	} res]} {
	    return -code error "Bad request: $res"
	}

	set ref [lindex $request 1]

	# We store the fully parsed request. Later
	# we call lower-level copy functionality
	# which avoids a reparsing.

	$queue put [list $type $ref [array get opts]]

	# Start the engine executing transfers in the background, if
	# it is not already running.

	if {!$busy} {
	    after 0 [mymethod Transfer]
	}

	$self ReportStatus
	return
    }

    method busy {} {
	return $busy
    }

    method pending {} {
	return [$queue size]
    }

    # ### ### ### ######### ######### #########
    ## Internal helper commands

    method Transfer {} {
	# Get the next pending request. It is already fully-parsed.

	foreach {type ref o} [$queue get] break
	array set opts $o

	# Save the actual completion callback and redirect the
	# completion of the copy operation to ourselves for proper
	# management.

	set opts(-command) [mymethod \
		Done $opts(-command)]

	# Start the transfer. We catch this as it can fail immediately
	# (example: string-type copy and not enough data). We go
	# through 'Done' for the reporting of such errors to avoid
	# forgetting all the other management stuff (like the engine
	# forced to stop).

	set busy 1
	$self ReportStatus

	switch -exact -- $type {
	    chan {
		set code [catch {
		    doChan $ref $chan opts
		} res]
	    }
	    string {
		set code [catch {
		    doString $ref $chan opts
		} res]
	    }
	}

	if {$code} {
	    $self Done $command 0 $res
	}

	return
    }

    method Done {command args} {
	# args is either (n)
	#             or (n errormessage)

	# A transfer ending in an error causes the instance to stop
	# processing requests. I.e. all requests waiting after the
	# failed one are not executed anymore.

	if {[llength $args] == 2} {
	    set busy 0
	    $self ReportStatus
	    $self Notify $command $args
	    return
	}

	# Depending on the status of the queue of pending requests we
	# either trigger the start of the next transfer, or stop the
	# engine. The completion of the current transfer however is
	# unconditionally reported through its completion callback.

	if {[$queue size]} {
	    after 0 [mymethod Transfer]
	} else {
	    set busy 0
	    $self ReportStatus
	}

	$self Notify $command $args
	return
    }

    method ReportStatus {} {
	if {![llength $options(-on-status-change)]} return
	uplevel #0 [linsert $options(-on-status-change) end $self [$queue size] $busy]
	return
    }

    method Notify {cmd alist} {
	foreach a $args {lappend cmd $a}
	uplevel #0 $cmd
    }

    # ### ### ### ######### ######### #########
    ## Data structures
    ## - Channel the transfered data is written to
    ## - Queue of pending requests.

    variable chan  {}
    variable queue {}
    variable busy  0

    ##
    # ### ### ### ######### ######### #########
}

# ### ### ### ######### ######### #########
## Ready

package provide transfer::copy::queue 0.2

