# $Id$

namespace eval auth {
    set ns http://tkabber.jabber.ru/otr
    set otrdir [file join $::configdir otr]
    if {![file exists $otrdir]} {
	file mkdir $otrdir
	catch {file attributes $otrdir -permissions 0700}
    }
    # State variable with all auth data
    set Auth [dict create]

    hook::add finload_hook [namespace current]::restore
}

proc auth::store {auth} {
    variable otrdir

    if {![catch {
	    set fd [open [file join $otrdir auth.xml.new] w]
	    fconfigure $fd -encoding utf-8

	    puts $fd {<?xml version="1.0" encoding="UTF-8"?>}
	    puts $fd [serialize_auth $auth]

	    close $fd
	} res]} {
	file rename -force -- [file join $otrdir auth.xml.new] \
			      [file join $otrdir auth.xml]
    }
}

proc auth::serialize_auth {auth} {
    variable ns

    set subtags {}
    dict for {jid val} $auth {
	set items [serialize_auth_items $jid $val]
	::xmpp::xml::split $items tag xmlns attrs cdata subels
	if {[llength $subels] > 0} {
	    lappend subtags $items
	}
    }

    ::xmpp::xml::toTabbedText [::xmpp::xml::create authentication \
					-xmlns $ns \
					-subelements $subtags]
}

proc auth::serialize_auth_items {jid items} {
    variable ns

    set subtags {}
    dict for {item val} $items {
	lappend subtags [serialize_auth_item $item $val]
    }

    ::xmpp::xml::create profile -xmlns $ns \
				-attrs [list jid $jid] \
				-subelements $subtags
}

proc auth::serialize_auth_item {item val} {
    variable ns

    lassign $item jid fingerprint

    ::xmpp::xml::create item -xmlns $ns \
			     -attrs [list jid $jid \
					  fingerprint $fingerprint \
					  auth $val]
}

proc auth::restore {} {
    variable otrdir
    variable Auth

    if {[catch {set fd [open [file join $otrdir auth.xml]]}]} {
	return [dict create]
    }

    set data [read $fd]
    close $fd

    set Auth [deserialize_auth [lindex [::xmpp::xml::parseData $data] 0]]
}

proc auth::deserialize_auth {xmlel} {
    variable ns

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

    if {$tag ne "authentication"} {
	return -code error "The root element must be 'authentication'"
    }
    if {$xmlns ne $ns} {
	return -code error "The namespace must be '$ns'"
    }
    set auth [dict create]
    foreach subel $subels {
	::xmpp::xml::split $subel tag1 xmlns1 attrs1 cdata1 subels1
	dict set auth [::xmpp::xml::getAttr $attrs1 jid] [deserialize_items $subels1]
    }
    set auth
}

proc auth::deserialize_items {xmlels} {
    set items [dict create]
    foreach xmlel $xmlels {
	::xmpp::xml::split $xmlel tag xmlns attrs cdata subels
	dict set items [list [::xmpp::xml::getAttr $attrs jid] \
			     [::xmpp::xml::getAttr $attrs fingerprint]] \
		       [::xmpp::xml::getAttr $attrs auth]
    }
    set items
}

proc auth::set_auth {vauth myjid jid fingerprint value} {
    upvar $vauth auth

    set myjid [::xmpp::jid::normalize $myjid]
    set jid [::xmpp::jid::normalize $jid]
    if {![dict exists $auth $myjid]} {
	dict set auth $myjid [dict create]
    }
    dict set auth $myjid [list $jid $fingerprint] $value
    set auth
}

proc auth::del_auth {auth myjid jid fingerprint} {
    set myjid [::xmpp::jid::normalize $myjid]
    set jid [::xmpp::jid::normalize $jid]
    if {[dict exists $auth $myjid]} {
	dict unset auth $myjid [list $jid $fingerprint]
    }
    if {[llength [dict get $auth $myjid]] == 0} {
	dict unset auth $myjid
    }
    set auth
}

