# $Id: muc.tcl 1512 2008-10-11 13:08:59Z sergei $

# Multi-User Chat support (XEP-0045)

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

namespace eval muc {
    set winid 0
    custom::defvar options(gen_enter_exit_msgs) 1 \
	[::msgcat::mc "Generate status messages when occupants\
	    enter/exit MUC compatible conference rooms."] \
	-type boolean -group Chat
    custom::defvar options(gen_muc_status_change_msgs) 0 \
	[::msgcat::mc "Generate groupchat messages when occupant\
	    changes his/her status and/or status message."] \
	-type boolean -group Chat
    custom::defvar options(gen_muc_position_change_msgs) 0 \
	[::msgcat::mc "Generate groupchat messages when occupant's\
	    room position (affiliation and/or role) changes."] \
	-type boolean -group Chat
    custom::defvar options(propose_configure) 0 \
	[::msgcat::mc "Propose to configure newly created MUC room.\
		       If set to false then the default room configuration\
		       is automatically accepted."] \
	-type boolean -group Chat
    custom::defvar options(history_maxchars) 10000 \
	[::msgcat::mc "Maximum number of characters in the history in MUC\
		       compatible conference rooms."] \
	-type integer -group Chat
    custom::defvar options(history_maxstanzas) 20 \
	[::msgcat::mc "Maximum number of stanzas in the history in MUC\
		       compatible conference rooms."] \
	-type integer -group Chat
    custom::defvar options(request_only_unseen_history) 0 \
	[::msgcat::mc "Request only unseen (which aren't displayed in the\
		       chat window) messages in the history in MUC compatible\
		       conference rooms."] \
	-type boolean -group Chat
    custom::defvar options(report_muc_rooms) 0 \
	[::msgcat::mc "Report the list of current MUC rooms on\
		       disco#items query."] \
	-type boolean -group IQ
}

# MUC affiliations (for translation):
# [::msgcat::mc "owner"]
# [::msgcat::mc "admin"]
# [::msgcat::mc "member"]
# [::msgcat::mc "outcast"]
# [::msgcat::mc "none"]
# MUC roles (for translation):
# [::msgcat::mc "moderator"]
# [::msgcat::mc "participant"]
# [::msgcat::mc "visitor"]
# (yet another "none" omitted)

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

