# spy.tcl --
#
#       Tkabber presence spy module, version 1.0.
#       Traces presence messages, logging them into tkabber window
#       and/or file.
#
# Copyright (C) 2003 Maciek Pasternacki <maciekp@japhy.fnord.org>
# Modifications: Sergei Golovan <sgolovan@nes.ru>

package require msgcat

namespace eval spy {
    ::msgcat::mcload [file join [file dirname [info script]] msgs]

    if {![::plugins::is_registered spy]} {
        ::plugins::register spy \
                            -namespace [namespace current] \
                            -source [info script] \
                            -description [::msgcat::mc "Whether the Spy\
                                                        Presence plugin is\
                                                        loaded."] \
                            -loadcommand [namespace code load] \
                            -unloadcommand [namespace code unload]
        return
    }

    variable options

    custom::defgroup Plugins [::msgcat::mc "Plugins options."] \
        -group Tkabber
    custom::defgroup Spy [::msgcat::mc "Spy Presence plugin options."] \
        -group Plugins

    custom::defvar options(log_file) "" \
        [::msgcat::mc "File to log Spy Presence messages."] \
        -type string -group Spy

    custom::defvar options(timestamp_format) {[%m/%d %R]} \
        [::msgcat::mc "Format of timestamp in Spy Presence window."] \
        -type string -group Spy

    custom::defvar options(plugin_spy_sound) \
        [fullpath sounds default chat_their_message.wav] \
        [::msgcat::mc "Play this sound file"] \
        -command [list ::sound::load_sound_file plugin_spy] \
        -type file -group Spy
}

proc spy::load {} {
    variable the_file
    variable watches

    set the_file ""
    set watches {}

    hook::add postload_hook [namespace current]::init_spy 100
    hook::add client_presence_hook \
              [namespace current]::client_presence_handler 100
    hook::add quit_hook [namespace current]::deinit_spy 100
    hook::add finload_hook [namespace current]::setup_menu
    hook::add save_session_hook [namespace current]::save_session

    init_spy
    setup_menu
}

proc spy::unload {} {
    variable the_file
    variable watches

    desetup_menu

    if {[winfo exists .spy]} {
        destroy_win .spy
    }
    deinit_spy

    hook::remove postload_hook [namespace current]::init_spy 100
    hook::remove client_presence_hook \
                 [namespace current]::client_presence_handler 100
    hook::remove quit_hook [namespace current]::deinit_spy 100
    hook::remove finload_hook [namespace current]::setup_menu
    hook::remove save_session_hook [namespace current]::save_session

    set the_file ""
    set watches {}

    namespace delete [namespace current]::search
}

option add *Spy.timestampforeground Black         widgetDefault
option add *Spy.nickforeground      DarkBlue      widgetDefault
option add *Spy.jidforeground       DarkBlue      widgetDefault
option add *Spy.presenceforeground  DarkRed       widgetDefault
option add *Spy.reasonforeground    DarkMagenta   widgetDefault

proc spy::open_window {} {
    variable watches

    set w .spy
    if {[winfo exists $w]} {
        raise_win $w
        return
    }

    add_win $w -title [::msgcat::mc "Presence Spy"] \
               -tabtitle [::msgcat::mc "Spy"] \
               -class Spy \
               -raisecmd [list [namespace current]::focus_log] \
               -raise 1

    Frame $w.controls
    Button $w.controls.add -text [::msgcat::mc "Set watch"] \
      -command "[namespace current]::set_watch"

    set watch_entry [Entry $w.controls.watch_regex]
    bind $watch_entry <Return> "[namespace current]::set_watch"
    pack $w.controls.add -side left -anchor e
    pack $watch_entry -side left -anchor w -fill x
    pack $w.controls -side bottom -fill x

    Frame $w.watches
    foreach watch $watches {
        set watch_id [lindex $watch 0]
        set watch_regex [lindex $watch 1]
        add_watch_frame $watch_id $watch_regex
    }
    pack $w.watches -side bottom -fill x

    set sw [ScrolledWindow $w.isw -scrollbar vertical]
    pack $sw -side top -fill both -expand yes -in $w -pady 1m

    set log [Text $w.log -wrap word -state disabled -takefocus 1]
    $sw setwidget $log

    bind [Wrapped $log] <1> [list [namespace current]::focus_log]

    $log tag configure timestamp \
        -foreground [option get $w timestampforeground Spy]
    $log tag configure nick \
        -foreground [option get $w nickforeground Spy]
    $log tag configure jid \
        -foreground [option get $w jidforeground Spy]
    $log tag configure presence \
        -foreground [option get $w presenceforeground Spy]
    $log tag configure reason \
        -foreground [option get $w reasonforeground Spy]

    search::setup_panel $w
}

