# $Id: annotations.tcl 1389 2008-03-07 10:28:05Z sergei $
#
# Annotations (XEP-0145) support
#

namespace eval annotations {
    # variable to store roster notes
    array set notes {}

    set ::NS(rosternotes) "storage:rosternotes"
}

proc annotations::free_notes {connid} {
    variable notes

    array unset notes $connid,*
}

hook::add disconnected_hook [namespace current]::annotations::free_notes

proc annotations::request_notes {connid} {
    variable NS
    variable notes

    private::retrieve [list [jlib::wrapper:createtag storage \
				 -vars [list xmlns $::NS(rosternotes)]]] \
	-command [list [namespace current]::process_notes $connid] \
	-connection $connid
}

hook::add connected_hook [namespace current]::annotations::request_notes

proc annotations::process_notes {connid res child} {
    variable notes

    if {$res != "OK"} return

    free_notes $connid

    foreach xmldata $child {
	jlib::wrapper:splitxml $xmldata tag vars isempty cdata children

	if {[jlib::wrapper:getattr $vars xmlns] == $::NS(rosternotes)} {
	    foreach note $children {
		create_note $connid $note
	    }
	}
    }
}

proc annotations::create_note {connid xmldata args} {
    variable notes

    set merge 0
    foreach {opt val} $args {
	switch -- $opt {
	    -merge { set merge $val }
	    default {
		return -code error "Bad option \"$opt\":\
		    must be -merge"
	    }
	}
    }

    jlib::wrapper:splitxml $xmldata tag vars isempty cdata children

    set jid [jlib::wrapper:getattr $vars jid]
    set cdate [jlib::wrapper:getattr $vars cdate]
    set mdate [jlib::wrapper:getattr $vars mdate]

    if {![catch { scan_time $cdate } cdate]} {
	set cdate [clock seconds]
    }
    if {![catch { scan_time $mdate } mdate]} {
	set cdate [clock seconds]
    }

    if {!$merge || [more_recent $connid $jid $cdate $mdate]} {
	set notes($connid,jid,$jid)   $jid
	set notes($connid,cdate,$jid) $cdate
	set notes($connid,mdate,$jid) $mdate
	set notes($connid,note,$jid)  $cdata
	return 1
    } else {
	return 0
    }
}

proc annotations::scan_time {timestamp} {
    if {[regexp {(.*)T(.*)Z} $timestamp -> date time]} {
	return [clock scan "$date $time" -gmt true]
    } else {
	return [clock scan $timestamp -gmt true]
    }
}

proc annotations::more_recent {connid jid cdate mdate} {
    variable notes

    if {![info exists notes($connid,jid,$jid)]} {
	return 1
    } elseif {[info exists notes($connid,mdate,$jid)]} {
	return [expr {$mdate > $notes($connid,mdate,$jid)}]
    } elseif {[info exists notes($connid,cdate,$jid)]} {
	return [expr {$cdate > $notes($connid,cdate,$jid)}]
    } else {
	return 1
    }
}

proc annotations::cleanup_and_store_notes {connid args} {
    variable notes

    set roster_jids {}
    foreach rjid [roster::get_jids $connid] {
	lappend roster_jids [node_and_server_from_jid $rjid]
    }

    foreach idx [array names notes $connid,jid,*] {
	set jid $notes($idx)
	if {[lsearch -exact $roster_jids $jid] < 0 || \
		![info exists notes($connid,note,$jid)] || \
		$notes($connid,note,$jid) == ""} {
	    catch { unset notes($connid,jid,$jid) }
	    catch { unset notes($connid,cdate,$jid) }
	    catch { unset notes($connid,mdate,$jid) }
	    catch { unset notes($connid,note,$jid) }
	}
    }

    eval [list store_notes $connid] $args
}

proc annotations::serialize_notes {connid} {
    variable notes

    set notelist {}
    foreach idx [array names notes $connid,jid,*] {
	set jid $notes($idx)

	set vars [list jid $jid]
	if {[info exists notes($connid,cdate,$jid)]} {
	    set cdate [clock format $notes($connid,cdate,$jid) \
			     -format "%Y-%m-%dT%TZ" -gmt true]
	}
	lappend vars cdate $cdate
	if {[info exists notes($connid,mdate,$jid)]} {
	    set mdate [clock format $notes($connid,mdate,$jid) \
			     -format "%Y-%m-%dT%TZ" -gmt true]
	}
	lappend vars mdate $mdate
	if {[info exists notes($connid,note,$jid)] && \
		$notes($connid,note,$jid) != ""} {
	    lappend notelist \
		[jlib::wrapper:createtag note \
		     -vars $vars \
		     -chdata $notes($connid,note,$jid)]
	}
    }

    jlib::wrapper:createtag storage \
	-vars [list xmlns $::NS(rosternotes)] \
	-subtags $notelist
}

proc annotations::store_notes {connid args} {
    set command [list [namespace current]::store_notes_result $connid]
    foreach {opt val} $args {
	switch -- $opt {
	    -command { set command $val }
	    default {
		return -code error "Bad option \"$opt\":\
		    must be -command"
	    }
	}
    }

    private::store [list [serialize_notes $connid]] \
	-command $command \
	-connection $connid
}

proc annotations::store_notes_result {connid res child} {

    if {$res == "OK"} return

    if {[winfo exists .store_notes_error]} {
	destroy .store_notes_error
    }
    MessageDlg .store_notes_error -aspect 50000 -icon error \
	-message [format [::msgcat::mc "Storing roster notes failed: %s"] \
			 [error_to_string $child]] \
	-type user -buttons ok -default 0 -cancel 0
}

