# $Id: mute.tcl 1354 2008-01-28 17:39:01Z sergei $

set ::NS(mute_ancestor) "http://jabber.org/protocol/mute#ancestor"
set ::NS(mute_editor) "http://jabber.org/protocol/mute#editor"

package require msgcat

namespace eval mute {}
namespace eval mute::editor {
    ::msgcat::mcload [file join [file dirname [info script]] msgs]
}

proc mute::editor::get_winid {connid jid id} {
    return [win_id mute [list $connid $jid]//@mute@//$id]
}

proc mute::editor::add_user_menu_item {m connid jid} {
    $m add command \
	-label [::msgcat::mc "MUTE"] \
	-command [list [namespace current]::list_request $connid $jid]
}

hook::add roster_create_groupchat_user_menu_hook \
    [namespace current]::mute::editor::add_user_menu_item 49
hook::add chat_create_user_menu_hook \
    [namespace current]::mute::editor::add_user_menu_item 49
hook::add roster_jid_popup_menu_hook \
    [namespace current]::mute::editor::add_user_menu_item 49


proc mute::editor::list_request {connid jid} {
    jlib::send_iq get \
	[jlib::wrapper:createtag list \
	     -vars [list xmlns $::NS(mute_ancestor)]] \
	-to $jid \
	-command [list [namespace current]::list_recv $connid $jid] \
	-connection $connid
}

proc mute::editor::list_recv {connid jid res child} {
    variable txts

    if {$res != "OK"} {
	after idle [list MessageDlg .mute_list_error -icon error \
			 -message [::msgcat::mc "Error getting list: %s" \
						[error_to_string $child]] \
			 -type ok]
	return ""
    }

    jlib::wrapper:splitxml $child tag vars isempty chdata children

    set ids {}
    foreach ch $children {
	jlib::wrapper:splitxml $ch tag1 vars1 isempty1 chdata1 children1
	set id [jlib::wrapper:getattr $vars1 id]
	lappend ids $id
	set desc($id) [jlib::wrapper:getattr $vars1 desc]
    }

    set w .mute_invite

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

    Dialog $w -title [::msgcat::mc "List of texts from %s" $jid] \
	-separator 1 -anchor e -default 0

    foreach id $ids {
	$w add -text "$id: $desc($id)" \
	    -command [list [namespace current]::subscribe_request $connid $jid $id]
    }

    $w add -text Cancel -command [list destroy $w]
    
    $w draw
}

proc mute::editor::subscribe_request {connid jid id} {
    jlib::send_iq set \
	[jlib::wrapper:createtag subscribe \
	     -vars [list xmlns $::NS(mute_ancestor)] \
	     -subtags [list [jlib::wrapper:createtag item \
				 -vars [list id $id]]]] \
	-to $jid \
	-command [list [namespace current]::subscribe_recv $connid $jid $id] \
	-connection $connid
}

proc mute::editor::subscribe_recv {connid jid id res child} {
    variable txts

    if {$res != "OK"} {
	after idle [list MessageDlg .mute_list_error -icon error \
			 -message [::msgcat::mc "Error getting list: %s" \
						[error_to_string $child]] \
			 -type ok]
	return ""
    }
}

proc mute::editor::open_win {connid jid id} {
    variable txts

    set w [get_winid $connid $jid $id]
    if {[winfo exists $w]} {
	return
    }

    add_win $w -title [::msgcat::mc "Edit %s" $id] \
	       -tabtitle [::msgcat::mc "Edit %s" $id] \
	       -class Mute

    set bbox [ButtonBox $w.bbox -spacing 10 -padx 10]
    pack $bbox -side bottom -anchor e

    $bbox add -text [::msgcat::mc "Commit current version"] \
	-command [list [namespace current]::commit $connid $jid $id]
    $bbox add -text [::msgcat::mc "Revert to master version"] \
	-command [list [namespace current]::revert $connid $jid $id]

    set sep [Separator::create $w.sep -orient horizontal]
    pack $sep -pady 2m -fill x -side bottom

    set sw [ScrolledWindow $w.sw]
    set text [text $w.text]
    pack $sw -side top -anchor w -expand yes -fill both
    $sw setwidget $text
}

proc mute::editor::get_textw {connid jid id} {
    return "[get_winid $connid $jid $id].text"
}

proc mute::editor::set_text {connid jid id text} {
    variable txts

    set txts(text,$connid,$jid,$id) $text
    set t [get_textw $connid $jid $id]

    $t delete 1.0 end
    $t insert 0.0 $text
}

proc mute::editor::revert {connid jid id} {
    variable txts

    set text $txts(text,$connid,$jid,$id)
    set tw [get_textw $connid $jid $id]

    lassign [split [$tw index insert] .] line pos
    $tw delete 1.0 end
    $tw insert 0.0 $text
    $tw mark set insert "$line.$pos"
}

proc mute::editor::commit {connid jid id} {
    variable txts

    set orig $txts(text,$connid,$jid,$id)

    set tw [get_textw $connid $jid $id]
    set edit [$tw get 1.0 "end -1 chars"]

    if {[cindex $edit end] != "\n"} {
	set edit "$edit\n"
    }

    # TODO: check temp files
    set fn "/tmp/mute[rand 1000000]"

    set fd [open $fn.orig w]
    fconfigure $fd -encoding utf-8
    puts -nonewline $fd $orig
    close $fd

    set fd [open $fn.edit w]
    fconfigure $fd -encoding utf-8
    puts -nonewline $fd $edit
    close $fd

    catch { exec diff -u $fn.orig $fn.edit > $fn.diff }

    set fd [open $fn.diff r]
    fconfigure $fd -encoding utf-8
    gets $fd
    gets $fd
    set diff [read $fd]
    close $fd

    file delete $fn.orig $fn.edit $fn.diff

    if {$diff != ""} {
	jlib::send_iq set \
	    [jlib::wrapper:createtag patch \
		 -vars [list xmlns $::NS(mute_ancestor) id $id] \
		 -chdata $diff] \
	    -to $jid \
	    -command [list [namespace current]::patch_res $connid $jid $id] \
	    -connection $connid
    }
}

proc mute::editor::patch_res {connid jid id res child} {
    variable txts

    if {$res != "OK"} {
	after idle [list MessageDlg .mute_list_error -icon error \
			 -message [::msgcat::mc "Error patching: %s" \
						[error_to_string $child]] \
			 -type ok]
	return ""
    }

    set text $txts(text,$connid,$jid,$id)

    set tw [get_textw $connid $jid $id]

    lassign [split [$tw index insert] .] line pos
    $tw delete 1.0 end
    $tw insert 0.0 $text
    $tw mark set insert "$line.$pos"
}

proc mute::editor::patch {connid jid id patch} {
    variable txts

    set tw [get_textw $connid $jid $id]

    if {![info exists txts(text,$connid,$jid,$id)] || ![winfo exists $tw]} {
	return [list error cancel not-allowed]
    }

    set text $txts(text,$connid,$jid,$id)

    set edit [$tw get 1.0 "end -1 chars"]

    lassign [split [$tw index insert] .] line pos

    # TODO: check temp files
    set fn "/tmp/mute[rand 1000000]"

    set fd [open $fn.old w]
    fconfigure $fd -encoding utf-8
    puts -nonewline $fd $text
    close $fd

    set fd [open $fn.orig w]
    fconfigure $fd -encoding utf-8
    puts -nonewline $fd $text
    close $fd

    set fd [open $fn.patch w]
    fconfigure $fd -encoding utf-8
    puts -nonewline $fd $patch
    close $fd

    set fd [open $fn.edit w]
    fconfigure $fd -encoding utf-8
    puts -nonewline $fd $edit
    close $fd

    if {[catch { exec patch $fn.orig $fn.patch }]} {
	puts "something wrong..."
	return
    }

    set fd [open $fn.orig r]
    fconfigure $fd -encoding utf-8
    set new [read $fd]
    close $fd

    catch { exec merge $fn.edit $fn.old $fn.orig }

    set fd [open $fn.edit r]
    fconfigure $fd -encoding utf-8
    set newedit [read $fd]
    close $fd

    file delete $fn.old $fn.orig $fn.patch $fn.edit

    set txts(text,$connid,$jid,$id) $new

    $tw delete 1.0 end
    $tw insert 0.0 $newedit

    set lineregexp {@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@}
    set shift 0
    foreach l [split $patch \n] {
	if {[regexp $lineregexp $l temp ol os nl ns]} {
	    if {$ol >= $line} break
	    set shift [expr {$nl + $ns - $ol - $os}]
	}
    }

    set line [expr {$line + $shift}]
    $tw mark set insert "$line.$pos"

    return [list result ""]
}

proc mute::editor::recv_set_iq {connid from lang child} {
    jlib::wrapper:splitxml $child tag vars isempty chdata children

    switch -- $tag {
	text {
	    set id [jlib::wrapper:getattr $vars id]
	    open_win $connid $from $id
	    set_text $connid $from $id $chdata
	}
	patch {
	    set id [jlib::wrapper:getattr $vars id]
	    set patch $chdata
	    return [patch $connid $from $id $patch]
	}
	default {
	    return [list error cancel feature-not-implemented]
	}
    }
}

iq::register_handler set {} $::NS(mute_editor) \
    [namespace current]::mute::editor::recv_set_iq



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

namespace eval mute::ancestor {
    set txts(ids) {id1 id2}
    set txts(desc,id1) "Useful text"
    set txts(desc,id2) "Useless text"

    set txts(text,id1) "...
8. Admin Use Cases
   8.1. Banning a User
   8.2. Modifying the Ban List
   8.3. Granting Membership
   8.4. Revoking Membership
   8.5. Modifying the Member List
   8.6. Granting Moderator Privileges
   8.7. Revoking Moderator Privileges
   8.8. Modifying the Moderator List  
...
"

    set txts(text,id2) ""
}

proc mute::ancestor::send_text {connid jid id} {
    variable txts

    jlib::send_iq set \
	[jlib::wrapper:createtag text \
	     -vars [list xmlns $::NS(mute_editor) id $id] \
	     -chdata $txts(text,$connid,$id)] \
	-to $jid \
	-connection $connid
#	-command [list [namespace current]::subscribe_recv $connid $jid $id]
}


proc mute::ancestor::patch {connid jid id patch} {
    variable txts

    set text $txts(text,$connid,$id)

    # TODO: check temp files
    set fn "/tmp/mute[rand 1000000]"

    set fd [open $fn.orig w]
    fconfigure $fd -encoding utf-8
    puts -nonewline $fd $text
    close $fd

    set fd [open $fn.patch w]
    fconfigure $fd -encoding utf-8
    puts -nonewline $fd $patch
    close $fd

    if {[catch { exec patch $fn.orig $fn.patch }]} {
	return [list error cancel not-allowed]
    }

    set fd [open $fn.orig r]
    fconfigure $fd -encoding utf-8
    set new [read $fd]
    close $fd

    file delete $fn.orig $fn.patch

    set txts(text,$connid,$id) $new
    after idle [list [namespace current]::distribute_patch $connid $id $patch]

    return [list result ""]
}

proc mute::ancestor::distribute_patch {connid id patch} {
    variable txts

    foreach subscriber $txts(subscribed,$connid,$id) {
	jlib::send_iq set \
	    [jlib::wrapper:createtag patch \
		 -vars [list xmlns $::NS(mute_editor) id $id] \
		 -chdata $patch] \
	    -to $subscriber \
	    -command [list [namespace current]::unsubscribe_on_error \
			  $connid $subscriber $id] \
	    -connection $connid
    }
}

proc mute::ancestor::unsubscribe_on_error {connid jid id res child} {
    variable txts

    if {$res != "OK"} {
	set idx [lsearch -exact $txts(subscribed,$connid,$id) $jid]
	set txts(subscribed,$connid,$id) [lreplace $txts(subscribed,$connid,$id) $idx $idx]
	puts "REMOVE $connid $jid"
    }
}

proc mute::ancestor::recv_get_iq {connid from lang child} {
    jlib::wrapper:splitxml $child tag vars isempty chdata children

    switch -- $tag {
	list {
	    variable txts
	    # TODO
	    if {![info exists txts(ids,$connid)]} {
		set txts(ids,$connid) $txts(ids)
		foreach id $txts(ids) {
		    set txts(desc,$connid,$id) $txts(desc,$id)
		    set txts(text,$connid,$id) $txts(text,$id)
		}
	    }

	    set items {}
	    foreach id $txts(ids,$connid) {
		lappend items [jlib::wrapper:createtag item \
				   -vars [list id $id desc $txts(desc,$connid,$id)]]
	    }
	    return [list result [jlib::wrapper:createtag list \
				     -vars [list xmlns $::NS(mute_ancestor)] \
				     -subtags $items]]
	}
	default {
	    return [list error cancel feature-not-implemented]
	}
    }
    return ""
}

iq::register_handler get {} $::NS(mute_ancestor) \
    [namespace current]::mute::ancestor::recv_get_iq


proc mute::ancestor::recv_set_iq {connid from lang child} {
    jlib::wrapper:splitxml $child tag vars isempty chdata children

    switch -- $tag {
	subscribe {
	    variable txts

	    foreach ch $children {
		jlib::wrapper:splitxml $ch tag1 vars1 isempty1 chdata1 children1

		set id [jlib::wrapper:getattr $vars1 id]
		if {[lcontain $txts(ids,$connid) $id]} {
		    lappend txts(subscribed,$connid,$id) $from
		    set txts(subscribed,$connid,$id) [lrmdups $txts(subscribed,$connid,$id)]
		    after idle [list [namespace current]::send_text $connid $from $id]
		    return [list result ""]
		} else {
		    return [list error modify bad-request]
		}
	    }
	    return [list error modify bad-request]
	}
	patch {
	    set id [jlib::wrapper:getattr $vars id]
	    set patch $chdata
	    return [patch $connid $from $id $patch]
	}
	default {
	    return [list error cancel feature-not-implemented]
	}
    }
    return ""
}

iq::register_handler set {} $::NS(mute_ancestor) \
    [namespace current]::mute::ancestor::recv_set_iq



