package require textutil

option add *Chat.theyforeground        blue   widgetDefault
option add *Chat.meforeground          red3   widgetDefault
option add *Chat.highlightforeground   red3   widgetDefault
option add *Chat.serverlabelforeground green  widgetDefault
option add *Chat.serverforeground      violet widgetDefault
option add *Chat.infoforeground        blue   widgetDefault
option add *Chat.errforeground         red    widgetDefault
option add *Chat.inputheight           3      widgetDefault


namespace eval chat {
    set enrichid 0
    custom::defgroup Chat [::msgcat::mc "Chat options."] -group Tkabbur
    custom::defvar options(smart_scroll) 0 \
	[::msgcat::mc "Enable chat window autoscroll only when last message is shown."] \
	-type boolean -group Chat
    custom::defvar options(stop_scroll) 0 \
	[::msgcat::mc "Stop chat window autoscroll."] \
	-type boolean -group Chat
    custom::defvar options(display_status_description) 1 \
	[::msgcat::mc "Display description of user status in chat windows."] \
	-type boolean -group Chat
    custom::defvar options(default_message_type) chat \
	[::msgcat::mc "Default message type (if not specified explicitly)."] \
	-type radio -group Chat \
	-values [list normal [::msgcat::mc "Normal"] \
		      chat [string trim [::msgcat::mc "Chat "]]]

    custom::defvar open_chat_list {} [::msgcat::mc "List of users for chat."] \
	    -group Hidden

}

set chat_width 50
set chat_height 18

plugins::load [file join plugins chat]

proc chat::open_chat_dialog {} {
    variable open_chat_jid
    variable open_chat_list
    variable open_chat_connid

    if {[lempty [jlib::connections]]} return

    set gw .open_chat
    catch { destroy $gw }

    set connid [lindex [jlib::connections] 0]
    set open_chat_connid [jlib::connection_jid $connid]

    Dialog $gw -title [::msgcat::mc "Open chat"] -separator 1 -anchor e \
	    -default 0 -cancel 1

    set gf [$gw getframe]
    grid columnconfigure $gf 1 -weight 1

    set open_chat_jid ""

    label $gf.ljid -text [::msgcat::mc "JID:"]
    ecursor_entry [ComboBox $gf.jid -textvariable [namespace current]::open_chat_jid \
	    -values [linsert $open_chat_list 0 ""] -width 35].e
    
    grid $gf.ljid -row 0 -column 0 -sticky e
    grid $gf.jid  -row 0 -column 1 -sticky ew

    if {[llength [jlib::connections]] > 1} {
	set connections {}
	foreach c [jlib::connections] {
	    lappend connections [jlib::connection_jid $c]
	}
	label $gf.lconnection -text [::msgcat::mc "Connection:"]
	ComboBox $gf.connection -textvariable [namespace current]::open_chat_connid \
				-values $connections

	grid $gf.lconnection -row 1 -column 0 -sticky e
	grid $gf.connection  -row 1 -column 1 -sticky ew
    }

    $gw add -text [::msgcat::mc "Chat"] -command "[namespace current]::open_chat $gw"
    $gw add -text [::msgcat::mc "Cancel"] -command "destroy $gw"

    $gw draw $gf.jid
}

proc chat::open_chat {gw} {
    variable open_chat_jid
    variable open_chat_list
    variable open_chat_connid

    destroy $gw

    foreach c [jlib::connections] {
	if {[jlib::connection_jid $c] == $open_chat_connid} {
	    set connid $c
	}
    }
    if {![info exists connid]} {
	set connid [jlib::route $open_chat_jid]
    }

    set open_chat_list [update_combo_list $open_chat_list $open_chat_jid 20]
    open_to_user $connid $open_chat_jid
}

proc chat::get_nick {connid jid type} {
    variable chats

    set nick $jid
    switch -- $type {
	chat {
	    set group [node_and_server_from_jid $jid]
	    set chatid1 [chatid $connid $group]
	    if {[is_groupchat $chatid1]} {
		set nick [resource_from_jid $jid]
	    } else {
		set nick [roster::itemconfig $connid \
			      [roster::find_jid $connid $jid] -name]
		if {$nick == ""} {
		    if {[node_from_jid $jid] != ""} {
			set nick [node_from_jid $jid]
		    } else {
			set nick $jid
		    }
		}
	    }
	}
	groupchat {
	    set nick [resource_from_jid $jid]
	}
    }
    return $nick
}

proc chat::winid {chatid} {
    set connid [get_connid $chatid]
    set jid [get_jid $chatid]
    set tag [jid_to_tag $jid]
    return .chat_${connid}_$tag
}

proc chat::winid_to_chatid {winid} {
    variable chat_id
    expr {[info exists chat_id($winid)] ? $chat_id($winid) : ""}
}

proc chat::chatid {connid jid} {
    return [list $connid $jid]
}

proc chat::get_connid {chatid} {
    return [lindex $chatid 0]
}

proc chat::get_jid {chatid} {
    return [lindex $chatid 1]
}

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

proc client:message \
     {connid from id type is_subject subject body err thread priority x} {

    debugmsg chat "MESSAGE: $connid; $from; $id; $type; $is_subject;\
		   $subject; $body; $err; $thread; $priority; $x"

    hook::run rewrite_message_hook connid from id type is_subject \
				   subject body err thread priority x

    debugmsg chat "REWRITTEN MESSAGE: $connid; $from; $id; $type; $is_subject;\
		   $subject; $body; $err; $thread; $priority; $x"

    hook::run process_message_hook $connid $from $id $type $is_subject \
				   $subject $body $err $thread $priority $x
}

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

