# Console Jabber Client
# Copyright (C) 2004-2009  Jacek Konieczny
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as published
# by the Free Software Foundation
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

import string
import curses
import os

import pyxmpp
from pyxmpp.jabber import delay
from cjc import ui
from cjc.plugin import PluginBase
from cjc import common
from cjc import cjc_globals

theme_attrs=(
    ("chat.me", curses.COLOR_YELLOW,curses.COLOR_BLACK,curses.A_BOLD, curses.A_UNDERLINE),
    ("chat.peer", curses.COLOR_WHITE,curses.COLOR_BLACK,curses.A_NORMAL, curses.A_NORMAL),
    ("chat.info", curses.COLOR_GREEN,curses.COLOR_BLACK,curses.A_NORMAL, curses.A_NORMAL),
)

theme_formats=(
        ("chat.started",u"[%(T:timestamp)s] %[chat.info]* Chat with %(J:peer:nick)s (%(J:peer:jid)s) started\n"),
    ("chat.me",u"[%(T:timestamp)s] %[chat.me]<%(J:me:nick)s>%[] %(msg)s\n"),
    ("chat.peer",u"[%(T:timestamp)s] %[chat.peer]<%(J:peer:nick)s>%[] %(msg)s\n"),
    ("chat.action",u"[%(T:timestamp)s] %[chat.info]* %(J:jid:nick)s %(msg)s\n"),
    ("chat.descr",u"Chat with %(J:peer:full)s [%(J:peer:show)s] %(J:peer:status)s"),
    ("chat.day_change",u"%{@day_change}"),
)

class Conversation:
    def __init__(self,plugin,me,peer,thread=None):
        self.plugin=plugin
        self.me=me
        self.peer=peer
        if thread:
            self.thread=thread
            self.thread_inuse=1
        else:
            plugin.last_thread+=1
            self.thread="chat-thread-%i" % (plugin.last_thread,)
            self.thread_inuse=0
        self.fparams={
            "peer":self.peer,
            "jid":self.me,
        }
        self.buffer=ui.TextBuffer(self.fparams,"chat.descr",
                "chat buffer",self)
        self.buffer.preference=plugin.settings["buffer_preference"]
        self.buffer.user_input=self.user_input
        self.buffer.append_themed("chat.started",self.fparams)
        self.buffer.update()

    def change_peer(self,peer):
        self.peer=peer
        self.fparams["peer"]=peer
        self.buffer.update_info(self.fparams)

    def add_msg(self,s,format,who,timestamp=None):
        fparams=dict(self.fparams)
        fparams["jid"]=who
        if timestamp:
            fparams["timestamp"]=timestamp
        if s.startswith(u"/me "):
            fparams["msg"]=s[4:]
            self.buffer.append_themed("chat.action",fparams)
            self.buffer.update()
            return
        fparams["msg"]=s
        self.buffer.append_themed(format,fparams)
        self.buffer.update()

    def add_sent(self,s):
        self.add_msg(s,"chat.me",self.me)

    def add_received(self,s,timestamp):
        self.add_msg(s,"chat.peer",self.peer,timestamp)

    def user_input(self,s):
        if not self.plugin.cjc.stream:
            self.buffer.append_themed("error","Not connected")
            self.buffer.update()
            return 0
        if self.plugin.settings.get("log_filename"):
            self.plugin.log_message("out",self.me,self.peer,None,s,self.thread)
        m=pyxmpp.Message(to_jid=self.peer,stanza_type="chat",body=s,thread=self.thread)
        self.plugin.cjc.stream.send(m)
        self.add_sent(s)
        return 1

    def error(self,stanza):
        err=stanza.get_error()
        emsg=err.get_message()
        msg="Error"
        if emsg:
            msg+=": %s" % emsg
        etxt=err.get_text()
        if etxt:
            msg+=" ('%s')" % etxt
        self.buffer.append_themed("error",msg)
        self.buffer.update()

    def cmd_me(self,args):
        if not args:
            return 1
        args=args.all()
        if not args:
            return 1
        self.user_input(u"/me "+args)
        return 1

    def cmd_close(self,args):
        args.finish()
        key=self.peer.bare().as_unicode()
        if self.plugin.conversations.has_key(key):
            l=self.plugin.conversations[key]
            if self in l:
                l.remove(self)
        self.buffer.close()
        return 1

    def cmd_whois(self,args):
        self.buffer.deactivate_command_table()
        try:
            if not args.get():
                args=ui.CommandArgs(self.peer.as_unicode())
            ui.run_command("whois",args)
        finally:
            self.buffer.activate_command_table()

    def cmd_info(self,args):
        self.buffer.deactivate_command_table()
        try:
            if not args.get():
                args=ui.CommandArgs(self.peer.as_unicode())
            ui.run_command("info",args)
        finally:
            self.buffer.activate_command_table()


ui.CommandTable("chat buffer",50,(
    ui.Command("me",Conversation.cmd_me,
        "/me text",
        "Sends /me text",
        ("text",)),
    ui.Command("close",Conversation.cmd_close,
        "/close",
        "Closes current chat buffer"),
    ui.Command("whois",Conversation.cmd_whois,
        "/whois [options] [user]",
        None),
    ui.Command("info",Conversation.cmd_info,
        "/info [options] [user]",
        None),
    )).install()