proc annotations::add_user_popup_info {infovar connid jid} {
    variable notes
    upvar 0 $infovar info

    set jid [node_and_server_from_jid $jid]

    if {[info exists notes($connid,note,$jid)] && \
	    $notes($connid,note,$jid) != ""} {
	append info "\n\tNote:\t"
	append info [string map [list "\n" "\n\t\t"] "$notes($connid,note,$jid)"]

	if {0} {
	    if {[info exists notes($connid,cdate,$jid)]} {
		append info [format "\n\tNote created: %s" \
				 [clock format $notes($connid,cdate,$jid) \
					-format "%Y-%m-%d %T" -gmt false]]
	    }
	    if {[info exists notes($connid,mdate,$jid)]} {
		append info [format "\n\tNote modified: %s" \
				 [clock format $notes($connid,mdate,$jid) \
					-format "%Y-%m-%d %T" -gmt false]]
	    }
	}
    }
}

hook::add roster_user_popup_info_hook \
    [namespace current]::annotations::add_user_popup_info 80

proc annotations::show_dialog {connid jid} {
    variable notes

    set jid [node_and_server_from_jid $jid]

    set allowed_name [jid_to_tag $jid]
    set w .note_edit_${connid}_$allowed_name

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

    Dialog $w -title [format [::msgcat::mc "Edit roster notes for %s"] $jid] \
	-modal none -separator 1 -anchor e \
	-default 0 -cancel 1

    $w add -text [::msgcat::mc "Store"] \
	-command [list [namespace current]::commit_changes $w $connid $jid]
    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]

    set f [$w getframe]

    if {[info exists notes($connid,cdate,$jid)]} {
	label $f.cdate -text [format [::msgcat::mc "Created: %s"] \
				     [clock format $notes($connid,cdate,$jid) \
					    -format "%Y-%m-%d %T" -gmt false]]
	pack $f.cdate -side top -anchor w
    }

    if {[info exists notes($connid,mdate,$jid)]} {
	label $f.mdate -text [format [::msgcat::mc "Modified: %s"] \
				     [clock format $notes($connid,mdate,$jid) \
					    -format "%Y-%m-%d %T" -gmt false]]
	pack $f.mdate -side top -anchor w
    }

    ScrolledWindow $f.sw
    pack $f.sw -side top -expand yes -fill both
    textUndoable $f.note -width 50 -height 5 -wrap word
    if {[info exists notes($connid,note,$jid)]} {
	$f.note insert 0.0 $notes($connid,note,$jid)
    }
    $f.sw setwidget $f.note

    bind $f.note <Control-Key-Return> "$w invoke default
				       break"
    bind $w <Key-Return> { }
    bind $w <Control-Key-Return> "$w invoke default
				  break"

    $w draw $f.note
}

proc annotations::commit_changes {w connid jid} {
    variable notes

    set text [$w getframe].note

    set date [clock seconds]

    set notes($connid,jid,$jid) $jid
    if {![info exists notes($connid,cdate,$jid)]} {
	set notes($connid,cdate,$jid) $date
    }
    set notes($connid,mdate,$jid) $date
    set notes($connid,note,$jid) [$text get 0.0 "end -1 char"]

    cleanup_and_store_notes $connid

    destroy $w
}

proc annotations::prefs_user_menu {m connid jid} {
    set rjid [roster::find_jid $connid $jid]
    if {$rjid == ""} {
	set state disabled
    } else {
	set state normal
    }
    $m add command -label [::msgcat::mc "Edit item notes..."] \
	-command [list [namespace current]::show_dialog $connid $rjid] \
	-state $state
}

hook::add chat_create_user_menu_hook \
    [namespace current]::annotations::prefs_user_menu 76
hook::add roster_conference_popup_menu_hook \
    [namespace current]::annotations::prefs_user_menu 76
hook::add roster_service_popup_menu_hook \
    [namespace current]::annotations::prefs_user_menu 76
hook::add roster_jid_popup_menu_hook \
    [namespace current]::annotations::prefs_user_menu 76

proc annotations::note_page {tab connid jid editable} {
    variable notes

    if {$editable} return

    set jid [node_and_server_from_jid $jid]

    if {![info exists notes($connid,note,$jid)] || \
	    $notes($connid,note,$jid) == ""} {
	return
    }

    set notestab [$tab insert end notes -text [::msgcat::mc "Notes"]]
    set n [userinfo::pack_frame $notestab.notes [::msgcat::mc "Roster Notes"]]

    if {[info exists notes($connid,cdate,$jid)]} {
	label $n.cdate -text [format [::msgcat::mc "Created: %s"] \
				     [clock format $notes($connid,cdate,$jid) \
					    -format "%Y-%m-%d %T" -gmt false]]
	pack $n.cdate -side top -anchor w
    }

    if {[info exists notes($connid,mdate,$jid)]} {
	label $n.mdate -text [format [::msgcat::mc "Modified: %s"] \
				     [clock format $notes($connid,mdate,$jid) \
					    -format "%Y-%m-%d %T" -gmt false]]
	pack $n.mdate -side top -anchor w
    }

    set sw [ScrolledWindow $n.sw -scrollbar vertical]
    text $n.text -height 12 -wrap word
    $sw setwidget $n.text
    $n.text insert 0.0 $notes($connid,note,$jid)
    $n.text configure -state disabled
    pack $sw -side top -fill both -expand yes
    pack $n -fill both -expand yes
}

hook::add userinfo_hook [namespace current]::annotations::note_page 40

# vim:ts=8:sw=4:sts=4:noet
