# auth.tcl --
#
#       This file is a part of Off-the-record messaging plugin for
#       the Tkabebr XMPP client. It implements keeping track of
#       authenticated OTR buddies.

namespace eval auth {
    variable ns http://tkabber.jabber.ru/otr
    variable otrdir [file join $::configdir otr]
    if {![file exists $otrdir]} {
        file mkdir $otrdir
        catch {file attributes $otrdir -permissions 0700}
    }
    # State variable with all auth data
    variable 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

    set w .otreditauth

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

    Dialog $w -title [::msgcat::mc "Edit OTR authentication"] \
              -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]

    set l [::mclistbox::mclistbox $sw.listbox -width 100 -height 16]

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

    bind $l <<ContextMenu>> \
         [namespace code [list select_and_popup_menu \
                               [double% $w] [double% $l] %x %y]]

    bindscroll $sw $l

    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]
    $l column add N -label $label(N)
    foreach name {jid fingerprint auth del} {
        $l column add $name -label $label($name) \
                            -image search/sort/noArrow \
                            -command [list search::Sort $l $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]]
        incr width($name) 5
    }

    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)] + 2} {
                set width($name) [string length $data($name)]
                incr width($name) 2
            }
        }

        $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::select_and_popup_menu {w l x y} {
    set index [$l find $x $y]
    if {$index < 0} return

    $l sel clear 0 end
    $l sel 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 [lindex $data 1]
    clipboard append " "
    clipboard append [lindex $data 2]
}

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 sel 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 sel 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 $jid $fingerprint] $auth1
    }
    dict set Auth $authjid $items
    store $Auth
    [namespace parent]::reset_auth $authjid
    fill_mclistbox $w $l
}

# vim:ft=tcl:ts=8:sw=4:sts=4:et