proc auth::get_auth {auth myjid jid fingerprint} {
    set myjid [::xmpp::jid::normalize $myjid]
    set jid [::xmpp::jid::normalize $jid]
    if {[catch {dict get $auth $myjid [list $jid $fingerprint]} res]} {
	return 0
    } else {
	return $res
    }
}

proc auth::edit_auth_dialog {} {
    variable Auth
    variable authjid
    variable lastsort

    set w .otreditauth

    if {[winfo exists $w]} {
	destroy $w
    }

    Dialog $w -title [::msgcat::mc "Edit OTR authentication"] \
	      -separator 1 -anchor e -default 0 -cancel 1

    set f [$w getframe]

    $w add -text [::msgcat::mc "Apply"] \
	   -state disabled \
	   -command [namespace code [list apply_edit_auth_changes $w $f.items.listbox]]
    $w add -text [::msgcat::mc "Close"] \
	   -command [list destroy $w]

    set myjids [lsort [dict keys $Auth]]
    set authjid [lindex $myjids 0]
    set connections [connections]
    if {[llength $connections] > 0} {
	set myjid [::xmpp::jid::normalize [::xmpp::jid::removeResource [lindex $connections 0]]]
	if {[dict exists $Auth $myjid]} {
	    set authjid $myjid
	}
    }
    trace add variable [namespace current]::authjid write \
	  [namespace code [list fill_mclistbox $w $f.items.listbox]]

    bind $w <Destroy> [list unset -nocomplain [namespace current]::authjid]

    if {[llength $myjids] == 0} {
	set myjids {""}
    }

    label $f.lmyjid -text [::msgcat::mc "Your JID: "]
    tk_optionMenu $f.myjid [namespace current]::authjid {*}$myjids

    grid $f.lmyjid -row 0 -column 0 -sticky e
    grid $f.myjid  -row 0 -column 1 -sticky ew

    set sw [ScrolledWindow $f.items]

    ::mclistbox::mclistbox $sw.listbox \
	-resizeonecolumn 1 \
	-labelanchor w \
	-width 100 \
	-height 16

    grid $sw -row 1 -column 0 -columnspan 2 -sticky nsew
    $sw setwidget $sw.listbox

    set lastsort ""

    bind $sw.listbox <<ContextMenu>> \
	"[namespace current]::select_and_popup_menu [list [double% $w] [double% $sw.listbox]] \
	     \[[double% $sw.listbox] nearest \[::mclistbox::convert %W -y %y\]\]"

    bindscroll $sw $sw.listbox

    set l $sw.listbox

    set label(N) " [::msgcat::mc #] "
    set label(jid) " [::msgcat::mc JID] "
    set label(fingerprint) " [::msgcat::mc Fingerprint] "
    set label(auth) " [::msgcat::mc Authenticated] "
    set label(del) " [::msgcat::mc Delete] "
    foreach name {N jid fingerprint auth del} {
	$l column add $name -label $label($name)
	$l label bind $name <ButtonPress-1> [namespace code [list sort %W [double% $name]]]
    }
    $l column add filler -label "" -width 0
    $l configure -fillcolumn filler

    fill_mclistbox $w $l

    $w draw
}

proc auth::fill_mclistbox {w l args} {
    variable Auth
    variable authjid

    if {![winfo exists $l]} return

    if {[dict exists $Auth $authjid]} {
	set items [dict get $Auth $authjid]
    } else {
	set items {}
    }

    $l delete 0 end

    foreach name {N jid fingerprint auth del} {
	set width($name) [string length [$l column cget $name -label]]
    }

    set row 0
    dict for {key val} $items {
	lassign $key jid fingerprint
	if {$val} {
	    set data(auth) " [::msgcat::mc Yes] "
	} else {
	    set data(auth) " [::msgcat::mc No] "
	}

	set data(N) " [incr row] "
	set data(jid) " $jid "
	set data(fingerprint) " $fingerprint "

	foreach name {N jid fingerprint auth} {
	    if {$width($name) < [string length $data($name)]} {
		set width($name) [string length $data($name)]
	    }
	}

	$l insert end [list $data(N) $data(jid) $data(fingerprint) $data(auth) ""]
    }

    foreach name {N jid fingerprint auth del} {
	$l column configure $name -width $width($name)
    }

    # The filled listbox is unchanged, so disable apply button
    $w itemconfigure 0 -state disabled
}

