# $Id: si.tcl 1914 2010-02-05 17:07:31Z sergei $
#
# Stream Initiation (XEP-0095) implementation
#

###############################################################################

namespace eval si {
    set transport(list) {}
}

set ::NS(si) http://jabber.org/protocol/si

###############################################################################
###############################################################################

proc si::newout {xlib jid} {
    variable streams

    set id [rand 1000000000]
    while {[info exists streams(out,$xlib,$jid,$id)]} {
	set id [rand 1000000000]
    }
    set streamid 0
    set stream [namespace current]::0
    while {[info exists $stream]} {
	set stream [namespace current]::[incr streamid]
    }
    upvar #0 $stream state

    set state(xlib) $xlib
    set state(jid) $jid
    set state(id) $id
    set streams(out,$xlib,$jid,$id) $stream

    return $stream
}

proc si::freeout {stream} {
    variable streams
    upvar #0 $stream state

    catch {
	set xlib $state(xlib)
	set jid $state(jid)
	set id $state(id)

	unset state
	unset streams(out,$xlib,$jid,$id)
    }
}

###############################################################################

proc si::newin {xlib jid id} {
    variable streams

    if {[info exists streams(in,$xlib,$jid,$id)]} {
	return -code error
    }

    set streamid 0
    set stream [namespace current]::0
    while {[info exists $stream]} {
	set stream [namespace current]::[incr streamid]
    }
    upvar #0 $stream state

    set state(xlib) $xlib
    set state(jid) $jid
    set state(id) $id
    set streams(in,$xlib,$jid,$id) $stream

    return $stream
}

proc si::in {xlib jid id} {
    variable streams

    return $streams(in,$xlib,$jid,$id)
}

proc si::freein {stream} {
    variable streams
    upvar #0 $stream state

    catch {
	set xlib $state(xlib)
	set jid $state(jid)
	set id $state(id)

	unset state
	unset streams(in,$xlib,$jid,$id)
    }
}

###############################################################################
###############################################################################

proc si::connect {stream chunk_size mimetype profile profile_el command} {
    variable transport
    upvar #0 $stream state

    set trans [lsort -unique -index 1 $transport(list)]
    set options {}
    foreach t $trans {
	set name [lindex $t 0]
	if {![info exists transport(allowed,$name)] || \
		$transport(allowed,$name)} {
	    lappend options "" $transport(oppos,$name)
	}
    }

    set fields [::xmpp::data::formField field \
			-var stream-method \
			-type list-single \
			-options $options]

    set feature \
	[::xmpp::xml::create feature \
	     -xmlns http://jabber.org/protocol/feature-neg \
	     -subelement [::xmpp::data::form $fields]]

    set_status [::msgcat::mc "Opening SI connection"]

    ::xmpp::sendIQ $state(xlib) set \
	-query [::xmpp::xml::create si \
		    -xmlns $::NS(si) \
		    -attrs [list id $state(id) \
				 mime-type $mimetype \
				 profile $profile] \
		    -subelement $profile_el \
		    -subelement $feature] \
	-to $state(jid) \
	-command [list si::connect_response $stream $chunk_size \
					    $profile $command]
}

###############################################################################