proc chat::rewrite_message \
     {vconnid vfrom vid vtype vis_subject vsubject \
      vbody verr vthread vpriority vx} {
    upvar 2 $vfrom from
    upvar 2 $vtype type
    variable options

    if {$type == ""} {
	set type $options(default_message_type)
    }

    set from [tolower_node_and_domain $from]
}

hook::add rewrite_message_hook [namespace current]::chat::rewrite_message 99

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

proc chat::process_message_fallback \
     {connid from id type is_subject subject body err thread priority x} {
    variable chats

    set chatid [chatid $connid $from]

    switch -- $type {
	chat {
	    if {$thread != ""} {
		set chats(thread,$chatid) $thread
	    }
	    set chats(id,$chatid) $id
	    if {$is_subject && $subject!=""} {
			set abody [format [::msgcat::mc "Subject is set to: %s"] $subject]
			chat::add_message $chatid $from myinfo $abody {}
	    }
	}
	groupchat {
	    set chatid [chatid $connid [node_and_server_from_jid $from]]

	    if {![is_groupchat $chatid]} return

	    if {$is_subject && $subject!=""} {
		set_subject $chatid $subject
		if {[cequal $body ""]} {
		    set nick [resource_from_jid $from]
		    if {[cequal $nick ""]} {
			set body [format [::msgcat::mc "Subject is set to: %s"] $subject]
		    } else {
			set body [format [::msgcat::mc "/me has set the subject to: %s"] $subject]
		    }
		}
	    }
	}
	error {
	    if {[is_groupchat $chatid]} {
		if {$is_subject} {
		    set body "subject: "
		    restore_subject $chatid
		} else {
		    set body ""
		}
		append body [error_to_string $err]
	    } else {
		if {$is_subject && $subject != ""} {
		    set body "[::msgcat::mc {Subject:}] $subject\n$body"
		}
		set body "[error_to_string $err]\n$body"
	    }
	}
	default {
	    debugmsg chat "MESSAGE: UNSUPPORTED message type '$type'"
	}
    }

    chat::open_window $chatid $type

    chat::add_message $chatid $from $type $body $x
    if {[llength $x] > 0} {
        message::show_dialog $connid $from $id $type $subject $body $thread $priority $x 0
    }
}

hook::add process_message_hook \
    [namespace current]::chat::process_message_fallback 99

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

proc chat::window_titles {chatid} {
    set connid [get_connid $chatid]
    set jid [get_jid $chatid]
    set chatname [roster::itemconfig $connid \
		      [roster::find_jid $connid $jid] -name]
    if {$chatname != ""} {
	set tabtitlename $chatname
	set titlename $chatname
    } else {
	set titlename $jid
	if {[is_groupchat $chatid]} {
	    set tabtitlename [node_from_jid $jid]
	} else {
	    set tabtitlename [get_nick $connid $jid chat]
	}
	if {$tabtitlename == ""} {
	    set tabtitlename $titlename
	}
    }
    set titlename "($connid) $titlename"
    set tabtitlename "($connid) $tabtitlename"
    return [list $tabtitlename [format [::msgcat::mc "Chat with %s"] $titlename]]
}

proc chat::reconnect_groupchats {connid} {
    variable chats
    global grouproster
	
    foreach chatid [opened $connid] {
	if {[is_groupchat $chatid]} {
	    if {[info exists ::muc::muc_password($chatid)]} {
		set password $::muc::muc_password($chatid)
	    } else {
		set password ""
	    }
	    muc::join_group $connid \
			    [get_jid $chatid] \
			    [get_our_groupchat_nick $chatid] \
			    $password
	} else {
	    set chats(status,$chatid) connected
	}
    }
}

hook::add connected_hook [namespace current]::chat::reconnect_groupchats 99

proc chat::disconnect_groupchats {connid} {
    variable chats
    global statusdesc

    foreach chatid [opened $connid] {
	if {![winfo exists [chat_win $chatid]]} return

	set jid [get_jid $chatid]

	if {$chats(status,$chatid) != "disconnected"} {
	    set chats(status,$chatid) disconnected
	    add_message $chatid $jid error [::msgcat::mc "Disconnected"] {}
	}

	set cw [winid $chatid]

	if {[winfo exists $cw.status.icon]} {
	    $cw.status.icon configure \
		-image [ifacetk::roster::get_jid_icon $connid $jid unavailable] \
		-helptext ""
	}

	if {[winfo exists $cw.status.desc]} {
	    $cw.status.desc configure \
		-text "($statusdesc(unavailable))" \
		-helptext ""
	}
    }
}

hook::add predisconnected_hook [namespace current]::chat::disconnect_groupchats 99