proc spy::focus_log {} {
    set w .spy

    focus [Wrapped $w.log]
}

proc spy::set_watch {} {
    variable watches
    set w .spy

    if {![winfo exists $w]} return

    set entr $w.controls.watch_regex
    set regex [$entr get]

    if {$regex eq ""} return

    # Add new watch if there is no such already present
    foreach watch $watches {
        set r [lindex $watch 1]
        set idx [lindex $watch 0]
        if {$r == $regex} {
            #set b [$w.watches.$idx cget -background]
            #$w.watches.$idx configure -background red
            #after 1500 [list $w.watches.$idx configure -background $b]
            return
        }
    }
    if {[catch {regexp $regex ""}]} {
        if {[catch {
                $entr configure \
                      -foreground [option get $entr errorForeground Entry]
             }]} {
             # The invalid.TEntry style is defined in
             # tkabber/plugins/search/search.tcl
             $entr configure -style invalid.TEntry
        }
        return
    } else {
        if {[catch {
                $entr configure \
                      -foreground [option get $entr foreground Entry]
             }]} {
             $entr configure -style TEntry
        }
    }
    set next_watch 0
    while {[winfo exists $w.watches.$next_watch]} {
        incr next_watch
    }
    lappend watches [list $next_watch $regex]
    add_watch_frame $next_watch $regex
}

proc spy::add_watch_frame {watch_id watch_regex} {
    variable spy_alerts
    set w .spy

    if {![winfo exists $w]} return

    Frame $w.watches.$watch_id
    Button $w.watches.$watch_id.remove -text [::msgcat::mc "Remove"] \
      -command [list [namespace current]::remove_watch $watch_id $watch_regex]
    Label $w.watches.$watch_id.regex -text $watch_regex \
      -foreground [option get $w nickforeground Spy]
    Label $w.watches.$watch_id.nick -text "" \
      -foreground [option get $w nickforeground Spy]
    Label $w.watches.$watch_id.jid -text "" \
      -foreground [option get $w nickforeground Spy]
    Label $w.watches.$watch_id.timestamp -text "" \
      -foreground [option get $w timestampforeground Spy]
    Label $w.watches.$watch_id.presence -text "" \
      -foreground [option get $w presenceforeground Spy]
    Label $w.watches.$watch_id.reason -text "" \
          -foreground [option get $w reasonforeground Spy]
    Checkbutton $w.watches.$watch_id.alert \
                -text [::msgcat::mc "Alert when available"] \
                -variable [namespace current]::spy_alerts($watch_id)

    set [namespace current]::spy_alerts($watch_id) 0

    Checkbutton $w.watches.$watch_id.sound \
                -text [::msgcat::mc "Sound when available"] \
                -variable [namespace current]::spy_alerts($watch_id.sound)

    set [namespace current]::spy_alerts($watch_id.sound) 0

    pack $w.watches.$watch_id.remove \
         $w.watches.$watch_id.regex \
         $w.watches.$watch_id.timestamp \
         $w.watches.$watch_id.nick \
         $w.watches.$watch_id.jid \
         $w.watches.$watch_id.presence -side left
    pack $w.watches.$watch_id.alert -side right
    pack $w.watches.$watch_id.sound -side right
    pack $w.watches.$watch_id.reason -side left -fill x
    pack $w.watches.$watch_id -side bottom -fill x

}

proc spy::remove_watch {watch_id watch_regex} {
    variable watches

    set w .spy.watches.$watch_id
    if {![winfo exists $w]} return

    set idx [lsearch -exact $watches [list $watch_id $watch_regex]]
    set watches [lreplace $watches $idx $idx]
    destroy $w
}

proc spy::update_watch {xlib watch_id nick jid type reason} {
    variable options
    variable spy_alerts
    set w .spy.watches.$watch_id
    if {![winfo exists $w]} return

    $w.timestamp configure -text \
        [clock format [clock seconds] -format $options(timestamp_format)]
    $w.nick configure -text $nick
    $w.jid configure -text $jid
    $w.presence configure -text $type
    $w.reason configure -text $reason

    if {$spy_alerts($watch_id) && $type=="available"} {
        alert_dialog $xlib $watch_id $nick $jid
    }
    if {$spy_alerts($watch_id.sound) && $type=="available" } {
        ::sound::play $options(plugin_spy_sound)
    }
}

proc spy::alert_dialog {xlib watch_id nick jid} {
    set w .spy_alert_${watch_id}

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

    set message [::msgcat::mc "Spy Alert: user %s (%s) is available!" \
                              $nick $jid]
    Dialog $w -title $message -modal none -anchor e -default 0 -cancel 1

    set frame [$w getframe]
    Message $frame.msg -text $message -aspect 50000
    pack $frame.msg -side left -fill x

    $w add -text [::msgcat::mc "Open chat"] \
           -command [list [namespace current]::open_chat $w $xlib $jid ]
    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]

    $w draw
}