proc si::connect_response {stream chunk_size profile command status xml} {
    variable transport
    upvar #0 $stream state

    if {![info exists state(id)]} {
	# TODO: It would be good to send some error message to a receiver
	# (but it is not supported by the protocol).
	uplevel #0 $command [list [list 0 [::msgcat::mc "File transfer aborted"]]]
	return
    }

    if {$status != "ok"} {
	uplevel #0 $command [list [list 0 [error_to_string $xml]]]
	return
    }

    ::xmpp::xml::split $xml tag xmlns attrs cdata subels

    set trans [lsort -unique -index 1 $transport(list)]
    set options {}
    foreach t $trans {
	set name [lindex $t 0]
	if {![info exists transport(allowed,$name)] || \
		$transport(allowed,$name)} {
	    lappend options $transport(oppos,$name)
	}
    }

    set opts {}

    foreach item $subels {
	::xmpp::xml::split $item stag sxmlns sattrs scdata ssubels
	if {[string equal $sxmlns $profile]} {
	    # TODO
	} elseif {[string equal $sxmlns \
				http://jabber.org/protocol/feature-neg]} {
	    set opts [parse_negotiation_res $item]
	}
    }

    if {[llength $opts] == 1 && [lcontain $options [lindex $opts 0]]} {
	set name [lindex $opts 0]
	set state(transport) $name
	eval $transport(connect,$name) [list $stream $chunk_size $command]
	return
    }
    uplevel #0 $command \
	    [list [list 0 [::msgcat::mc "Stream method negotiation failed"]]]
}

###############################################################################

proc si::send_data {stream data command} {
    variable transport
    upvar #0 $stream state

    eval $transport(send,$state(transport)) [list $stream $data $command]
}

###############################################################################

proc si::close {stream} {
    variable transport
    upvar #0 $stream state

    eval $transport(close,$state(transport)) [list $stream]
    set_status [::msgcat::mc "SI connection closed"]
}

###############################################################################
###############################################################################

proc si::set_readable_handler {stream handler} {
    upvar #0 $stream state

    set state(readable_handler) $handler
}

proc si::set_closed_handler {stream handler} {
    upvar #0 $stream state

    set state(closed_handler) $handler
}

###############################################################################

proc si::recv_data {stream data} {
    upvar #0 $stream state

    debugmsg si "RECV_DATA [list $state(id) $data]"

    append state(data) $data
    eval $state(readable_handler) [list $stream]
}

###############################################################################

proc si::read_data {stream} {
    upvar #0 $stream state

    set data $state(data)
    set state(data) {}
    return $data
}

###############################################################################

proc si::closed {stream} {
    upvar #0 $stream state

    if {[info exists state(closed_handler)]} {
	eval $state(closed_handler) [list $stream]
    }
}

###############################################################################

proc si::parse_negotiation {xml} {
    ::xmpp::xml::split $xml tag xmlns attrs cdata subels

    lassign [::xmpp::data::findForm $subels] type form
    set fields [::xmpp::data::parseForm $form]

    foreach {tag field} $fields {
	switch -- $tag {
	    field {
		lassign $field var type label desc required options values media
		if {[string equal $var stream-method]} {
		    set soptions {}
		    foreach {olabel ovalue} $options {
			lappend soptions $ovalue
		    }
		    return $soptions
		}
	    }
	}
    }

    return {}
}

proc si::parse_negotiation_res {xml} {
    ::xmpp::xml::split $xml tag xmlns attrs cdata subels

    lassign [::xmpp::data::findForm $subels] type form
    set fields [::xmpp::data::parseSubmit $form]

    foreach {tag field} $fields {
	switch -- $tag {
	    field {
		lassign $field var type label values
		if {[string equal $var stream-method]} {
		    return $values
		}
	    }
	}
    }

    return {}
}

###############################################################################

proc si::set_handler {xlib from xml args} {
    variable profiledata
    variable transport

    ::xmpp::xml::split $xml tag xmlns attrs cdata subels

    set iqid [::xmpp::xml::getAttr $args -id]
    set id [::xmpp::xml::getAttr $attrs id]
    set mimetype [::xmpp::xml::getAttr $attrs mime-type]
    set profile [::xmpp::xml::getAttr $attrs profile]
    set stream {}
    set profile_res {}

    set lang [::xmpp::xml::getAttr $args -lang en]

    if {[info exists profiledata($profile)]} {
	foreach item $subels {
	    ::xmpp::xml::split $item stag sxmlns sattrs scdata ssubels

	    if {[string equal $sxmlns $profile]} {
		return [$profiledata($profile) \
				$xlib $from $lang $id $mimetype $item \
				[namespace code [list set_handler_cont $xlib $from $iqid $lang $id $subels]]]
	    }
	}
    } else {
	# bad-profile
	return [list error modify bad-request]
    }
}

proc si::set_handler_cont {xlib from iqid lang id subels status xml} {
    variable transport

    if {$status == "error"} {
        ::xmpp::sendIQ $xlib error \
                       -error $xml \
                       -to $from \
                       -id $iqid
	return
    }

    foreach item $subels {
	::xmpp::xml::split $item tag xmlns attrs cdata subels

	if {[string equal $xmlns http://jabber.org/protocol/feature-neg]} {
	    set options [parse_negotiation $item]

	    set trans [lsort -unique -index 1 $transport(list)]
	    set myoptions {}
	    foreach t $trans {
		set name [lindex $t 0]
		if {![info exists transport(allowed,$name)] || \
			$transport(allowed,$name)} {
		    lappend myoptions $transport(oppos,$name)
		}
	    }

	    foreach opt $options {
		if {[lsearch -exact $myoptions $opt] >= 0} {
		    set stream $opt
		    break
		}
	    }
	}
    }

    if {![info exists stream]} {
        ::xmpp::sendIQ $xlib error \
                       -error [::xmpp::stanzaerror::error modify bad-request] \
                       -to $from \
                       -id $iqid
	return
    }

    set res_elements {}
    if {$xml != {}} {
	lappend res_elements $xml
    }

    set fields [list stream-method [list $opt]]
    lappend res_elements \
	    [::xmpp::xml::create feature \
		    -xmlns http://jabber.org/protocol/feature-neg \
		    -subelement [::xmpp::data::submitForm $fields]]

    set res [::xmpp::xml::create si \
		    -xmlns $::NS(si) \
		    -subelements $res_elements]

    ::xmpp::sendIQ $xlib result \
                   -query $res \
                   -to $from \
                   -id $iqid
}

::xmpp::iq::register set * $::NS(si) si::set_handler

###############################################################################
###############################################################################

proc si::register_transport {name oppos prio default connect send close} {
    variable transport

    lappend transport(list) [list $name $prio]
    set transport(oppos,$name) $oppos
    set transport(connect,$name) $connect
    set transport(send,$name) $send
    set transport(close,$name) $close
    if {$default == "enabled"} {
	set transport(default,$name) 1
    } else {
	set transport(default,$name) 0
    }
}

###############################################################################

proc si::register_profile {profile handler} {
    variable profiledata
    set profiledata($profile) $handler
}

###############################################################################

proc si::setup_customize {} {
    variable transport

    set trans [lsort -unique -index 1 $transport(list)]

    foreach t $trans {
	lassign $t name prio

	custom::defvar transport(allowed,$name) $transport(default,$name) \
	[::msgcat::mc "Enable SI transport %s." $name] \
	-type boolean -group {Stream Initiation}
    }
}

hook::add postload_hook si::setup_customize

###############################################################################

namespace eval si {
    plugins::load [file join plugins si] -uplevel 1
}

###############################################################################