class Plugin(PluginBase):
    def __init__(self,app,name):
        PluginBase.__init__(self,app,name)
        self.conversations={}
        self.last_thread=0
        cjc_globals.theme_manager.set_default_attrs(theme_attrs)
        cjc_globals.theme_manager.set_default_formats(theme_formats)
        self.available_settings={
            "log_filename": ("Where messages should be logged to",(str,None)),
            "log_format_in": ("Format of incoming message log entries",(str,None)),
            "log_format_out": ("Format of outgoing message log entries",(str,None)),
            "buffer_preference": ("Preference of chat buffers when switching to the next active buffer. If 0 then the buffer is not even shown in active buffer list.",int),
            "auto_popup": ("When enabled each new chat buffer is automatically made active.",bool),
            }
        self.settings={
                "log_filename": "%($HOME)s/.cjc/logs/chats/%(J:peer:bare)s",
                "log_format_in": "[%(T:now:%c)s] <%(J:sender:nick)s> %(body)s\n",
                "log_format_out": "[%(T:now:%c)s] <%(J:sender:nick)s> %(body)s\n",
                "buffer_preference": 100,
                "auto_popup": False,
                }
        app.add_event_handler("presence changed",self.ev_presence_changed)
        app.add_event_handler("day changed",self.ev_day_changed)
        ui.activate_cmdtable("chat",self)

    def cmd_chat(self,args):
        peer=args.shift()
        if not peer:
            self.error("/chat without arguments")
            return

        if not self.cjc.stream:
            self.error("Connect first!")
            return

        peer=self.cjc.get_best_user(peer)
        if peer is None:
            return

        conversation=Conversation(self,self.cjc.jid,peer)
        key=peer.bare().as_unicode()
        if self.conversations.has_key(key):
            self.conversations[key].append(conversation)
        else:
            self.conversations[key]=[conversation]

        text=args.all()
        if text:
            conversation.user_input(text)

        cjc_globals.screen.display_buffer(conversation.buffer)

    def ev_presence_changed(self,event,arg):
        key=arg.bare().as_unicode()
        if not self.conversations.has_key(key):
            return
        for conv in self.conversations[key]:
            if conv.peer==arg or conv.peer==arg.bare():
                conv.buffer.update_info(conv.fparams)

    def ev_day_changed(self,event,arg):
        for convs in self.conversations.values():
            for conv in convs:
                conv.buffer.append_themed("chat.day_change",{},activity_level=0)
                conv.buffer.update()

    def session_started(self,stream):
        self.cjc.stream.set_message_handler("chat",self.message_chat)
        self.cjc.stream.set_message_handler("error",self.message_error,None,90)

    def message_error(self,stanza):
        fr=stanza.get_from()
        thread=stanza.get_thread()
        key=fr.bare().as_unicode()

        conv=None
        if self.conversations.has_key(key):
            convs=self.conversations[key]
            for c in convs:
                if not thread and (not c.thread or not c.thread_inuse):
                    conv=c
                    break
                if thread and thread==c.thread:
                    conv=c
                    break
            if conv and conv.thread and not thread:
                conv.thread=None
            elif conv and thread:
                conv.thread_inuse=1

        if not conv:
            return 0

        conv.error(stanza)
        return 1

    def message_chat(self,stanza):
        fr=stanza.get_from()
        thread=stanza.get_thread()
        subject=stanza.get_subject()
        body=stanza.get_body()
        if body is None:
            body=u""
        if subject:
            body=u"%s: %s" % (subject,body)
        elif not body:
            return

        d=delay.get_delay(stanza)
        if d:
            timestamp=d.get_datetime_local()
        else:
            timestamp=None
        if self.settings.get("log_filename"):
            self.log_message("in",fr,self.cjc.jid,subject,body,thread,timestamp)

        key=fr.bare().as_unicode()
        conv=None
        if self.conversations.has_key(key):
            convs=self.conversations[key]
            for c in convs:
                if not thread and (not c.thread or not c.thread_inuse):
                    conv=c
                    break
                if thread and thread==c.thread:
                    conv=c
                    break
            if conv and conv.thread and not thread:
                conv.thread=None
            elif conv and thread:
                conv.thread_inuse=1

        if not conv:
            conv=Conversation(self,self.cjc.jid,fr,thread)
            if self.conversations.has_key(key):
                self.conversations[key].append(conv)
            else:
                self.conversations[key]=[conv]
            if self.settings.get("auto_popup"):
                cjc_globals.screen.display_buffer(conv.buffer)
            else:
                conv.buffer.update()
        else:
            if fr!=conv.peer:
                conv.change_peer(fr)

        self.cjc.send_event("chat message received",body)
        conv.add_received(body,timestamp)
        return 1

    def log_message(self,dir,sender,recipient,subject,body,thread,timestamp=None):
        format=self.settings["log_format_"+dir]
        filename=self.settings["log_filename"]
        d={
            "sender": sender,
            "recipient": recipient,
            "subject": subject,
            "body": body,
            "thread": thread
            }
        if timestamp:
            d["timestamp"]=timestamp
        if dir=="in":
            d["peer"]=sender
        else:
            d["peer"]=recipient
        filename=cjc_globals.theme_manager.substitute(filename,d)
        s=cjc_globals.theme_manager.substitute(format,d)
        try:
            dirname=os.path.split(filename)[0]
            if dirname and not os.path.exists(dirname):
                os.makedirs(dirname)
            f=open(filename,"a")
            try:
                f.write(s.encode("utf-8","replace"))
            finally:
                f.close()
        except (IOError,OSError),e:
            self.error("Couldn't write chat log: "+str(e))

ui.CommandTable("chat",51,(
    ui.Command("chat",Plugin.cmd_chat,
        "/chat nick|jid [text]",
        "Start chat with given user",
        ("user","text")),
    )).install()
# vi: sts=4 et sw=4