proc spy::open_chat {w xlib jid} {
    destroy $w

    chat::open_to_user $xlib $jid
}

proc spy::display {xlib jid nick type reason} {
    variable options
    variable watches
    set w .spy

    if {![winfo exists $w]} return
    set log $w.log

    $log configure -state normal

    $log insert end \
         [clock format [clock seconds] -format $options(timestamp_format)] \
         timestamp " " {}

    if {$nick ne ""} {
        $log insert end $nick nick " (" {} $jid jid ") " {}
    } else {
        $log insert end $jid jid " " {}
    }

    $log insert end "$type" presence

    if {$reason ne ""} {
        $log insert end " (" {} $reason reason ")" {}
    }
    $log insert end "\n"
    $log see end

    $log configure -state disabled

    foreach watch $watches {
        set watch_id [lindex $watch 0]
        set watch_regex [lindex $watch 1]
        if {[regexp $watch_regex $nick] || [regexp $watch_regex $jid]} {
            update_watch $xlib $watch_id $nick $jid $type $reason
        }
    }
}

proc spy::client_presence_handler {xlib from type x args} {
    variable options
    variable the_file

    if {[catch { chat::get_nick $xlib $from chat } nick]} {
        if {[catch { chat::get_nick $from chat } nick]} {
            set nick $from
        }
    }

    if {$nick eq $from} {
        set nick ""
    }

    set reason ""
    set show ""
    foreach {attr val} $args {
        switch -- $attr {
            -status {set reason $val}
            -show   {set show   $val}
        }
    }

    if {$type eq ""} {
        set type available
    }
    if {$show ne ""} {
        set type $type/$show
    }

    display $xlib $from $nick $type $reason

    if {$options(log_file) ne "" && $the_file eq ""} {
        init_spy
    }

    if {$options(log_file) eq "" && $the_file ne ""} {
        deinit_spy
    }

    if {$the_file ne ""} {
        puts -nonewline $the_file "[clock format [clock seconds]] "
        if {$nick ne ""} {
            puts -nonewline $the_file "$nick ($from) "
        } else {
            puts -nonewline $the_file "$from "
        }
        puts -nonewline $the_file "$type"
        if {$reason ne ""} {
            puts -nonewline $the_file " ($reason)"
        }
        puts $the_file ""
        flush $the_file
    }
}

proc spy::init_spy {} {
    variable the_file
    variable options

    if {$options(log_file) ne ""} {
        if {$the_file ne ""} {
            catch {close $the_file}
            set the_file ""
        }
        set the_file [open $options(log_file) a]
        puts $the_file "[clock format [clock seconds]] Started spying."
    }
}

proc spy::deinit_spy {} {
    variable the_file
    if {$the_file ne ""} {
        puts $the_file "[clock format [clock seconds]] Spy goes away."
        catch {close $the_file}
        set the_file ""
    }
}

proc spy::setup_menu {} {
    catch {
        set m [.mainframe getmenu plugins]

        $m add command -label [::msgcat::mc "Spy presence"] \
                       -command [namespace current]::open_window
    }
}

proc spy::desetup_menu {} {
    catch {
        set m [.mainframe getmenu plugins]
        set idx [$m index [::msgcat::mc "Spy presence"]]

        $m delete $idx
    }
}

namespace eval spy::search {}

proc spy::search::open_panel {w sf} {
    pack $sf -side bottom -anchor w -fill x -before $w.isw
    update idletasks
    $w.log see end
}

proc spy::search::close_panel {w sf} {
    $w.log tag remove search_highlight 0.0 end
    pack forget $sf
    [namespace parent]::focus_log
}

proc spy::search::setup_panel {w} {
    set log $w.log

    $log mark set sel_start end
    $log mark set sel_end 0.0

    set sf [plugins::search::spanel [winfo parent $log].search \
                -searchcommand [list ::plugins::search::do_text_search $log] \
                -closecommand [list [namespace current]::close_panel $w]]

    bind $w.log <<OpenSearchPanel>> \
        [double% [list [namespace current]::open_panel $w $sf]]
}

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

proc spy::restore_window {args} {
    open_window
}

proc spy::save_session {vsession} {
    upvar 2 $vsession session
    global usetabbar

    # We don't need JID at all, so make it empty (special case)
    set user     ""
    set server   ""
    set resource ""

    # TODO
    if {!$usetabbar} return

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

        if {[string equal $path .spy]} {
            lappend session [list $prio $user $server $resource \
                [list [namespace current]::restore_window] \
            ]
        }
        incr prio
    }
}

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