proc auth::sort {w tag} {
    variable lastsort

    set data [$w get 0 end]
    set index [lsearch -exact [$w column names] $tag]
    if {$lastsort != $tag} {
	set result [lsort -dictionary -index $index $data]
	set lastsort $tag
    } else {
	set result [lsort -decreasing -dictionary -index $index $data]
	set lastsort ""
    }
    set result1 {}
    set i 0
    foreach row $result {
	lappend result1 [lreplace $row 0 0 " [incr i] "]
    }
    $w delete 0 end
    $w insert end {*}$result1
}

proc auth::select_and_popup_menu {w l index} {
    $l selection clear 0 end
    $l selection set $index

    if {[winfo exists [set m .otrauthpopupmenu]]} {
	destroy $m
    }
    menu $m -tearoff 0

    $m add command -label [::msgcat::mc "Set authenticated"] \
		   -command [namespace code [list set_authenticated $w $l $index 1]]
    $m add command -label [::msgcat::mc "Unset authenticated"] \
		   -command [namespace code [list set_authenticated $w $l $index 0]]
    $m add command -label [::msgcat::mc "Mark for deletion"] \
		   -command [namespace code [list set_delete $w $l $index 1]]
    $m add command -label [::msgcat::mc "Unmark for deletion"] \
		   -command [namespace code [list set_delete $w $l $index 0]]
    $m add command -label [::msgcat::mc "Copy to clipboard"] \
		   -command [namespace code [list copy_to_clipboard $w $l $index]]

    tk_popup $m [winfo pointerx .] [winfo pointery .]
}

proc auth::copy_to_clipboard {w l index} {
    set data [$l get $index]
    clipboard clear
    clipboard append [string range [lindex $data 1] 1 end-1]
    clipboard append " "
    clipboard append [string range [lindex $data 2] 1 end-1]
}

proc auth::set_authenticated {w l index val} {
    if {$val} {
	set auth " [::msgcat::mc Yes] "
    } else {
	set auth " [::msgcat::mc No] "
    }
    set data [lreplace [$l get $index] 3 3 $auth]
    $l delete $index
    $l insert $index $data
    $l selection set $index
    $w itemconfigure 0 -state normal
    $w itemconfigure 1 -text [::msgcat::mc "Cancel"]
}

proc auth::set_delete {w l index val} {
    if {$val} {
	set del " [::msgcat::mc Yes] "
    } else {
	set del ""
    }
    set data [lreplace [$l get $index] 4 4 $del]
    $l delete $index
    $l insert $index $data
    $l selection set $index
    $w itemconfigure 0 -state normal
    $w itemconfigure 1 -text [::msgcat::mc "Cancel"]
}

proc auth::apply_edit_auth_changes {w l} {
    variable Auth
    variable authjid

    $w itemconfigure 0 -state disabled
    $w itemconfigure 1 -text [::msgcat::mc "Close"]

    set data [$l get 0 end]

    set items [dict create]
    foreach item $data {
	lassign $item i jid fingerprint auth del
	
	if {$del eq " [::msgcat::mc Yes] "} continue

	if {$auth eq " [::msgcat::mc Yes] "} {
	    set auth1 1
	} else {
	    set auth1 0
	}
	dict set items [list [string range $jid 1 end-1] \
			     [string range $fingerprint 1 end-1]] $auth1
    }
    dict set Auth $authjid $items
    store $Auth
    [namespace parent]::reset_auth $authjid
    fill_mclistbox $w $l
}