proc chat::open_window {chatid type args} {
    global chat_width chat_height
    variable chats
    variable chat_id
    variable options
    global grouproster
    global statusdesc

    set connid [get_connid $chatid]
    set jid [get_jid $chatid]
    set jid [tolower_node_and_domain $jid]
    set chatid [chatid $connid $jid]

    set cw [winid $chatid]

    set cleanroster 1
    foreach {key val} $args {
	switch -- $key {
	    -cleanroster { set cleanroster $val }
	}
    }

    if {[winfo exists $cw]} {
	if {$type == "groupchat" && \
		$chats(status,$chatid) == "disconnected" && \
		$cleanroster} {
	    set grouproster(users,$chatid) {}
	}

	if {!$::usetabbar && \
		[info exists ::raise_on_activity] && $::raise_on_activity} {
	    if {$type == "chat"} {
		wm deiconify $cw
	    }
	    raise $cw
	}
	return
    }

    hook::run open_chat_pre_hook $chatid $type

    set chats(type,$chatid) $type
    set chats(subject,$chatid) ""
    if {$type == "groupchat"} {
	set chats(status,$chatid) disconnected
    } else {
	set chats(status,$chatid) connected
    }
    set chats(exit_status,$chatid) ""
    add_to_opened $chatid
    
    set chat_id($cw) $chatid

    lassign [chat::window_titles $chatid] chats(tabtitlename,$chatid) \
	chats(titlename,$chatid)

    add_win $cw -title $chats(titlename,$chatid) \
	-tabtitle $chats(tabtitlename,$chatid) \
	-class Chat -type $type \
	-raisecmd "focus [list $cw.input]
                   hook::run raise_chat_tab_hook [list $cw] [list $chatid]"

    frame $cw.status
    pack $cw.status -side top -fill x
    if {[cequal $type chat]} {
	set status [get_user_status $connid $jid]
	Label $cw.status.icon \
	    -image [ifacetk::roster::get_jid_icon $connid $jid $status] \
	    -helptext [get_user_status_desc $connid $jid]
	pack $cw.status.icon -side left
	if {$options(display_status_description)} {
	    Label $cw.status.desc -text "($statusdesc($status))"  \
		-helptext [get_user_status_desc $connid $jid]
	    pack $cw.status.desc -side left
	}
    }
    if {[cequal $type chat]} {
	menubutton $cw.status.mb -text $jid  \
	    -menu $cw.status.mb.menu
	create_user_menu $cw.status.mb.menu $chatid
	pack $cw.status.mb -side left
    } else {
        menubutton $cw.status.mb -text [::msgcat::mc "Subject:"] \
	    -direction below \
	    -menu $cw.status.mb.menu -relief flat
	create_conference_menu $cw.status.mb.menu $chatid
	pack $cw.status.mb -side left

	entry $cw.status.subject  \
	    -xscrollcommand [list [namespace current]::set_subject_tooltip $chatid]
	pack $cw.status.subject -side left -fill x -expand yes

	bind $cw.status.subject <Return> \
	    [list chat::change_subject [double% $chatid]]

	bind $cw.status.subject <Escape> \
	    [list chat::restore_subject [double% $chatid]]

	balloon::setup $cw.status.subject \
		       -command [list [namespace current]::set_subject_balloon $chatid]
    }
    foreach tag [bind Menubutton] {
        if {[string first 1 $tag] >= 0} {
	    regsub -all 1 $tag 3 new
	    bind $cw.status.mb $new [bind Menubutton $tag]
	}
    }

    PanedWin $cw.pw0 -side right -pad 0 -width 4
    pack $cw.pw0 -fill both -expand yes


    set upw [PanedWinAdd $cw.pw0 -weight 1 -minsize 0]
    set dow [PanedWinAdd $cw.pw0 -weight 0 -minsize 0]

    set isw [ScrolledWindow $cw.isw -scrollbar vertical]
    pack $cw.isw -fill both -expand yes -side bottom -in $dow
    textUndoable $cw.input -width $chat_width \
	-height [option get $cw inputheight Chat] -wrap word
    $isw setwidget $cw.input
    [winfo parent $dow] configure -height [winfo reqheight $cw.input]
    set chats(inputwin,$chatid) $cw.input

    if {[cequal $type groupchat]} {
	PanedWin $cw.pw -side bottom -pad 1 -width 4
	pack $cw.pw -fill both -expand yes -in $upw

	set cf [PanedWinAdd $cw.pw -weight 1 -minsize 0]

	set uw [PanedWinAdd $cw.pw -weight 0 -minsize 0]
	set chats(userswin,$chatid) $uw.users

	set rosterwidth [option get . chatRosterWidth [winfo class .]]
	if {$rosterwidth == ""} {
	    set rosterwidth [winfo pixels . 3c]
	}

	ifacetk::roster::create $uw.users -width $rosterwidth \
	    -popup ifacetk::roster::groupchat_popup_menu \
	    -singleclick [list [namespace current]::user_singleclick $chatid] \
	    -doubleclick ::ifacetk::roster::jid_doubleclick \
	    -draginitcmd [namespace current]::draginitcmd \
	    -dropovercmd [list [namespace current]::dropovercmd $chatid] \
	    -dropcmd [list [namespace current]::dropcmd $chatid]
	pack $uw.users -fill both -side right -expand yes
	[winfo parent $uw] configure -width $rosterwidth

	set grouproster(users,$chatid) {}

	set pack_in $cf
    } else {
	set cf $cw
	set pack_in $upw
    }

    set chats(chatwin,$chatid) $cf.chat

    set csw [ScrolledWindow $cf.csw -scrollbar vertical -auto none]
    pack $csw -expand yes -fill both -side top -in $pack_in

    ::richtext::richtext $cf.chat \
        -width $chat_width -height $chat_height \
        -wrap word

    ::plugins::chatlog::config $cf.chat \
	-theyforeground [query_optiondb $cw theyforeground] \
	-meforeground [query_optiondb $cw meforeground] \
	-serverlabelforeground [query_optiondb $cw serverlabelforeground] \
	-serverforeground [query_optiondb $cw serverforeground] \
	-infoforeground [query_optiondb $cw infoforeground] \
	-errforeground [query_optiondb $cw errforeground] \
	-highlightforeground [query_optiondb $cw highlightforeground]

    $csw setwidget $cf.chat

    reverse_scroll $cf.chat

    focus $cw.input

    bind $cw.input <Shift-Key-Return> { }

    bind $cw.input <Key-Return> [double% "
	chat::send_message [list $cw] [list $chatid] [list $type]
	break"]

    regsub -all %W [bind Text <Prior>] $cf.chat prior_binding
    regsub -all %W [bind Text <Next>] $cf.chat next_binding
    bind $cw.input <Meta-Prior> $prior_binding
    bind $cw.input <Meta-Next> $next_binding
    bind $cw.input <Alt-Prior> $prior_binding
    bind $cw.input <Alt-Next> $next_binding
    bind $cw.input <Meta-Prior> +break
    bind $cw.input <Meta-Next> +break
    bind $cw.input <Alt-Prior> +break
    bind $cw.input <Alt-Next> +break
    bind $cw.input <Control-Meta-Prior> continue
    bind $cw.input <Control-Meta-Next> continue
    bind $cw.input <Control-Alt-Prior> continue
    bind $cw.input <Control-Alt-Next> continue

    bind $cw <Destroy> [list chat::close_window [double% $chatid]]

    hook::run open_chat_post_hook $chatid $type

    if {$type=="chat"} {
	set msg [get_nick $connid $jid chat]
	append msg " " [::get_long_status_desc [::get_user_status $connid $jid]]
	set desc [::get_user_status_desc $connid $jid]
	if {$desc != {}} {
		append msg " ($desc)"
	}
	::chat::add_message $chatid "" myinfo $msg {}
    }
}

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

# This proc is used by the "richtext widget" to query the option DB for
# it's attributes which are really maintained by the main chat window
proc chat::query_optiondb {w option} {
    option get $w $option Chat
}

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

proc chat::draginitcmd {target x y top} {
    return {}
}

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

proc chat::dropovercmd {chatid target source event x y op type data} {
    variable chats

    set chat_connid [get_connid $chatid]
    lassign $data jid_connid jid

    if {$source != ".roster.canvas" || \
	    ![info exists chats(status,$chatid)] || \
	    $chats(status,$chatid) == "disconnected" || \
	    $chat_connid != $jid_connid || \
	    ![::roster::itemconfig $jid_connid $jid -isuser]} {
	DropSite::setcursor dot
	return 0
    } else {
	DropSite::setcursor based_arrow_down
	return 1
    }
}

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

proc chat::dropcmd {chatid target source x y op type data} {
    set group [get_jid $chatid]
    lassign $data connid jid
    set reason [::msgcat::mc "Please join %s" $group]

    if {[muc::is_compatible $group]} {
	muc::invite_muc $connid $group $jid $reason
    } else {
	muc::invite_xconference $connid $group $jid $reason
    }
}

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

proc chat::user_singleclick {chatid cjid} {
    lassign $cjid connid jid
    set nick [get_nick $connid $jid groupchat]
    hook::run groupchat_roster_user_singleclick_hook $chatid $nick
}

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

proc chat::bind_window_click {chatid type} {
    set cw [chat::chat_win $chatid]
    bind $cw <ButtonPress-1><ButtonRelease-1> \
	[list hook::run chat_window_click_hook [double% $chatid] %W %x %y]
}

hook::add open_chat_post_hook [namespace current]::chat::bind_window_click

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

proc chat::close_window {chatid} {
    variable chats
    variable chat_id

    if {![is_opened $chatid]} return

    remove_from_opened $chatid

    set connid [get_connid $chatid]
    set jid [get_jid $chatid]

    set cw [winid $chatid]
    unset chat_id($cw)

    if {[is_groupchat $chatid]} {
	muc::leave_group $connid $jid [get_our_groupchat_nick $chatid] \
			 $chats(exit_status,$chatid)
	set chats(status,$chatid) disconnected
	client:presence $connid $jid unavailable "" {}
	foreach jid [get_jids_of_user $connid $jid] {
	    client:presence $connid $jid unavailable "" {}
	}
    }

    destroy $cw
    hook::run close_chat_post_hook $chatid
}

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

proc chat::trace_room_nickname_change {connid group nick new_nick} {
    set from $group/$nick
    set to $group/$new_nick
    set chatid [::chat::chatid $connid $from]
    set ix [lsearch -exact [::chat::opened] $chatid]
    if {$ix < 0} return

    set msg [format [::msgcat::mc \
	"%s has changed nick to %s."] \
	$nick $new_nick]
    ::chat::add_message $chatid "" chat $msg {}

    set cw [chat_win $chatid]
    $cw config -state normal
    $cw delete {end - 1 char} ;# zap trailing newline
    $cw insert end " "
    set tooltip [::msgcat::mc "Opens a new chat window\
	for the new nick of the room occupant"]
    ::plugins::urls::render_url $cw url $tooltip {} \
	-title [::msgcat::mc "Open new conversation"] \
	-command [list [namespace current]::open_to_user $connid $to]
    $cw insert end \n
    $cw config -state disabled
}

hook::add room_nickname_changed_hook chat::trace_room_nickname_change

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

proc chat::check_connid {connid chatid} {
    if {[get_connid $chatid] == $connid} {
	return 1
    } else {
	return 0
    }
}

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

proc chat::opened {{connid {}}} {
    variable chats

    if {![info exists chats(opened)]} {
	return {}
    } elseif {$connid != {}} {
	return [lfilter [list [namespace current]::check_connid $connid] \
			$chats(opened)]
    } else {
	return $chats(opened)
    }
}

proc chat::is_opened {chatid} {
    variable chats

    if {[info exists chats(opened)] && \
	    ([lsearch -exact $chats(opened) $chatid] >= 0)} {
	return 1
    } else {
	return 0
    }
}

proc chat::add_to_opened {chatid} {
    variable chats

    lappend chats(opened) $chatid
    set chats(opened) [lsort -unique $chats(opened)]
}

proc chat::remove_from_opened {chatid} {
    variable chats

    set idx [lsearch -exact $chats(opened) $chatid]
    if {$idx >= 0} {
	set chats(opened) [lreplace $chats(opened) $idx $idx]
    }
}

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

proc chat::users_win {chatid} {
    variable chats
    return $chats(userswin,$chatid)
}

proc chat::chat_win {chatid} {
    variable chats
    return $chats(chatwin,$chatid)
}

proc chat::input_win {chatid} {
    variable chats
    return $chats(inputwin,$chatid)
}

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

proc chat::is_chat {chatid} {
    variable chats
    if {[info exists chats(type,$chatid)]} {
	return [cequal $chats(type,$chatid) chat]
    } else {
	return 1
    }
}

proc chat::is_groupchat {chatid} {
    variable chats
    if {[info exists chats(type,$chatid)]} {
	return [cequal $chats(type,$chatid) groupchat]
    } else {
	return 0
    }
}

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

proc chat::is_disconnected {chatid} {
    variable chats

    if {![info exists chats(status,$chatid)] || \
	    $chats(status,$chatid) == "disconnected"} {
	return 1
    } else {
	return 0
    }

}

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

proc chat::create_user_menu {m chatid} {
    menu $m -tearoff 0

    set connid [get_connid $chatid]
    set jid [get_jid $chatid]

    hook::run chat_create_user_menu_hook $m $connid $jid

    return $m
}

proc chat::create_conference_menu {m chatid} {
    menu $m -tearoff 0

    set connid [get_connid $chatid]
    set jid [get_jid $chatid]

    hook::run chat_create_conference_menu_hook $m $connid $jid

    return $m
}

proc chat::add_separator {m connid jid} {
    $m add separator
}

hook::add chat_create_user_menu_hook chat::add_separator 40
hook::add chat_create_user_menu_hook chat::add_separator 42
hook::add chat_create_user_menu_hook chat::add_separator 50
hook::add chat_create_user_menu_hook chat::add_separator 70
hook::add chat_create_user_menu_hook chat::add_separator 85
hook::add chat_create_conference_menu_hook chat::add_separator 40
hook::add chat_create_conference_menu_hook chat::add_separator 42
hook::add chat_create_conference_menu_hook chat::add_separator 50

proc chat::send_message {cw chatid type} {
    set iw $cw.input

    set connid [get_connid $chatid]

    if {[catch { set user [jlib::connection_user $connid] }]} {
	set user ""
    }

    set body [$iw get 1.0 "end -1 chars"]

    debugmsg chat "SEND_MESSAGE:\
 [list $chatid] [list $user] [list $body] [list $type]"

    set chatw [chat_win $chatid]
    $chatw mark set start_message "end -1 chars"
    $chatw mark gravity start_message left

    hook::run chat_send_message_hook $chatid $user $body $type

    $iw delete 1.0 end
    catch {$iw edit reset}
}

proc chat::add_message {chatid from type body x} {
    variable chats
    variable options

    if {[is_opened $chatid]} {
	set chatw [chat_win $chatid]

	if {[lindex [$chatw yview] 1] == 1} {
	    set scroll 1
	} else {
	    set scroll 0
	}
    } else {
	set scroll 1
    }

    hook::run draw_message_hook $chatid $from $type $body $x

    if {[is_opened $chatid]} {
	set chatw [chat_win $chatid]

	if {![$chatw compare "end -1 chars linestart" == "end -1 chars"]} {
	    $chatw insert end "\n"
	}

	# In the last condition we work around the underscrolling if the chat
	# window wasn't maped yet. It's unlikely that mapped chat window will
	# be exactly 1 pixel tall.
	if {$body != "" && !$options(stop_scroll) && \
		(!$options(smart_scroll) || \
		     ($options(smart_scroll) && $scroll) || \
		     [chat::is_our_jid $chatid $from] || \
		     [winfo height $chatw] == 1)} {
	    after idle [list catch [list $chatw yview moveto 1]]
	}

	$chatw configure -state disabled
    }
}

proc chat::add_open_to_user_menu_item {m connid jid} {
    $m add command -label [::msgcat::mc "Start chat"] \
	-command [list chat::open_to_user $connid $jid]
}

hook::add roster_create_groupchat_user_menu_hook \
    [namespace current]::chat::add_open_to_user_menu_item 10
hook::add roster_jid_popup_menu_hook \
    [namespace current]::chat::add_open_to_user_menu_item 10
hook::add message_dialog_menu_hook \
    [namespace current]::chat::add_open_to_user_menu_item 10
hook::add search_popup_menu_hook \
    [namespace current]::chat::add_open_to_user_menu_item 10

proc chat::open_to_user {connid user args} {
    set msg ""
    foreach {opt val} $args {
	switch -- $opt {
	    -message { set msg $val }
	}
    }

    if {$connid == ""} {
	set connid [jlib::route $user]
    }

    set jid [get_jid_of_user $connid $user]

    if {[cequal $jid ""]} {
	set jid $user
    }
    set jid [tolower_node_and_domain $jid]
    set chatid [chatid $connid $jid]
    set cw [winid $chatid]
    if {![winfo exists $cw]} {
	chat::open_window $chatid chat
    }
    raise_win $cw
    focus -force $cw.input

    if {![cequal $msg ""]} {
	debugmsg chat "SEND_MESSAGE ON OPEN:\
	    [list $chatid] [jlib::connection_user $connid] [list $msg] chat"

	set chatw [chat_win $chatid]
	$chatw mark set start_message "end -1 chars"
	$chatw mark gravity start_message left

	hook::run chat_send_message_hook $chatid [jlib::connection_user $connid] \
	    $msg chat
    }
}

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

proc chat::change_presence {connid jid type x args} {
    global grouproster
    variable chats
    variable options
    global statusdesc

    switch -- $type {
	error -
	unavailable { set status $type }
	available {
	    set status available
	    foreach {key val} $args {
		switch -- $key {
		    -show { set status [normalize_show $val] }
		}
	    }
	}
	default { return }
    }

    set group [node_and_server_from_jid $jid]
    set chatid [chatid $connid $group]

    if {[is_opened $chatid]} {
	if {[is_groupchat $chatid]} {
	    debugmsg chat "ST: $connid $jid $status"
	    if {[resource_from_jid $jid] == ""} {
		return
	    }
	    set nick [get_nick $connid $jid groupchat]
	    if {[cequal $status unavailable] || [cequal $status error]} {
		if {$status == "error" && [is_our_jid $chatid $jid]} {
		    add_message $chatid $group error \
			[format [::msgcat::mc "Error %s"] \
			     [get_jid_presence_info status $connid $jid]] {}
			set chats(status,$chatid) disconnected
			client:presence $connid $group unavailable "" {}
		}
		if {$status == "unavailable" && [is_our_jid $chatid $jid]} {
		    if {[muc::is_compatible $group] || \
			    ![muc::is_changing_nick $chatid]} {
			add_message $chatid $group error \
			    [::msgcat::mc "Disconnected"] {}
			set chats(status,$chatid) disconnected
			client:presence $connid $group unavailable "" {}
		    }
		}
		debugmsg chat "$jid UNAVAILABLE"
		muc::process_unavailable $chatid $nick
		hook::run chat_user_exit $chatid $nick
		lvarpop grouproster(users,$chatid) \
		    [lsearch -exact $grouproster(users,$chatid) $jid]
		catch { unset grouproster(status,$chatid,$jid) }
		debugmsg chat "GR: $grouproster(users,$chatid)"
		chat::redraw_roster_after_idle $chatid
	    } else {
		if {$chats(status,$chatid) == "disconnected" && \
			[is_our_jid $chatid $jid]} {
		    set chats(status,$chatid) connected
		    client:presence $connid $group "" "" {}
		}
		set userswin [users_win $chatid]
		if {![lcontain $grouproster(users,$chatid) $jid]} {
		    #roster::addline $userswin jid $nick $jid
		    lappend grouproster(users,$chatid) $jid
		    set grouproster(status,$chatid,$jid) $status

		    muc::process_available $chatid $nick
		    hook::run chat_user_enter $chatid $nick
		    muc::report_available $chatid $nick 1
		} else {
		    muc::report_available $chatid $nick 0
		}
		set grouproster(status,$chatid,$jid) $status
		ifacetk::roster::changeicon $userswin $jid roster/user/$status
		ifacetk::roster::changeforeground $userswin $jid $status
		chat::redraw_roster_after_idle $chatid
	    }
	}
    }

    # This case traces both chat and private groupchat conversations:
	set chatid [chatid $connid $jid]
	if {[is_opened $chatid]} {
	    set msg [get_nick $connid $jid chat]
	    append msg " " [::get_long_status_desc [::get_user_status $connid $jid]]
	    set desc [::get_user_status_desc $connid $jid]
	    if {$desc != {}} {
		append msg " ($desc)"
	    }
	    ::chat::add_message $chatid "" myinfo $msg {}
	}

    set cw [winid [chatid $connid $jid]]

    if {[winfo exists $cw.status.icon]} {
	$cw.status.icon configure \
	    -image [ifacetk::roster::get_jid_icon $connid $jid $status] \
	    -helptext [get_user_status_desc $connid $jid]
    }

    if {[winfo exists $cw.status.desc]} {
	$cw.status.desc configure -text "($statusdesc([get_user_status $connid $jid]))" \
	    -helptext [get_user_status_desc $connid $jid]
    }

    set user [node_and_server_from_jid $jid]
    set cw [winid [chatid $connid $user]]

    if {[winfo exists $cw.status.icon]} {
	$cw.status.icon configure \
	    -image [ifacetk::roster::get_jid_icon \
			$connid $user [get_user_status $connid $user]] \
	    -helptext [get_user_status_desc $connid $user]
    }
}

hook::add client_presence_hook chat::change_presence 70

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

proc chat::redraw_roster {group} {
    global grouproster

    set connid [get_connid $group]
    set userswin [users_win $group]

    set grouproster(users,$group) [lsort $grouproster(users,$group)]
    ifacetk::roster::clear $userswin 0

    set levels {}
    foreach jid $grouproster(users,$group) {
	if {[info exists ::muc::users(role,$connid,$jid)]} {
	    if {$::muc::users(role,$connid,$jid) == ""} {
		set level a[::msgcat::mc "Users"]
		lappend levels $level
		lappend levelusers($level) $jid
	    } else {
		if {$::muc::users(role,$connid,$jid) != "" && \
			$::muc::users(role,$connid,$jid) != "none"} {
		    set level $::muc::users(role,$connid,$jid)
		    switch -- $level {
			moderator   {set level 4[::msgcat::mc "Moderators"]}
			participant {set level 5[::msgcat::mc "Participants"]}
			visitor     {set level 6[::msgcat::mc "Visitors"]}
		    }
		    lappend levels $level
		    lappend levelusers($level) $jid
		}

		#if {$::muc::users(affiliation,$jid) != "" && \
		#	$::muc::users(affiliation,$jid) != "none"} {
		#    set level $::muc::users(affiliation,$jid)
		#    switch -- $level {
		#	owner   {set level 0Owners}
		#	admin   {set level 1Admins}
		#	member  {set level 2Members}
		#	outcast {set level 9Outcasts}
		#    }
		#    lappend levels $level
		#    lappend levelusers($level) $jid
		#}
	    }
	} else {
	    set level 8[::msgcat::mc "Users"]
	    lappend levels $level
	    lappend levelusers($level) $jid
	}	    
    }
    set levels [lrmdups $levels]

    foreach level $levels {
	ifacetk::roster::addline $userswin group \
	    "[crange $level 1 end] ([llength $levelusers($level)])" \
	    [crange $level 1 end] [crange $level 1 end] 0
	set jid_nicks {}
	foreach jid $levelusers($level) {
	    lappend jid_nicks [list $jid [get_nick $connid $jid groupchat]]
	}
	set jid_nicks [lsort -index 1 -dictionary $jid_nicks]

	foreach item $jid_nicks {
	    lassign $item jid nick
	    set status $grouproster(status,$group,$jid)

	    ifacetk::roster::addline $userswin jid $nick \
		[list $connid $jid] [crange $level 1 end] 0
	    ifacetk::roster::changeicon $userswin \
		[list $connid $jid] roster/user/$status
	    if {$::plugins::nickcolors::options(use_colored_roster_nicks)} {
		ifacetk::roster::changeforeground $userswin \
		    [list $connid $jid] $status \
		    [plugins::nickcolors::get_color $nick]
	    } else {
		ifacetk::roster::changeforeground $userswin \
		    [list $connid $jid] $status
	    }
	}
    }

    ifacetk::roster::update_scrollregion $userswin
}

proc chat::redraw_roster_after_idle {group} {
    variable afterid

    if {[info exists afterid($group)]} \
	return

    set afterid($group) [after idle "
	chat::redraw_roster [list $group]
	unset [list chat::afterid($group)]
    "]
}

proc chat::restore_subject {chatid} {
    variable chats

    set sw [winid $chatid].status.subject

    if {[is_opened $chatid] && [winfo exists $sw]} {
	$sw delete 0 end
	$sw insert 0 $chats(subject,$chatid)
    }
}

proc chat::set_subject {chatid subject} {
    variable chats

    set cw [winid $chatid]

    if {[is_opened $chatid]} {
	$cw.status.subject delete 0 end
	$cw.status.subject insert 0 $subject
	set chats(subject,$chatid) $subject
    }
}

proc chat::change_subject {chatid} {
    set cw [winid $chatid]
    set connid [get_connid $chatid]
    set jid [get_jid $chatid]

    set subject [$cw.status.subject get]

    message::send_msg $jid -connection $connid \
	-type groupchat -subject $subject
}

proc chat::set_subject_balloon {chatid} {
    variable chats

    set sw [winid $chatid].status.subject

    if {[info exists chats(subject_tooltip,$chatid)]} {
	return [list $chatid $chats(subject_tooltip,$chatid) \
		     -width [winfo width $sw]]
    } else {
	return [list $chatid ""]
    }
}

proc chat::set_subject_tooltip {chatid lo hi} {
    variable chats

    set sw [winid $chatid].status.subject

    if {![winfo exists $sw]} return

    if {($lo == 0) && ($hi == 1)} {
	set chats(subject_tooltip,$chatid) ""
    } else {
	set chats(subject_tooltip,$chatid) [$sw get]
    }
}

proc chat::is_our_jid {chatid jid} {
    return [cequal [our_jid $chatid] $jid]
}

proc chat::our_jid {chatid} {
    variable chats

    set connid [get_connid $chatid]
    set jid [get_jid $chatid]
    switch -- $chats(type,$chatid) {
	groupchat {
	    return $jid/[get_our_groupchat_nick $chatid]
	}
	chat {
	    set group [node_and_server_from_jid $jid]
	    set groupid [chatid $connid $group]
	    if {[is_groupchat $groupid]} {
		return $group/[get_our_groupchat_nick $groupid]
	    } else {
		return [jlib::connection_jid $connid]
	    }
	}
    }
    return ""
}

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

proc chat::add_invite_menu_item {m connid jid} {
    $m add command -label [::msgcat::mc "Invite to conference..."] \
	   -command [list chat::invite_dialog $jid 0 -connection $connid]
}

hook::add chat_create_user_menu_hook \
    [namespace current]::chat::add_invite_menu_item 20
hook::add roster_create_groupchat_user_menu_hook \
    [namespace current]::chat::add_invite_menu_item 20
hook::add roster_jid_popup_menu_hook \
    [namespace current]::chat::add_invite_menu_item 20
hook::add message_dialog_menu_hook \
    [namespace current]::chat::add_invite_menu_item 20
hook::add search_popup_menu_hook \
    [namespace current]::chat::add_invite_menu_item 20

proc chat::add_invite2_menu_item {m connid jid} {
    $m add command -label [::msgcat::mc "Invite users..."] \
	   -command [list chat::invite_dialog2 $jid 0 -connection $connid]
}

hook::add chat_create_conference_menu_hook \
    [namespace current]::chat::add_invite2_menu_item 20

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

proc chat::invite_dialog {user {ignore_muc 0} args} {
    variable chats
    global invite_gc

    foreach {opt val} $args {
	switch -- $opt {
	    -connection { set connid $val }
	}
    }
    if {![info exists connid]} {
	set connid [jlib::route $user]
    }

    set jid [get_jid_of_user $connid $user]

    if {[cequal $jid ""]} {
        set jid $user
    }

    set gw .invite
    catch { destroy $gw }

    if {[catch { set nick [roster::get_label $user] }]} {
	if {[catch {set nick [roster::get_label \
				  [node_and_server_from_jid $user]] }]} {
	    if {[catch { set nick [chat::get_nick $connid \
						  $user groupchat] }]} {
		set nick $user
	    }
	}
    }

    set titles {}
    set jids {}
    foreach chatid [lsort [lfilter [namespace current]::is_groupchat [opened $connid]]] {
	lappend jids $chatid [get_jid $chatid]
        lappend titles $chatid [node_from_jid [get_jid $chatid]]
    }
    if {[llength $titles] == 0} {
        MessageDlg ${gw}_err -aspect 50000 -icon info \
	    -message \
		[format [::msgcat::mc "No conferences for %s in progress..."] \
		    [jlib::connection_jid $connid]] \
	    -type user \
	    -buttons ok -default 0 -cancel 0
        return
    }

    CbDialog $gw [format [::msgcat::mc "Invite %s to conferences"] $nick] \
	[list [::msgcat::mc "Invite"] "chat::invitation [list $jid] 0 $ignore_muc
				       destroy $gw" \
	      [::msgcat::mc "Cancel"] "destroy $gw"] \
	invite_gc $titles $jids
}

proc chat::invite_dialog2 {jid ignore_muc args} {
    variable chats
    global invite_gc

    foreach {opt val} $args {
	switch -- $opt {
	    -connection { set connid $val }
	}
    }
    if {![info exists connid]} {
	set connid [jlib::route $jid]
    }

    set gw .invite
    catch { destroy $gw }

    set title [node_from_jid $jid]

    set choices {}
    set balloons {}
    foreach choice [roster::get_jids $connid] {
	if {![cequal [roster::itemconfig $connid $choice -category] conference]} {
	    lappend choices [list $connid $choice] [roster::get_label $connid $choice]
	    lappend balloons [list $connid $choice] $choice
	} 
    } 
    if {[llength $choices] == 0} {
        MessageDlg ${gw}_err -aspect 50000 -icon info \
	    -message \
		[format [::msgcat::mc "No users in %s roster..."] \
		     [jlib::connection_jid $connid]] \
	    -type user \
	    -buttons ok -default 0 -cancel 0
	return
    }

    CbDialog $gw [format [::msgcat::mc "Invite users to %s"] $title] \
	[list [::msgcat::mc "Invite"] "chat::invitation [list $jid] 1 $ignore_muc
				       destroy $gw" \
	      [::msgcat::mc "Cancel"] "destroy $gw"] \
	invite_gc $choices $balloons
}

proc chat::invitation {jid usersP ignore_muc {reason ""}} {
    global invite_gc

    foreach choice [array names invite_gc] {
        if {$invite_gc($choice)} {
	    lassign $choice con gc
	    if {$usersP} {
		set to $gc
		set group $jid
	    } else {            
		set to $jid
		set group $gc
	    }
	    if {[cequal $reason ""]} {
		set reas [::msgcat::mc "Please join %s" $group]
	    } else {
		set reas $reason
	    }
	    if {!$ignore_muc && [muc::is_compatible $group]} {
		muc::invite_muc $con $group $to $reas
	    } else {
		muc::invite_xconference $con $group $to $reas
	    }
	}
    }
}

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

proc chat::restore_window {cjid type nick connid jid} {
    set chatid [chatid $connid $cjid]

    if {$type == "groupchat"} {
	set_our_groupchat_nick $chatid $nick
    }

    # TODO: Password?
    open_window $chatid $type
    set cw [winid $chatid]
    raise_win $cw
}

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

proc chat::save_session {vsession} {
    upvar 2 $vsession session
    global usetabbar
    variable chats
    variable chat_id

    # TODO
    if {!$usetabbar} return

    set prio 0
    foreach page [.nb pages] {
	set path [ifacetk::nbpath $page]

	if {[info exists chat_id($path)]} {
	    set chatid $chat_id($path)

	    set connid [get_connid $chatid]
	    set jid [get_jid $chatid]
	    set type $chats(type,$chatid)

	    if {$type == "groupchat"} {
		set nick [get_our_groupchat_nick $chatid]
	    } else {
		set nick ""
	    }

	    set user [jlib::connection_requested_user $connid]
	    set server [jlib::connection_requested_server $connid]
	    set resource [jlib::connection_requested_resource $connid]

	    lappend session [list $prio $user $server $resource \
		[list [namespace current]::restore_window $jid $type $nick] \
	    ]
	}
	incr prio
    }
}

hook::add save_session_hook [namespace current]::chat::save_session

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

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