set ::NS(muc)       http://jabber.org/protocol/muc
set ::NS(muc#admin) http://jabber.org/protocol/muc#admin
set ::NS(muc#owner) http://jabber.org/protocol/muc#owner
set ::NS(muc#user)  http://jabber.org/protocol/muc#user

set ::NS(muc#rooms)  http://jabber.org/protocol/muc#rooms

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

proc muc::add_groupchat_user_menu_items {m connid jid} {
    set group [node_and_server_from_jid $jid]

    if {![is_compatible $group]} return

    set mm [menu $m.muc -tearoff 0]
    $mm add command -label [::msgcat::mc "Whois"] \
	-command [list muc::whois $connid $jid]
    $mm add command -label [::msgcat::mc "Kick"] \
	-command [list muc::change_item_param \
		      {role none} down $connid $jid ""]
    $mm add command -label [::msgcat::mc "Ban"] \
	-command [list muc::change_item_param \
		      {affiliation outcast} down $connid $jid ""]
    $mm add command -label [::msgcat::mc "Grant Voice"] \
	-command [list muc::change_item_param \
		      {role participant} up $connid $jid ""]
    $mm add command -label [::msgcat::mc "Revoke Voice"] \
	-command [list muc::change_item_param \
		      {role visitor} down $connid $jid ""]
    $mm add command -label [::msgcat::mc "Grant Membership"] \
	-command [list muc::change_item_param \
		      {affiliation member} up $connid $jid ""]
    $mm add command -label [::msgcat::mc "Revoke Membership"] \
	-command [list muc::change_item_param \
		      {affiliation none} down $connid $jid ""]
    $mm add command -label [::msgcat::mc "Grant Moderator Privileges"] \
	-command [list muc::change_item_param \
		      {role moderator} up $connid $jid ""]
    $mm add command -label [::msgcat::mc "Revoke Moderator Privileges"] \
	-command [list muc::change_item_param \
		      {role participant} down $connid $jid ""]
    $mm add command -label [::msgcat::mc "Grant Admin Privileges"] \
	-command [list muc::change_item_param \
		      {affiliation admin} up $connid $jid ""]
    $mm add command -label [::msgcat::mc "Revoke Admin Privileges"] \
	-command [list muc::change_item_param \
		      {affiliation member} down $connid $jid ""]
    #$mm add command -label [::msgcat::mc "Grant Owner Privileges"] \
    #    -command [list muc::change_item_param \
    #    	      {affiliation owner} up $connid $jid ""]
    #$mm add command -label [::msgcat::mc "Revoke Owner Privileges"] \
    #    -command [list muc::change_item_param \
    #		      {affiliation admin} down $connid $jid ""]

    $m add cascade -label [::msgcat::mc "MUC"] -menu $mm
}

hook::add roster_create_groupchat_user_menu_hook \
    muc::add_groupchat_user_menu_items 39

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

proc muc::create_conference_menu_items {m connid jid} {
    set chatid [chat::chatid $connid $jid]

    set idx [expr {[$m index end] + 1}]

    trace variable ::muc::muc_compatible($jid) w \
	[list muc::add_conference_menu_items $m $chatid $idx]

    bind $m <Destroy> [list trace vdelete ::muc::muc_compatible($jid) w \
			    [list muc::add_conference_menu_items $m $chatid $idx]]

}
    
hook::add chat_create_conference_menu_hook muc::create_conference_menu_items 37

proc muc::add_conference_menu_items {m chatid idx args} {
    set group [chat::get_jid $chatid]

    if {![is_compatible $group] || ![winfo exists $m]} return

    trace vdelete ::muc::muc_compatible($group) w \
	[list muc::add_conference_menu_items $m $chatid $idx]

    add_muc_menu_items $m $chatid $idx
}

proc muc::add_muc_menu_items {m chatid idx} {
    set mm [menu $m.muc -tearoff 0]

    $mm add command -label [::msgcat::mc "Configure room"] \
	-command [list muc::request_config $chatid]
    $mm add command -label [::msgcat::mc "Edit voice list"] \
	-command [list muc::request_list role participant $chatid]
    $mm add command -label [::msgcat::mc "Edit ban list"] \
	-command [list muc::request_list affiliation outcast $chatid]
    $mm add command -label [::msgcat::mc "Edit member list"] \
	-command [list muc::request_list affiliation member $chatid]
    $mm add command -label [::msgcat::mc "Edit moderator list"] \
	-command [list muc::request_list role moderator $chatid]
    $mm add command -label [::msgcat::mc "Edit admin list"] \
	-command [list muc::request_list affiliation admin $chatid]
    $mm add command -label [::msgcat::mc "Edit owner list"] \
	-command [list muc::request_list affiliation owner $chatid]
    $mm add separator
    $mm add command -label [::msgcat::mc "Destroy room"] \
	-command [list muc::request_destruction_dialog $chatid "" ""]

    $m insert $idx cascade -label [::msgcat::mc "MUC"] -menu $mm
}

proc muc::disco_node_menu_setup {m bw tnode data parentdata} {
    lassign $data type connid jid node
    lassign $parentdata ptype pconnid pjid pnode
    switch -- $type {
	item {
	    set identities [disco::get_jid_identities $connid $jid $node]
	    set pidentities [disco::get_jid_identities $pconnid $pjid $pnode]

	    set features [disco::get_jid_features $connid $jid $node]
	    set pfeatures [disco::get_jid_features $pconnid $pjid $pnode]

	    # JID with resource is not a room JID
	    if {[node_and_server_from_jid $jid] != $jid} return

	    # A room must have non-empty node
	    if {[node_from_jid $jid] == ""} return

	    if {[lempty $identities]} {
		set identities $pidentities
	    }

	    if {[lempty $features]} {
		set features $pfeatures
	    }

	    foreach id $identities {
		if {[jlib::wrapper:getattr $id category] == "conference"} {
		    foreach f $features {
			if {[jlib::wrapper:getattr $f var] == $::NS(muc)} {
			    add_muc_menu_items $m [chat::chatid $connid $jid] end
			    return
			}
		    }
		}
	    }
	}
    }
}

hook::add disco_node_menu_hook muc::disco_node_menu_setup 60

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

proc muc::handle_commands {chatid user body type} {
    if {$type != "groupchat"} return

    set connid [chat::get_connid $chatid]
    set group [chat::get_jid $chatid]
    if {[cequal [crange $body 0 5] "/kick "]} {
	set params {role none}
	set dir down
	lassign [parse_nick_reason $body 6] nick reason
    } elseif {[cequal [crange $body 0 4] "/ban "]} {
	set params {affiliation outcast}
	set dir down
	lassign [parse_nick_reason $body 5] nick reason
    } elseif {[cequal [crange $body 0 6] "/unban "]} {
	set jid [parse_nick $body 7]
	unban $connid $group $jid
	return stop
    } elseif {[cequal [crange $body 0 6] "/whois "]} {
	set nick [parse_nick $body 7]
	whois $connid $group/$nick
	return stop
    } elseif {[cequal [crange $body 0 6] "/voice "]} {
	set params {role participant}
	set dir up
	lassign [parse_nick_reason $body 7] nick reason
    } elseif {[cequal [crange $body 0 8] "/devoice "]} {
	set params {role visitor}
	set dir down
	lassign [parse_nick_reason $body 9] nick reason
    } elseif {[cequal [crange $body 0 7] "/member "]} {
	set params {affiliation member}
	set dir up
	lassign [parse_nick_reason $body 8] nick reason
    } elseif {[cequal [crange $body 0 9] "/demember "]} {
	set params {affiliation none}
	set dir down
	lassign [parse_nick_reason $body 10] nick reason
    } elseif {[cequal [crange $body 0 10] "/moderator "]} {
	set params {role moderator}
	set dir up
	lassign [parse_nick_reason $body 11] nick reason
    } elseif {[cequal [crange $body 0 12] "/demoderator "]} {
	set params {role participant}
	set dir down
	lassign [parse_nick_reason $body 13] nick reason
    } elseif {[cequal [crange $body 0 6] "/admin "]} {
	set params {affiliation admin}
	set dir up
	lassign [parse_nick_reason $body 7] nick reason
    } elseif {[cequal [crange $body 0 8] "/deadmin "]} {
	set params {affiliation member}
	set dir down
	lassign [parse_nick_reason $body 9] nick reason
    } else {
	return
    }
    
    change_item_param $params $dir $connid $group/$nick $reason

    return stop
}

hook::add chat_send_message_hook muc::handle_commands 50

proc muc::parse_nick {body n} {
    return [lindex [parse_nick_reason $body $n] 0]
}

proc muc::parse_nick_reason {body n} {
    # Parse nickname and reason
    # first line is a nick, rest are reason
    set nick_reason [crange $body $n end]
    set ne [string first "\n" $nick_reason]
    if {$ne < 0} {
	set nick $nick_reason
	set reason ""
    } else {
	set nick [string range $nick_reason 0 [expr {$ne - 1}]]
	set reason [string range $nick_reason [expr {$ne + 1}] end]
    }
    return [list $nick [string trim $reason]]
}

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

proc muc::commands_comps {chatid compsvar wordstart line} {
    set group [chat::get_jid $chatid]
    if {![is_compatible $group]} return

    upvar 0 $compsvar comps

    if {!$wordstart} {
	lappend comps {/whois } {/kick } {/ban } {/unban } \
	    {/voice } {/devoice } \
	    {/member } {/demember } \
	    {/moderator } {/demoderator } \
	    {/admin } {/deadmin }
    }
}

hook::add generate_completions_hook muc::commands_comps

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

proc muc::get_real_jid {connid jid} {
    variable users

    if {[info exists users(jid,$connid,$jid)] && \
	    $users(jid,$connid,$jid) != ""} {
	return $users(jid,$connid,$jid)
    } else {
	return ""
    } 
}

proc muc::whois {connid user} {
    set group [node_and_server_from_jid $user]
    set chatid [chat::chatid $connid $group]
    set nick [chat::get_nick $connid $user groupchat]

    set real_jid [get_real_jid $connid $user]

    if {$real_jid != ""} {
	chat::add_message $chatid $group info \
	    [::msgcat::mc "whois %s: %s" $nick $real_jid] {}
    } else {
	chat::add_message $chatid $group error \
	    [::msgcat::mc "whois %s: no info" $nick] {}
    }
}

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

proc muc::compare_roles {role1 role2} {
    set roles {none visitor participant moderator}

    set idx1 [lsearch -exact $roles $role1]
    set idx2 [lsearch -exact $roles $role2]
    expr {$idx1 - $idx2}
}

proc muc::compare_affs {aff1 aff2} {
    set affs {outcast none member admin owner}

    set idx1 [lsearch -exact $affs $aff1]
    set idx2 [lsearch -exact $affs $aff2]
    expr {$idx1 - $idx2}
}

proc muc::change_item_param {params dir connid user reason} {
    variable users

    set group [node_and_server_from_jid $user]
    set chatid [chat::chatid $connid $group]
    set nick  [chat::get_nick $connid $user groupchat]

    lassign $params key val
    if {![catch { set aff $users(affiliation,$connid,$user) }] && \
	    ![catch { set role $users(role,$connid,$user) }]} {
	switch -- $key/$dir {
	    affiliation/up {
		if {[compare_affs $aff $val] >= 0} {
		    chat::add_message $chatid $group error \
			"affiliation $val $nick:\
			 [::msgcat::mc {User already %s} $aff]" {}
		    return
		}
	    }
	    affiliation/down {
		if {[compare_affs $aff $val] <= 0} {
		    chat::add_message $chatid $group error \
			"affiliation $val $nick:\
			 [::msgcat::mc {User already %s} $aff]" {}
		    return
		}
	    }
	    role/up {
		if {[compare_roles $role $val] >= 0} {
		    chat::add_message $chatid $group error \
			"role $val $nick:\
			 [::msgcat::mc {User already %s} $role]" {}
		    return
		}
	    }
	    role/down {
		if {[compare_roles $role $val] <= 0} {
		    chat::add_message $chatid $group error \
			"role $val $nick:\
			 [::msgcat::mc {User already %s} $role]" {}
		    return
		}
	    }
	    default {
		return
	    }
	}
    }

    set itemsubtags {}
    if {$reason != ""} {
	lappend itemsubtags [jlib::wrapper:createtag reason \
				 -chdata $reason]
    }
    set vars [list nick $nick]
    if {$params == {affiliation outcast}} {
	# For unknown reason banning request MUST be based on
	# user's bare JID (which may be not known by admin)
	set real_jid [get_real_jid $connid $user]
	if {$real_jid != ""} {
	    set vars [list jid [node_and_server_from_jid $real_jid]]
	}
	
    }
    set item [jlib::wrapper:createtag item \
		  -vars [concat $vars $params] \
		  -subtags $itemsubtags]

    jlib::send_iq set \
	[jlib::wrapper:createtag query \
	     -vars [list xmlns $::NS(muc#admin)] \
	     -subtags [list $item]] \
	-to $group \
	-connection $connid \
	-command [list muc::test_error_res "$params $nick" $connid $group]
}

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

proc muc::unban {connid group jid} {
    jlib::send_iq get \
	[jlib::wrapper:createtag query \
	     -vars [list xmlns $::NS(muc#admin)] \
	     -subtags [list [jlib::wrapper:createtag item \
				 -vars {affiliation outcast}]]] \
	-to $group \
	-connection $connid \
	-command [list muc::unban_continue $connid $group $jid]
}

proc muc::unban_continue {connid group jid res child} {
    if {$res != "OK"} {
	chat::add_message [chat::chatid $connid $group] $group error \
	    "affiliation none $jid: [error_to_string $child]" {}
	return
    }

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

    set jid [node_and_server_from_jid $jid]
    set found 0
    foreach item $children {
	jlib::wrapper:splitxml $item tag1 vars1 isempty1 chdata1 children1
	switch -- $tag1 {
	    item {
		set jid1 [jlib::wrapper:getattr $vars1 jid]
		if {$jid == $jid1} {
		    set found 1
		    break
		}
	    }
	}
    }

    if {!$found} {
	chat::add_message [chat::chatid $connid $group] $group error \
	    "affiliation none $jid: User is not banned" {}
	return
    }

    set item [jlib::wrapper:createtag item \
		  -vars [list jid $jid affiliation none]]

    jlib::send_iq set \
	[jlib::wrapper:createtag query \
	     -vars [list xmlns $::NS(muc#admin)] \
	     -subtags [list $item]] \
	-to $group \
	-connection $connid \
	-command [list muc::test_unban_res $connid $group $jid]
}

proc muc::test_unban_res {connid group jid res child} {
    if {$res != "OK"} {
	chat::add_message [chat::chatid $connid $group] $group error \
	    "affiliation none $jid: [error_to_string $child]" {}
	return
    }

    chat::add_message [chat::chatid $connid $group] $group info \
	"affiliation none $jid: User is unbanned" {}
}

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

proc muc::request_destruction_dialog {chatid alt reason} {
    set connid [chat::get_connid $chatid]
    set group [chat::get_jid $chatid]

    set warning \
	[::msgcat::mc "Conference room %s will be destroyed\
		       permanently.\n\nProceed?" $group]

    set res [MessageDlg .muc_request_destruction -aspect 50000 -icon warning \
			-type user -buttons {yes no} -default 1 \
			-cancel 1 \
			-message $warning]
    if {!$res} {
	request_destruction $chatid $alt $reason
    }
}

proc muc::request_destruction {chatid alt reason} {
    set connid [chat::get_connid $chatid]
    set group [chat::get_jid $chatid]

    if {$alt != ""} {
	set vars [list jid $alt]
    } else {
	set vars {}
    }

    jlib::send_iq set \
	[jlib::wrapper:createtag query \
	     -vars [list xmlns $::NS(muc#owner)] \
	     -subtags [list \
			   [jlib::wrapper:createtag destroy \
				-vars $vars \
				-subtags [list \
					      [jlib::wrapper:createtag reason \
						   -chdata $reason]]]]] \
	-to $group \
	-connection $connid \
	-command [list muc::test_error_res "destroy" $connid $group]
}

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

proc muc::request_list {attr val chatid} {
    set connid [chat::get_connid $chatid]
    set group [chat::get_jid $chatid]
    jlib::send_iq get \
	[jlib::wrapper:createtag query \
	     -vars [list xmlns $::NS(muc#admin)] \
	     -subtags [list [jlib::wrapper:createtag item \
				 -vars [list $attr $val]]]] \
	-to $group \
	-connection $connid \
	-command [list muc::receive_list $attr $val $chatid]
}


proc muc::receive_list {attr val chatid res child} {
    set connid [chat::get_connid $chatid]
    set group [chat::get_jid $chatid]
    if {![cequal $res OK]} {
	chat::add_message $chatid $group error \
	    "$attr $val list: [error_to_string $child]" {}
	return
    }

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

    #data::draw_window $children [list muc::send_list $role $group]

    variable winid

    set w .muc_list$winid
    incr winid

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

    Dialog $w -title [::msgcat::mc [format "Edit %s list" $val]] \
        -modal none -separator 1 -anchor e -default 0 -cancel 1 \
        -parent .

    set wf [$w getframe]

    set sw [ScrolledWindow $wf.sw -scrollbar vertical]
    set sf [ScrollableFrame $w.fields -constrainedwidth yes]
    set f [$sf getframe]
    $sw setwidget $sf
    fill_list $sf $f $children $attr $val
    list_add_item $sf $f $attr $val
 
    $w add -text [::msgcat::mc "Send"] \
	-command [list muc::send_list $chatid $attr $val $w $f]
    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
    bind $w <Destroy> [list after idle [list muc::list_cleanup $w $f]]

    frame $w.fr
    pack $w.fr -side bottom -in $wf -fill x

    button $w.add -text [::msgcat::mc "Add new item"] \
	-command [list muc::list_add_item $sf $f $attr $val]
    pack $w.add -side right -in $w.fr -padx 1m -pady 1m

    label $w.lall -text [::msgcat::mc "All items:"]
    pack $w.lall -side left -in $w.fr -padx 1m -pady 1m

    switch -- $attr {
	role {
	    ComboBox $w.roleall -text $val \
		-values {moderator participant visitor none} \
		-editable no \
		-width 11 \
		-modifycmd [list muc::change_all_items $f $w.roleall $attr]
	    pack $w.roleall -side left -anchor w -in $w.fr -pady 1m
	}
	affiliation {
	    ComboBox $w.affiliationall -text $val \
		-values {owner admin member none outcast} \
		-editable no \
		-width 7 \
		-modifycmd [list muc::change_all_items $f $w.affiliationall $attr]
	    pack $w.affiliationall -side left -in $w.fr -pady 1m
	}
    }

    pack $sw -side top -expand yes -fill both

    bindscroll $f $sf

    set hf [frame $w.hf]
    pack $hf -side top
    set vf [frame $w.vf]
    pack $vf -side left

    update idletasks
    $hf configure -width [expr {[winfo reqwidth $f] + [winfo pixels $f 1c]}]

    set h [winfo reqheight $f]
    set sh [winfo screenheight $w]
    if {$h > $sh - 200} {
	set h [expr {$sh - 200}]
    }
    $vf configure -height $h

    $w draw
}

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

proc muc::change_all_items {f combobox attr} {
    variable listdata
    variable origlistdata

    set value [$combobox get]

    for {set i 1} {$i <= $listdata($f,rows)} {incr i} {
	set listdata($f,$attr,$i) $value
    }
}

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

proc muc::fill_list {sf f items attr val} {
    variable listdata
    variable origlistdata

    grid columnconfigure $f 0 -weight 1
    grid columnconfigure $f 1 -weight 1
    grid columnconfigure $f 2 -weight 0
    grid columnconfigure $f 3 -weight 2

    label $f.lnick -text [::msgcat::mc "Nick"]
    grid $f.lnick -row 0 -column 0 -sticky we -padx 1m
    bindscroll $f.lnick $sf
    label $f.ljid -text JID
    grid $f.ljid -row 0 -column 1 -sticky we -padx 1m
    bindscroll $f.ljid $sf
    switch -- $attr {
	role {
	    label $f.lrole -text [::msgcat::mc "Role"]
	    grid $f.lrole -row 0 -column 2 -sticky we -padx 1m
	    bindscroll $f.lrole $sf
	}
	affiliation {
	    label $f.laffiliation -text [::msgcat::mc "Affiliation"]
	    grid $f.laffiliation -row 0 -column 2 -sticky we -padx 1m
	    bindscroll $f.laffiliation $sf
	}
    }
    label $f.lreason -text [::msgcat::mc "Reason"]
    grid $f.lreason -row 0 -column 3 -sticky we -padx 1m
    bindscroll $f.lreason $sf

    set row 1

    set items2 {}
    foreach item $items {
	jlib::wrapper:splitxml $item tag vars isempty chdata children
	switch -- $tag {
	    item {
		set nick [jlib::wrapper:getattr $vars nick]
		set jid [jlib::wrapper:getattr $vars jid]
		set role [jlib::wrapper:getattr $vars role]
		set affiliation [jlib::wrapper:getattr $vars affiliation]
		set reason ""
		foreach subitem $children {
		    jlib::wrapper:splitxml $subitem tag1 vars1 isempty1 chdata1 children1
		    if {$tag1 == "reason"} {
			set reason $chdata1
		    }
		}
		lappend items2 [list $nick $jid $role $affiliation $reason]
	    }
	}
    }

    foreach item [lsort -dictionary -index 1 $items2] {
	lassign $item listdata($f,nick,$row) listdata($f,jid,$row) \
		      role affiliation listdata($f,reason,$row)

	entry $f.nick$row \
	    -textvariable muc::listdata($f,nick,$row) \
	    -takefocus 0 \
	    -highlightthickness 0 \
	    -width 20
	if {[catch {$f.nick$row configure -state readonly}]} {
	    $f.nick$row configure -state disabled
	}
	grid $f.nick$row -row $row -column 0 -sticky we -padx 1m
	bindscroll $f.nick$row $sf

	entry $f.jid$row \
	    -textvariable muc::listdata($f,jid,$row) \
	    -takefocus 0 \
	    -highlightthickness 0 \
	    -width 30
	if {[catch {$f.jid$row configure -state readonly}]} {
	    $f.jid$row configure -state disabled
	}
	grid $f.jid$row -row $row -column 1 -sticky we -padx 1m
	bindscroll $f.jid$row $sf

	switch -- $attr {
	    role {
		ComboBox $f.role$row -text $role \
		    -values {moderator participant visitor none} \
		    -editable no \
		    -width 11 \
		    -textvariable muc::listdata($f,role,$row)
		grid $f.role$row -row $row -column 2 -sticky we -padx 1m
		bindscroll $f.role$row $sf
	    }
	    affiliation {
		ComboBox $f.affiliation$row -text $affiliation \
		    -values {owner admin member none outcast} \
		    -editable no \
		    -width 7 \
		    -textvariable muc::listdata($f,affiliation,$row)
		grid $f.affiliation$row -row $row -column 2 -sticky we -padx 1m
		bindscroll $f.affiliation$row $sf
	    }
	}

	entry $f.reason$row \
	    -textvariable muc::listdata($f,reason,$row) \
	    -width 40
	grid $f.reason$row -row $row -column 3 -sticky we -padx 1m
	bindscroll $f.reason$row $sf
	
	incr row
    }

    set listdata($f,rows) [incr row -1]
    array set origlistdata [array get listdata ${f}*]
}

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

proc muc::list_add_item {sf f attr val} {
    variable listdata

    set row [incr listdata($f,rows)]

    entry $f.nick$row \
	-textvariable muc::listdata($f,nick,$row) \
	-width 20
    grid $f.nick$row -row $row -column 0 -sticky we -padx 1m
    bindscroll $f.nick$row $sf
    
    entry $f.jid$row \
	-textvariable muc::listdata($f,jid,$row) \
	-width 30
    grid $f.jid$row -row $row -column 1 -sticky we -padx 1m
    bindscroll $f.jid$row $sf

    switch -- $attr {
	role {
	    ComboBox $f.role$row -text none \
		-values {moderator participant visitor none} \
		-editable no \
		-width 11 \
		-textvariable muc::listdata($f,role,$row)
	    grid $f.role$row -row $row -column 2 -sticky we -padx 1m
	    bindscroll $f.role$row $sf
	}
	affiliation {
	    ComboBox $f.affiliation$row -text none \
		-values {owner admin member none outcast} \
		-editable no \
		-width 7 \
		-textvariable muc::listdata($f,affiliation,$row)
	    grid $f.affiliation$row -row $row -column 2 -sticky we -padx 1m
	    bindscroll $f.affiliation$row $sf
	}
    }

    entry $f.reason$row \
	-textvariable muc::listdata($f,reason,$row) \
	-width 40
    grid $f.reason$row -row $row -column 3 -sticky we -padx 1m
    bindscroll $f.reason$row $sf

    set listdata($f,$attr,$row) $val
}

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

proc muc::send_list {chatid attr val w f} {
    variable origlistdata
    variable listdata

    set items {}

    for {set i 1} {$i <= $origlistdata($f,rows)} {incr i} {
	set vars {}
	if {$listdata($f,$attr,$i) != $origlistdata($f,$attr,$i) || \
		$listdata($f,reason,$i) != $origlistdata($f,reason,$i)} {
	    lappend vars $attr $listdata($f,$attr,$i)
	}

	if {$vars != {}} {
	    if {$origlistdata($f,nick,$i) != ""} {
		lappend vars nick $origlistdata($f,nick,$i)
	    }
	    if {$origlistdata($f,jid,$i) != ""} {
		lappend vars jid $origlistdata($f,jid,$i)
	    }
	    set itemsubtags {}
	    set reason $listdata($f,reason,$i)
	    if {$reason != ""} {
	        lappend itemsubtags [jlib::wrapper:createtag reason \
	        			 -chdata $reason]
	    }
	    lappend items [jlib::wrapper:createtag item \
			       -vars $vars \
			       -subtags $itemsubtags]
	}
    }

    for {} {$i <= $listdata($f,rows)} {incr i} {
	set vars1 {}
	set vars2 {}
	if {$listdata($f,$attr,$i) != ""} {
	    lappend vars1 $attr $listdata($f,$attr,$i)
	}
	if {$listdata($f,nick,$i) != ""} {
	    lappend vars2 nick $listdata($f,nick,$i)
	}
	if {$listdata($f,jid,$i) != ""} {
	    lappend vars2 jid $listdata($f,jid,$i)
	}

	if {$vars1 != {} && $vars2 != {}} {
	    set vars [concat $vars2 $vars1]
	    set itemsubtags {}
	    set reason $listdata($f,reason,$i)
	    if {$reason != ""} {
	        lappend itemsubtags [jlib::wrapper:createtag reason \
	        			 -chdata $reason]
	    }
	    lappend items [jlib::wrapper:createtag item \
			       -vars $vars \
			       -subtags $itemsubtags]
	}
    }

    set connid [chat::get_connid $chatid]
    set group [chat::get_jid $chatid]

    if {$items != {}} {
	jlib::send_iq set [jlib::wrapper:createtag query \
			       -vars [list xmlns $::NS(muc#admin)] \
			       -subtags $items] \
	    -to $group \
	    -connection $connid \
	    -command [list muc::test_error_res \
			   [::msgcat::mc "Sending %s %s list" $attr $val] \
			   $connid $group]
    }
    destroy $w
}

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

proc muc::list_cleanup {w f} {
    variable listdata
    variable origlistdata

    array unset listdata ${f},*
    array unset origlistdata ${f},*
}

proc muc::request_config_dialog {chatid} {
    variable winid

    set w .muc_req_config$winid
    incr winid

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

    Dialog $w -title [::msgcat::mc "Room is created"] \
	-modal none -separator 1 -anchor e -default 0 -cancel 1

    set wf [$w getframe]
    message $wf.message -aspect 50000 \
	-text [::msgcat::mc "Room %s is successfully created" \
		    [chat::get_jid $chatid]]

    pack $wf.message -pady 2m

    $w add -text [::msgcat::mc "Configure room"] \
	-command "[list destroy $w]
		  [list [namespace current]::request_config $chatid]"
    $w add -text [::msgcat::mc "Accept default config"] \
	-command "[list destroy $w]
		  [list [namespace current]::request_instant_room $chatid]"

    $w draw
    
}

proc muc::request_instant_room {chatid} {
    jlib::send_iq set \
	[jlib::wrapper:createtag query \
	     -vars [list xmlns $::NS(muc#owner)] \
	     -subtags [list [jlib::wrapper:createtag x \
				 -vars [list xmlns $::NS(data) \
					     type submit]]]] \
	-to [chat::get_jid $chatid] \
	-connection [chat::get_connid $chatid]
}

proc muc::request_config {chatid} {
    jlib::send_iq get \
	[jlib::wrapper:createtag query \
	     -vars [list xmlns $::NS(muc#owner)]] \
	-to [chat::get_jid $chatid] \
	-connection [chat::get_connid $chatid] \
	-command [list muc::receive_config $chatid]
}

proc muc::receive_config {chatid res child} {
    set group [chat::get_jid $chatid]
    if {![cequal $res OK]} {
	chat::add_message $chatid $group error \
	    [::msgcat::mc "Configure form: %s" [error_to_string $child]] \
	    {}
	return
    }

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

    data::draw_window $children [list muc::send_config $chatid] \
				[list muc::cancel_config $chatid]
    return
}

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

proc muc::send_config {chatid w restags} {
    set connid [chat::get_connid $chatid]
    set group [chat::get_jid $chatid]
    jlib::send_iq set [jlib::wrapper:createtag query \
			   -vars [list xmlns $::NS(muc#owner)] \
			   -subtags $restags] \
	-to $group \
	-connection $connid \
	-command [list muc::test_error_res \
		       [::msgcat::mc "Sending configure form"] $connid $group]
    destroy $w
}

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

proc muc::cancel_config {chatid w} {
    set connid [chat::get_connid $chatid]
    set group [chat::get_jid $chatid]
    jlib::send_iq set [jlib::wrapper:createtag query \
			   -vars [list xmlns $::NS(muc#owner)] \
			   -subtags [list [jlib::wrapper:createtag x \
					       -vars [list xmlns jabber:x:data \
							   type cancel]]]] \
	-to $group \
	-connection $connid \
	-command [list muc::test_error_res \
		       [::msgcat::mc "Cancelling configure form"] $connid $group]
    destroy $w
}

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

proc muc::test_error_res {op connid group res child} {
    if {![cequal $res OK]} {
	set chatid [chat::chatid $connid $group]
	chat::add_message $chatid $group error \
	    [format "%s: %s" $op [error_to_string $child]] {}
	return
    }
}

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

proc muc::process_presence {connid from type x args} {
    switch -- $type {
	available -
	unavailable {
	    foreach xs $x {
		jlib::wrapper:splitxml $xs tag vars isempty chdata children
		if {[jlib::wrapper:getattr $vars xmlns] == $::NS(muc#user)} {
		    process_muc_user $connid $from $type $children
		    break
		}
	    }
	}
    }
}

hook::add client_presence_hook muc::process_presence

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

proc muc::process_muc_user {connid user type childrens} {
    variable users
    variable options

    foreach child $childrens {
	jlib::wrapper:splitxml $child tag vars isempty chdata children
	switch -- $tag {
	    item {
		if {$type != "unavailable"} {
		    set users(jid,$connid,$user) [jlib::wrapper:getattr $vars jid]
		    track_room_position $connid $user \
			[jlib::wrapper:getattr $vars role] \
			[jlib::wrapper:getattr $vars affiliation]
		} else {
		    set new_nick [jlib::wrapper:getattr $vars nick]
		    foreach ch $children {
			jlib::wrapper:splitxml $ch tag1 vars1 isempty1 \
			    chdata1 children1
			switch -- $tag1 {
			    reason {
				set reason $chdata1
			    }
			    actor {
				set actor [jlib::wrapper:getattr $vars1 jid]
			    }
			}
		    }
		}
	    }
	    destroy {
		set group [node_and_server_from_jid $user]
		set chatid [chat::chatid $connid $group]
		set msg [::msgcat::mc "Room is destroyed"]
		foreach ch $children {
		    jlib::wrapper:splitxml $ch tag1 vars1 isempty1 chdata1 children1
		    if {$tag1 == "reason" && [string trim $chdata1] != ""} {
			append msg [::msgcat::mc "\nReason: %s" [string trim $chdata1]]
		    }
		}
		set altjid [jlib::wrapper:getattr $vars jid]
		if {$altjid != ""} {
		    append msg [::msgcat::mc "\nAlternative venue: %s" $altjid]
		}
		if {$options(gen_enter_exit_msgs)} {
		    chat::add_message \
			$chatid $group groupchat $msg {}
		}
	    }
	    status {
		set code [jlib::wrapper:getattr $vars code]
		set group [node_and_server_from_jid $user]
		set chatid [chat::chatid $connid $group]
		switch -- $code {
		    201 {
			chat::add_message \
			    $chatid $group groupchat \
			    [::msgcat::mc "A new room is created"] {}
			# 201: room creation
			if {$options(propose_configure)} {
			    request_config_dialog $chatid
			} else {
			    # requesting "instant" room as specified in XEP-0045
			    # if the user wants to configure room (s)he can do it later
			    request_instant_room $chatid
			}
		    }
		    301 -
		    307 -
		    321 -
		    322 {
			# 301: ban, 307: kick, 321: loosing membership
			# 322: room becomes members-only
			set nick [chat::get_nick $connid $user groupchat]
			set real_jid [get_real_jid $connid $group/$nick]
			if {$real_jid != ""} {
			    set real_jid " ($real_jid)"
			}
			switch -- $code {
			    301 {set action \
				     [::msgcat::mc "%s has been banned" \
					  $nick$real_jid]}
			    307 {set action \
				     [::msgcat::mc "%s has been kicked" \
					  $nick$real_jid]}
			    321 {set action \
				     [::msgcat::mc \
					  "%s has been kicked because\
					   of membership loss" \
					  $nick$real_jid]}
			    322 {set action \
				     [::msgcat::mc \
					  "%s has been kicked because\
					   room became members-only" \
					  $nick$real_jid]}
			}

			if {[info exists actor] && $actor != ""} {
			    append action [::msgcat::mc " by %s" $actor]
			}

			if {[info exists reason] && $reason != ""} {
			    append action ": $reason"
			}

			if {$options(gen_enter_exit_msgs)} {
			    variable ignore_unavailable $nick
			    chat::add_message \
				$chatid $group groupchat $action {}
			}
		    }
		    303 {
			# 303: nickname change
			set nick [chat::get_nick $connid $user groupchat]
			if {[info exists new_nick] && $new_nick != ""} {
			    if {$nick == [get_our_groupchat_nick $chatid]} {
				set_our_groupchat_nick $chatid $new_nick
			    }
			    # TODO may be this reporting should not be done
			    # if the $nick is being ignored (MUC ignore)
			    if {$options(gen_enter_exit_msgs)} {
				variable ignore_available   $new_nick
				variable ignore_unavailable $nick

				set real_jid [get_real_jid $connid $group/$nick]
				if {$real_jid != ""} {
				    set real_jid " ($real_jid)"
				}
				chat::add_message \
				    $chatid $group groupchat \
				    [::msgcat::mc "%s is now known as %s" \
					 $nick$real_jid $new_nick] {}
			    }
			    ::hook::run room_nickname_changed_hook $connid $group $nick $new_nick
			}
		    }
		}
	    }
	}
    }
}


proc muc::process_available {chatid nick} {
    variable pending_nicks

    set connid [chat::get_connid $chatid]
    set group [chat::get_jid $chatid]

    if {![is_compatible $group] && [is_changing_nick $chatid] && \
	    [string equal $pending_nicks($chatid) $nick]} {
	set_our_groupchat_nick $chatid $nick
	unset pending_nicks($chatid)
    }
}


proc muc::process_unavailable {chatid nick} {
    variable options
    variable ignore_unavailable

    set group [chat::get_jid $chatid]

    if {[is_compatible $group] && $options(gen_enter_exit_msgs) && \
	    (![info exists ignore_unavailable] || \
		 $ignore_unavailable != $nick)} {
	set connid [chat::get_connid $chatid]
	set status [get_jid_presence_info status $connid $group/$nick]
	if {$status != ""} {
	    set end ": $status"
	} else {
	    set end ""
	}
	chat::add_message $chatid $group groupchat \
	    [cconcat [::msgcat::mc "%s has left" $nick] $end] {}
    }
    catch { unset ignore_unavailable }
}

proc muc::report_available {chatid nick entered} {
    variable options
    variable users
    variable ignore_available

    set connid [::chat::get_connid $chatid]
    set group [::chat::get_jid $chatid]

    if {![is_compatible $group]} return

    set jid $group/$nick

    set msg ""

    set report_entered [expr {$entered && $options(gen_enter_exit_msgs) \
	&& (![info exists ignore_available] || $ignore_available != $nick)}]

    if {$report_entered} {
	set real_jid [get_real_jid $connid $jid]
	if {$real_jid != ""} {
	    set occupant "$nick ($real_jid)"
	} else {
	    set occupant $nick
	}
	set msg [format [::msgcat::mc "%s has entered"] $occupant]
	if {$options(gen_muc_position_change_msgs)} {
	    append msg " " [::msgcat::mc "as %s/%s" \
		[::msgcat::mc $users(affiliation,$connid,$jid)] \
		[::msgcat::mc $users(role,$connid,$jid)]]
	}
    }
    catch { unset ignore_available }

    if {$options(gen_muc_status_change_msgs)} {
	set status [::get_user_status $connid $jid]
	if {$report_entered} {
	    append msg " " [::msgcat::mc "and"] " "
	} else {
	    append msg $nick " "
	}
	append msg [::get_long_status_desc $status]
	set desc [::get_user_status_desc $connid $jid]
	if {$desc != {}} {
	    append msg " ($desc)"
	}
    }

    ::chat::add_message $chatid $group groupchat $msg {}
}

proc muc::track_room_position {connid jid role affiliation} {
    variable options
    variable users

    upvar 0 users(role,$connid,$jid) _role \
	users(affiliation,$connid,$jid) _aff

    set group  [node_and_server_from_jid $jid]
    if {![is_compatible $group]} return

    set chatid [chat::chatid $connid $group]

    if {[chat::is_opened $chatid] && $options(gen_muc_position_change_msgs)} {
	set nick [chat::get_nick $connid $jid groupchat]

	set role_changed [expr {![info exists _role] \
	    || ![string equal $role $_role]}]
	set aff_changed  [expr {![info exists _aff] \
	    || ![string equal $affiliation $_aff]}]

	if {$aff_changed} {
	    if {$role_changed} {
		set msg [::msgcat::mc "%s has been assigned a new room position: %s/%s" \
		    $nick [::msgcat::mc $affiliation] [::msgcat::mc $role]]
	    } else {
		set msg [::msgcat::mc "%s has been assigned a new affiliation: %s" \
		    $nick [::msgcat::mc $affiliation]]
	    }
	} elseif {$role_changed} {
	    set msg [::msgcat::mc "%s has been assigned a new role: %s" \
		$nick [::msgcat::mc $role]]
	} else {
	    set msg ""
	}

	if {$msg != ""} {
	    ::chat::add_message $chatid $group groupchat $msg {}
	}
    }

    set _aff  $affiliation
    set _role $role
}

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

proc muc::change_nick {chatid nick} {
    global userstatus textstatus
    global statusdesc

    set group [chat::get_jid $chatid]

    if {![is_compatible $group]} {
	variable pending_nicks
	set pending_nicks($chatid) $nick
    }

    if {$userstatus == "invisible"} {
	set status available
    } else {
	set status $userstatus
    }

    if {$textstatus == ""} {
	set tstatus $statusdesc($status)
    } else {
	set tstatus $textstatus
    }

    send_custom_presence $group/$nick $status \
	-stat $tstatus \
	-connection [chat::get_connid $chatid] \
	-command [list muc::process_change_nick $chatid]
}


proc muc::process_change_nick {chatid connid from type x args} {
    if {![cequal $type error]} {
	return
    }

#    if {![cequal [lindex [jlib::wrapper:getattr $args -error] 1] conflict]} {
#	return
#    }

    if {![is_compatible [chat::get_jid $chatid]] && \
	    [is_changing_nick $chatid]} {
	variable pending_nicks
	unset pending_nicks($chatid)
    }
    chat::add_message $chatid [node_and_server_from_jid $from] error \
	[::msgcat::mc "Error %s" [jlib::wrapper:getattr $args -status]] {}
    return break
}


proc muc::is_changing_nick {chatid} {
    variable pending_nicks

    if {[info exists pending_nicks($chatid)]} {
	return 1
    } else {
	return 0
    }
}

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

proc muc::request_negotiation {connid group} {
    variable muc_compatible

    set muc_compatible($group) 0

    disco::request_info [server_from_jid $group] "" \
	-connection $connid \
	-cache yes \
	-handler [list muc::recv_negotiation1 $connid $group]
}


proc muc::recv_negotiation1 {connid group res identities features extras} {
    variable muc_compatible

    if {[cequal $res OK]} {
	foreach f $features {
	    set var [jlib::wrapper:getattr $f var]
	    if {$var == $::NS(muc)} {
		set muc_compatible($group) 1
		return
	    }
	}
    }

    disco::request_info $group "" \
	-connection $connid \
	-cache yes \
	-handler [list muc::recv_negotiation2 $group]
}

proc muc::recv_negotiation2 {group res identities features extras} {
    variable muc_compatible

    if {[cequal $res OK]} {
	foreach f $features {
	    set var [jlib::wrapper:getattr $f var]
	    if {$var == $::NS(muc)} {
		set muc_compatible($group) 1
		return
	    }
	}
    }
    set muc_compatible($group) 0
}


proc muc::is_compatible {group} {
    variable muc_compatible

    if {[info exists muc_compatible($group)]} {
	return $muc_compatible($group)
    } else {
	return 0
    }
}

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

proc muc::add_user_popup_info {infovar connid user} {
    variable users
    upvar 0 $infovar info

    if {[info exists users(jid,$connid,$user)] && \
	    $users(jid,$connid,$user) != ""} {
	append info [::msgcat::mc "\n\tJID: %s" $users(jid,$connid,$user)]
    }
    if {[info exists users(affiliation,$connid,$user)]} {
	append info [::msgcat::mc "\n\tAffiliation: %s" \
			 $users(affiliation,$connid,$user)]
    }
}

hook::add roster_user_popup_info_hook muc::add_user_popup_info

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

proc muc::set_message_timestamp {chatid from type body x} {
    variable timestamps

    if {![chat::is_disconnected $chatid]} {
	set timestamps($chatid) [clock seconds]
    }
}

hook::add draw_message_hook muc::set_message_timestamp 15

proc muc::clear_message_timestamp {chatid} {
    variable timestamps

    catch { unset timestamps($chatid) }
}

hook::add close_chat_post_hook muc::clear_message_timestamp

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

proc muc::join_group {connid group nick {password ""}} {
    global userstatus textstatus
    global statusdesc
    variable options
    variable timestamps
    variable muc_password

    set group [tolower_node_and_domain $group]
    set chatid [chat::chatid $connid $group]

    if {[info exists chat::chats(status,$chatid)] && \
	    ![string equal $chat::chats(status,$chatid) disconnected]} {
	change_nick $chatid $nick
	return
    }

    privacy::add_to_special_list $connid conference [server_from_jid $group]

    set_our_groupchat_nick $chatid $nick

    chat::open_window $chatid groupchat
    update idletasks

    request_negotiation $connid $group

    set x_subtags {}

    lappend x_subtags [jlib::wrapper:createtag password -chdata $password]
    set muc_password($chatid) $password

    set history_vars {}
    if {$options(history_maxchars) >= 0} {
	lappend history_vars maxchars $options(history_maxchars)
    }
    if {$options(history_maxstanzas) >= 0} {
	lappend history_vars maxstanzas $options(history_maxstanzas)
    }
    if {$options(request_only_unseen_history) && \
	    [info exists timestamps($chatid)]} {
	lappend history_vars \
		seconds [expr {[clock seconds] - $timestamps($chatid) + 2}]
    }
    if {![lempty $history_vars]} {
	lappend x_subtags [jlib::wrapper:createtag history -vars $history_vars]
    }

    if {$userstatus == "invisible"} {
	set status available
    } else {
	set status $userstatus
    }

    if {$textstatus == ""} {
	set tstatus $statusdesc($status)
    } else {
	set tstatus $textstatus
    }

    send_presence $status \
		  -to $group/$nick \
		  -stat $tstatus \
		  -connection $connid \
		  -xlist [list [jlib::wrapper:createtag x \
				    -vars [list xmlns $::NS(muc)] \
				    -subtags $x_subtags]]
}

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

proc muc::leave_group {connid group nick status} {
    send_presence unavailable \
		  -to $group/$nick \
		  -stat $status \
		  -connection $connid
}

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

proc muc::invite_muc {connid group jid reason} {
    # If $jid is a 'real' JID then invite
    # If $jid is in a conference room try to invite real JID

    set real_jid [get_real_jid $connid $jid]
    if {$real_jid == ""} {
	set real_jid $jid
    }

    message::send_msg $group \
	-xlist [list \
	    [jlib::wrapper:createtag x \
		 -vars [list xmlns $::NS(muc#user)] \
		 -subtags [list \
		     [jlib::wrapper:createtag invite \
			  -vars [list to $real_jid] \
			  -subtags [list [jlib::wrapper:createtag reason \
					      -chdata $reason]]]]]] \
	-connection $connid
}

proc muc::invite_xconference {connid group jid reason} {
    message::send_msg $jid \
	-type normal \
	-subject "Invitation" \
	-body $reason \
	-xlist [list [jlib::wrapper:createtag x \
			  -vars [list xmlns jabber:x:conference \
				      jid $group]]] \
	-connection $connid
}

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

proc muc::process_invitation {rowvar bodyvar f x connid from id type replyP} {
    upvar 2 $rowvar row
    upvar 2 $bodyvar body

    foreach xa $x {
	jlib::wrapper:splitxml $xa tag vars isempty chdata children
	set xmlns [jlib::wrapper:getattr $vars xmlns]

	switch -- $xmlns \
	    $::NS(xconference) {
		set xconference_group [jlib::wrapper:getattr $vars jid]
		set xconference_password ""
		if {[cequal $body ""] && ![cequal $chdata ""]} {
		    set xconference_body $chdata
		} else {
		    set xconference_body $body
		}
	    } \
	    $::NS(muc#user) {
		set password ""
		set inviter ""
		foreach ch $children {
		    jlib::wrapper:splitxml $ch tag1 vars1 isempty1 \
			chdata1 children1

		    switch -- $tag1 {
			invite {
			    set inviter [jlib::wrapper:getattr $vars1 from]
			    if {![cequal $inviter ""]} {
				foreach c [jlib::connections] {
				    set name \
					[roster::itemconfig $c \
					     [roster::find_jid $c $inviter] \
						  -name]
				    if {$name != ""} break
				}
				if {![cequal $name ""]} {
				    set inviter "$name ($inviter)"
				}
				set muc_body \
				    [::msgcat::mc \
					 "%s invites you to conference\
					  room %s" \
					 $inviter $from]

				foreach ch1 $children1 {
				    jlib::wrapper:splitxml $ch1 tag2 vars2 \
					isempty2 chdata2 children2
				    if {[cequal $tag2 "reason"]} {
					append muc_body \
					    [::msgcat::mc "\nReason is: %s" \
						 $chdata2]
				    }
				}
			    }
			    # TODO decline
			}
			password {
			    set password $chdata1
			}
		    }
		}
		if {![string equal $inviter ""]} {
		    set muc_group $from
		    set muc_password $password
		}
	    }
    }

    if {[info exists muc_group] && $muc_group != ""} {
	process_x_conference $f $connid $muc_group $muc_password $row
	incr row
	set body $muc_body
	return
    } elseif {[info exists xconference_group] && $xconference_group != ""} {
	process_x_conference $f $connid $xconference_group \
	    $xconference_password $row
	incr row
	set body $xconference_body
	return
    }
    
    return
}

hook::add message_process_x_hook muc::process_invitation

proc muc::process_x_conference {f connid group password row} {
    global gr_nick

    label $f.lgroup$row -text [::msgcat::mc "Invited to:"]
    button $f.group$row -text $group \
	-command [list ::join_group $group \
		       -nick [get_group_nick $group $gr_nick] \
		       -password $password \
		       -connection $connid]
    
    grid $f.lgroup$row -row $row -column 0 -sticky e
    grid $f.group$row  -row $row -column 1 -sticky ew
}

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

proc muc::join {jid args} {
    global gr_nick

    set category conference
    set newargs {}
    foreach {opt val} $args {
	switch -- $opt {
	    -category { set category $val }
	    -connection { lappend newargs -connection $val }
	}
    }

    if {![cequal $category conference]} {
	return
    }

    if {![cequal [node_from_jid $jid] {}]} {
	eval {::join_group $jid -nick [get_group_nick $jid $gr_nick]} \
	     $newargs
    } else {
	eval {::join_group_dialog -server [server_from_jid $jid] -group {}} \
	     $newargs
    }
}

hook::add postload_hook \
    [list disco::browser::register_feature_handler jabber:iq:conference \
	 muc::join -desc [list conference [::msgcat::mc "Join conference"]]]
hook::add postload_hook \
    [list disco::browser::register_feature_handler $::NS(muc) muc::join \
	 -desc [list conference [::msgcat::mc "Join conference"]]]
hook::add postload_hook \
    [list disco::browser::register_feature_handler "gc-1.0" muc::join \
	 -desc [list conference [::msgcat::mc "Join groupchat"]]]

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


iq::register_handler get query $::NS(muc) muc::iq_reply

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

proc muc::disco_reply {type connid from lang child} {
    variable options

    if {!$options(report_muc_rooms)} {
	return {error cancel not-allowed}
    }

    switch -- $type {
	info {
	    return {}
	}
	items {
	    set res {}
	    foreach chatid [lfilter chat::is_groupchat [chat::opened $connid]] {
		set group [chat::get_jid $chatid]
		if {[is_compatible $group]} {
		    lappend res [jlib::wrapper:createtag item \
				     -vars [list jid $group]]
		}
	    }
	    return $res
	}
    }
}

hook::add postload_hook \
    [list disco::register_node $::NS(muc#rooms) muc::disco_reply \
	  [::trans::trans "Current rooms"]]

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

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