#!/usr/bin/env python
# Time-stamp: <2002-03-20 19:32:15 crabbkw>
# Code and design by Casey Crabb (crabbkw@rose-hulman.edu)
# This code is licensed under the BSD license.
# See the LICENSE file for details
#
# Copyright Casey Crabb (crabbkw@rose-hulman.edu) July 2001
#

# from __future__ import nested_scopes
from threading import *
from IMCom import IMCom, errorCodes
from Preferences import Preferences
from LogHandler import LogHandler
from AutoStatus import AutoStatus
from CICommands import *
from Colors import *
import string
import socket
import os
import time
import sys
import getpass
import operator
import signal
import traceback
import SocketWrapper
import pprint

try:
    import codecs
    CODECS = 1
except:
    CODECS = 0

try:
    import locale
    LOCALE = 1
except:
    LOCALE = 0

try:
    import readline
    READLINE = 1
except:
    READLINE = 0


terminalWidth = 80
pp = pprint.PrettyPrinter(indent=4)

def mySort(x, y):
    a,b = x
    c,d = y
    return cmp(b,d)

def getTerminalWidth():
    terminalWidth = 80 #linelength

class CLI(Thread):
    "The Main thread and heart of the IMCom CLI interface."
    def __init__(self):
        Thread.__init__(self)
        self.threadHash = {}
        self.prompt = "IMCom> "
        self.lastMessaged = None
        self.lastReceived = None
        self.lastToDict = {}
        self.lastFromDict = {}
        self.showPresenceUpdates = 1
        self.ringBell = 1
        self.prefs = Preferences()
        self.profile = self.prefs.getDefaultProfile()
        self.imcom = IMCom(self)
        self.registerCallbacks()
        self.loghandler = LogHandler( self, self.profile )
        self.currentStatus = "online"
        self.currentStatusType = "auto"
        self.currentStatusReason = ""
        self.debug = 0
        self.debugdetails = {
            "autostatus" : 0,
            }
        self.mode = 0
        self.MULTILINE = 1
        self.outputQueue = []
        self.gettingCommand = 0
        self.userQueue = []
        self.commandQueue = []
        self.socketlisteners = []
        self.socketserver = ''

        if(READLINE):
            readline.parse_and_bind("tab: complete")
            readline.parse_and_bind("set bell-style none")
            readline.set_completer_delims(" ")
            readline.set_completer(tabCompleter)


    def registerCallbacks(self):
        self.imcom.cbHandleConferenceMessage  = self.handleConferenceMessage
        self.imcom.cbHandleDisconnected       = self.handleDisconnected
        self.imcom.cbHandleAdminWho           = self.handleAdminWho
        self.imcom.cbHandlePresenceUpdate     = self.handlePresenceUpdate
        self.imcom.cbHandleConferenceMessage  = self.handleConferenceMessage
        self.imcom.cbHandleConferencePresence = self.handleConferencePresence
        self.imcom.cbHandleMessageReceive     = self.handleMessageReceive
        self.imcom.cbHandleMessageError       = self.handleMessageError
        self.imcom.cbHandleIQError            = self.handleIQError
        self.imcom.cbHandleInfoError          = self.handleInfoError
        self.imcom.cbHandleFileReceive        = self.handleFileReceive
        self.imcom.cbHandleFileReceived       = self.handleFileReceived
        self.imcom.cbHandleFileErrorReceived  = self.handleFileErrorReceived
        self.imcom.cbHandleAgentList          = self.handleAgentList
        self.imcom.cbHandleAgentRegister      = self.handleAgentRegister
        self.imcom.cbHandleAgentRegistered    = self.handleAgentRegistered
        self.imcom.cbHandleSubscribe          = self.handleSubscribe
        self.imcom.cbHandleSubscribed         = self.handleSubscribed
        self.imcom.cbHandleUnsubscribed       = self.handleUnsubscribed
        self.imcom.cbHandleUnsubscribe        = self.handleUnsubscribe
        self.imcom.cbHandleRosterUpdateCheck  = self.handleRosterUpdateCheck
        self.imcom.cbHandleRosterUpdate       = self.handleRosterUpdate
        self.imcom.cbHandleVCardSubmit        = self.handleVCardSubmit
        self.imcom.cbHandleVCard              = self.handleVCard
        self.imcom.cbHandleNoVCard            = self.handleNoVCard
        self.imcom.cbHandleLogin              = self.login
        self.imcom.cbHandleGrabRoster         = self.grabRoster

    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # -------------------------------------------------------------------------
    #  Functions for the login procedure
    # -------------------------------------------------------------------------
    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    def grabRoster(self):
        """This function is called by the IMCom library when the roster
        has been downloaded. Because the presence events for people
        can come before the roster is downloaded this function needs
        to print out people for whom we've already gotten presence
        updates from.  This is because the presence code doesn't show
        us presence information for anyone who is not on our roster."""
        self.output("Roster has been downloaded")
        keys = self.imcom.jidhash.keys()
        for item in keys:
            if(self.imcom.preshash.has_key(item)):
                available, status, show = self.imcom.preshash[item]
                if(not available):
                    continue
                if(self.showPresenceUpdates):
                    if( self.imcom.gjidhash.has_key(item) and
                        (operator.contains(self.imcom.gjidhash[item],
                                           "lurker") or
                         operator.contains(self.imcom.gjidhash[item],
                                           "ignore"))):
                        return
                    a,show,status = self.imcom.preshash[item]
                    nick = self.getNick(item)
                    ttime = self.getTime()
                    ddate = self.getDate()
                    self.output(usercolor + nick + defaultcolor +
                                " changed status to " +
                                statuscolor + show + defaultcolor + " (" +
                                desccolor + status + defaultcolor + ") at " +
                                timecolor + ttime + defaultcolor)
            else:
                self.imcom.preshash[item] = (0, "unavailable", "offline")

    def login(self, successful):
        """This function is called by the IMCom library. It lets us
        know whether the login was successful or not."""
        if(successful):
            self.output(usercolor + "Logged on" + defaultcolor)
        else:
            self.output(errorcolor + "Login FAILED!" + defaultcolor)

    def applyProfile(self):
        """This function actually begins the login process by
        selecting a profile and calling the IMCom library's
        changeProfile command.  If no profile is available we allow
        the user to create one. This does not let the user create an
        account, only lets them to create a profile for an account
        pre-existing."""
        if(self.profile!=None):
            p = self.profile
            if(p.switches['ssl'][0] == "false" or
               p.switches['ssl'][0] == "no" or
               p.switches['ssl'][0] == "off" or
               p.switches['ssl'][0] == 0):
                self.imcom.changeProfile(p.server,p.port,p.user,
                                         p.password,p.resource,0,p.priority,
                                         p.encoding)
            else:
                self.imcom.changeProfile(p.server,p.port,p.user,
                                         p.password,p.resource,1,p.priority,
                                         p.encoding)
            self.loghandler.initLogHandler(p)
            self.autostatus = AutoStatus( self, self.profile )
            self.autostatus.start()
            self.loghandler.setProfile( self.profile )
            self.autostatus.setProfile( self.profile )
            return 1
        else:
            # I should create a new profile here.
            print("No profiles available or default profile not set.")
            self.createNewProfile()
            self.profile = self.prefs.getDefaultProfile()
            return 0

    def applyPrefs(self):
        """This function sets all the preference switches."""
        if(self.profile == None):
            return
        p = self.profile
        if(p.switches['ringbell'][0] == "true"):
            self.ringBell = 1
        else:
            self.ringBell = 0

        if( p.switches['colors'][0] == "true"):
            self.setColors()
        else:
            self.setNoColors()

        if( p.switches['debug'][0] == "false" ):
            self.debug = 0
            self.imcom.setDebug(0)
        else:
            self.debug = 1
            self.imcom.setDebug(1)

        if( p.switches['nickprompt'][0] == "false"):
            self.useNickAsPrompt = 0
        else:
            self.useNickAsPrompt = 1

        if( p.switches['allowinterrupt'][0] == "false" ):
            self.allowInterrupt = 0
        else:
            self.allowInterrupt = 1

        if( p.switches['statusshow'][0] == "false" or
        p.switches['statusshow'][0] == "no"):
            self.showPresenceUpdates = 0
        else:
            self.showPresenceUpdates = 1

    def createNewProfile(self):
        """This function creates a new profile and overwrites the
        preferences file ~/.imcom/imcomrc"""
        print("You don't seem to have a profile, lets make a new one")
        name = raw_input("Enter a profile name > ")
        server = raw_input("Enter the server name > ")
        port = raw_input("Enter the port (5222) > ")
        if(port == None or len(port) == 0):
            port = "5222"
        user = raw_input("Enter your login > " )
        password = getpass.getpass("Enter your password > ")
        resource = raw_input("Enter the resource (imcom) > ")
        if(resource == None or len(resource) == 0):
            resource = "imcom"
        encoding = raw_input("Enter the encoding you want to use (iso-8859-1) > ")
        if(encoding == None or len(encoding) == 0):
            encoding = "iso-8859-1"
        self.prefs.createInitialProfile(name,server,port,
                                        user,password,resource, encoding)


    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # -------------------------------------------------------------------------
    #  The functions which begin with handle are callbacks from the
    #  IMCom library
    # -------------------------------------------------------------------------
    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    def handleDisconnected(self):
        """This is called by the IMCom library when the server has
        disconnected us unexpectantly. In theory it should go into
        EVAL mode essentially dumped a developer onto a Python console
        with the state of IMCom preserved. This however, does not work
        well yet."""
        self.output(errorcolor + "Server Disconnected us at " +
                    timecolor + self.getTime() + defaultcolor)
        if(self.debug):
            self.output(errorcolor + "Going into EVAL mode, EOF (Control-d) to quit." + defaultcolor)
            line = "blah"
            while line:
                try:
                    line = raw_input("> ")
                    try:
                        pp.pprint(eval(line))
                    except:
                        print errorcolor + "That didn't eval well" + defaultcolor
                except EOFError:
                    sys.exit(-1)
                except KeyboardInterrupt:
                    sys.exit(-1)
            sys.exit(-1)
        self.imcom = None
        self.imcom = IMCom(self)
        self.registerCallbacks()
        self.output(errorcolor + "Waiting 5 seconds before reconnect..." + defaultcolor)
        time.sleep(5)
        self.applyProfile()
        if(self.currentStatus == "chat"):
            self.imcom.sendChat()
        elif(self.currentStatus == "away"):
            self.imcom.sendAway(self.currentStatusReason)
        elif(self.currentStatus == "xa"):
            self.imcom.sendXA(self.currentStatusReason)
        elif(self.currentStatus == "dnd"):
            self.imcom.sendDND(self.currentStatusReason)



    def handleAdminWho(self, who, show, status, available, resource):
        """This function is called by the IMCom library when there has been a request for the list of users on the server. You
        must be admin for this function to work."""
        s = usercolor + who + "/" + resource + defaultcolor + " is " + statuscolor + show + defaultcolor + " as " + desccolor + \
            status + defaultcolor
        self.output(s)


    def handlePresenceUpdate(self, who, show, status, available, resource,
        duplicate):
        """This function is called by the IMCom library when there has
        been a change in someone's precense. The user must be
        subscribed to the presence."""
        if(self.debug):
            logDebug("Updating presence of " + who + " to " + show +
                     " as " + status)

        # don't print the status of anyone not in our jidlist and not
        # a transport
        if( (not self.imcom.jidhash.has_key(who) or
             not self.imcom.gjidhash.has_key(who) ) and
            ( string.find( who, "@" ) != -1 ) ):
            if(self.debug):
                logDebug("jidhash doesn't have a " + who)
            return

        nick = self.getNick(who)

        # these handle error conditions in transports.
        if( ( string.find( who, "@" ) == -1 )
            and show and show == "offline" ):
            s = errorcolor + "ERROR: " + defaultcolor + \
                nick + " is offline"
            if(status):
                s = s + " : " + status
            self.output(s)
            return
        if( ( string.find( who, "@" ) == -1 )
            and show and ( string.find( show, " " ) != -1 ) ):
            s = errorcolor + "Notice: " + defaultcolor + \
                nick + ": " + show
            self.output(s)
            return

        # this is in response to a bug in trillian (icq program)
        # yes, the fix should be server-side but you know....
        if( ( self.profile.switches['igndupstatus'][0] == "true" ) 
            and duplicate ):
            return

        # commented out for testing/reimplementation of how IMCom
        # deals with transports.
        #if( string.find( who, "@" ) == -1 and not self.imcom.agenthash.has_key(who)):
        #    return

        # don't display the presence of people in the lurker group.
        if( self.imcom.gjidhash.has_key(who) and
            (operator.contains(self.imcom.gjidhash[who],"lurker") or
             operator.contains(self.imcom.gjidhash[who],"ignore"))):
            if(self.debug):
                logDebug("Skipping lurker: " + who)
            return

        # only display status updates if we are set to display status updates.
        if(self.showPresenceUpdates):
            ttime = self.getTime()
            ddate = self.getDate()
            if(available):
                s = usercolor + nick + defaultcolor
                # if they have a resource, and they're not a transport
                # then add the resource string.
                if(resource != None and resource != "" and not
                   self.imcom.agenthash.has_key(who)):
                    s = s + "/" + keycolor + resource + defaultcolor

                # append the rest of the information
                s = s + " changed status to " + \
                    statuscolor + show + defaultcolor + " (" + \
                    desccolor + status + defaultcolor + ") at " + \
                    timecolor + ttime + defaultcolor

                self.output(s)
            else:
                s= usercolor + nick + defaultcolor
                # if they have a resource, and they're not a transport
                # then add the resource string.
                if(resource != None and resource != "" and not
                   self.imcom.agenthash.has_key(who)):
                    s = s + "/" + keycolor + resource + defaultcolor

                # append the rest of the information
                s = s + " is unavailable : " + \
                    desccolor + status + defaultcolor + \
                    " at " + timecolor + ttime + defaultcolor
                self.output(s)



    def handleConferenceMessage(self, conf, nick, body, delay):
        "This function is called from the IMCom library when a message has been received from a conference"
        if(self.profile.switches['confsuppress'][0] == "false"):
            towrite = usercolor + nick + defaultcolor + "/" + keycolor + conf + defaultcolor
            ttime = self.getTime()
            ddate = self.getDate()
            towrite = towrite + " - Conference Message - "

            # append the timestamp and the rest of the information
            towrite = towrite + timecolor + ddate + " |  " + ttime + defaultcolor
            towrite = towrite + "\n" + messagebodycolor + body + defaultcolor
        else:
            towrite = usercolor + nick + defaultcolor + "/" + keycolor + conf + defaultcolor + " : "
            towrite = towrite + messagebodycolor + body + defaultcolor

        # ring the terminal bell if configured to
        if(self.ringBell):
            self.printText(chr(7))

        self.output(towrite)


    def handleConferencePresence(self, conf, nick, show, reason):
        "This function is called from the IMCom library when someone in a conference has changed status"
        if(self.showPresenceUpdates and 0):
            towrite = usercolor + nick + defaultcolor + "/" + keycolor + conf + defaultcolor

            ttime = self.getTime()
            ddate = self.getDate()
            towrite = towrite + " - Conference Message - "
            self.output(towrite)

    def handleMessageReceive(self, ffrom, body, thread, delay,
                             resource):
        """This function is called from the IMCom library when a
        message has been received from a user or transport/service."""

        # don't display messages from people who belong to the ignore
        # group.
        if( self.imcom.gjidhash.has_key(ffrom) and
            operator.contains(self.imcom.gjidhash[ffrom],"ignore")):
            if(self.debug):
                logDebug("Skipping message from ignored user: " + ffrom)
            return

        # append the jid to the top of the stack of last users to
        # event us.
        self.appendUserQueue(ffrom)

        # try and get their nickname. otherwise their nickname is
        # their jid.
        try:
            nick = self.imcom.jidhash[ffrom]
        except:
            nick = ffrom

        ttime = None
        ddate = None
        towrite = None
        towrite = usercolor + nick + defaultcolor

        if(resource != ""):
            towrite = towrite + "/" + keycolor + resource + defaultcolor

        # if the delay timestamp exists, format it properly, otherwise
        # the current time.
        if(delay != None):
            # parse the delay, attach the timestamp in good format.
            # tuple is (year, month, day, hour, minutes, seconds,
            #           daysintotheyear, dst)
            mytup = (int(delay[:4]),int(delay[4:6]),int(delay[6:8]),
                     int(delay[9:11]),int(delay[12:14]),int(delay[15:17]),0,0,0)
            thet = time.localtime( time.mktime(mytup) - time.timezone)
            ttime = time.strftime('%H:%M:%S',thet)
            ddate = time.strftime('%m/%d/%Y',thet)
            towrite = towrite + " - " + errorcolor + \
                      "DELAYED " + defaultcolor + "Message - "
        else:
            ttime = self.getTime()
            ddate = self.getDate()
            towrite = towrite + " - Message - "

        # append the timestamp and the rest of the information
        towrite = towrite + timecolor + ttime + defaultcolor
        if(delay != None):
            towrite = towrite + " on " + timecolor + ddate
        towrite = towrite + "\n" + messagebodycolor + body + defaultcolor

        # ring the terminal bell if configured to
        if(self.ringBell):
            self.printText(chr(7))

        self.lastReceived = ffrom
        self.output(towrite)
        # threadhash is used so that if someone is seeing this as a
        # particular conversation we continue the conversation. (Silly
        # GUI apps)
        self.threadHash[ffrom] = thread
        self.logMessage(ffrom,ffrom,ddate,ttime,body)



    def handleMessageError(self, ffrom, body, code):
        """This function is called by the IMCom library when there has
        been some sort of error related to sending or receiving a
        message. CLI just prints out the information for the user."""
        if( ( code == "0" or code == 0 ) and body != "" ):
            towrite = errorcolor + "ERROR: " + defaultcolor + body
        else:
            towrite = errorcolor + "ERROR: " + defaultcolor + \
                      "sending message to"\
                      " " + usercolor + ffrom + defaultcolor + " :"\
                      " " + errorcolor + errorCodes[code] + defaultcolor
        self.output(towrite)



    def handleIQError(self, ffrom, code, body):
        """This function is called by the IMCom library when there has
        been some sort of error related to information request or
        submission. CLI just prints out the information for the user."""
        towrite = errorcolor + "ERROR: " + defaultcolor + "InfoQuery event"\
                  " " + usercolor + ffrom + defaultcolor + " :"\
                  " " + errorcolor + errorCodes[code] + defaultcolor
        if body != None:
            towrite = towrite + " : " + errorcolor + body + defaultcolor
        self.output(towrite)



    def handleInfoError(self, code, text):
        """This function is called by the IMCom library when there has
        been some sort of error related to information request.
        CLI just prints out the information for the user."""
        towrite = errorcolor + "ERROR: " + defaultcolor + "getting info"\
                  " Code: " + errorcolor + code + defaultcolor + " "\
                  " Text: " + errorcolor + text + defaultcolor
        self.output(towrite)



    def handleFileReceive(self, jid, url):
        """This function is called by the IMCom library when someone
        has requested to send us a file."""
        self.appendUserQueue(jid)
        nick = self.getNick(jid)
        towrite = usercolor + nick + defaultcolor + " wants to send you"\
                  " a file at URL:\n" + keycolor + url + defaultcolor +\
                  "\nTo receive the file type: \n" + \
                  self.prefs.getCommand(self.profile, "getfile") + \
                  " " + nick
        self.output(towrite)



    def handleFileReceived(self, jid, file):
        """This function is called by the IMCom library when we have
        successfully received the file."""
        self.appendUserQueue(jid)
        nick = self.getNick(jid)
        towrite = "Successfully receieved a file from " + usercolor +\
                  nick + defaultcolor + \
                  "\nThe file is saved in " + keycolor + "~/.imcom/files/" + \
                  file + defaultcolor
        self.output(towrite)



    def handleFileErrorReceived(self, jid, url, type, text):
        """This function is called by the IMCom library when there has
        been some sort of error in file transmission."""
        self.appendUserQueue(jid)
        towrite = errorcolor + "Error: " + defaultcolor + \
                  "An error occured trying to get a file from " + \
                  usercolor + jid + defaultcolor + ".\nThe URL: " + \
                  keycolor + url + defaultcolor + "\n" + \
                  "Errortype: " + str(type) + "\n" + \
                  "Errorvalue: " + str(text)
        self.output(towrite)



    def handleAgentList(self, theList):
        """This function is called by the IMCom library when we have
        received a list of agents from the server. Agents is another
        word for Transport or Server in Jabber terminology."""
        self.output("The following is a list of transports available on" +
                    " your Jabber server.")
        for i in theList:
            if(i.name):
                self.output(keycolor + i.jid + defaultcolor +
                            " : " + defaultcolor + i.name)


    def handleAgentRegister(self, id, ffrom, instructions, fields):
        # 
        # this will cause issues if gettingCommand is already set to 1
        # however, in that case things get *really* complicated anyway...
        # and it's not likely to happen unless the user is playing with us
        #
        self.output("The following are instructions to complete the "\
                    "registration process, they may be of use, then "\
                    "again, don't count on it.")
        self.output(instructions)
        #self.output("Use the " +
        #            self.prefs.getCommand(self.profile, "agentreg") +
        #            " command with " + ffrom + " as the first parameter " +
        #            " and the data for the following fields IN ORDER " +
        #            "as the remaining parameters.")
        newcommand = self.prefs.getCommand( self.profile, "interactivereg" )
        newcommand += " " + ffrom
        for i in fields:
            newcommand += "<" + i
        self.commandQueue.append( newcommand )
            
    def handleAgentRegistered(self, ffrom):
        """This function is called by the IMCom library when an agent
        has been successfully registered."""
        self.output("Your registration request to " + ffrom +
                    " was successful. I will authorize the transport now "
                    "for you.")
        addcommand = self.prefs.getCommand( self.profile, "add" )
        self.output( "To add contacts who use the " + self.getNick( ffrom ) +
            " use the command:\n" + addcommand +
            " id@" + ffrom + " nick\n" +
            "for instance, " + addcommand + " airog@" + ffrom + " airog" )
        self.imcom.sendSubscribed(ffrom+"/registered")
        return

    def handleSubscribe(self, ffrom):
        """This function is called by the IMCom library when someone
        is requesting to add us to their roster."""
        self.appendUserQueue(ffrom)
        if(self.debug):
            logDebug("Handling a subscribe request, from : " + ffrom)
        ffrom = self.getNick(ffrom)
        self.output(usercolor + ffrom + defaultcolor +
                    " requests permission to subscribe to your presence")

    def handleSubscribed(self, ffrom):
        """This function is called by the IMCom library when someone
        authorized you to add them to your roster."""
        self.appendUserQueue(ffrom)
        nick = self.getNick(ffrom)
        self.output(usercolor + nick + defaultcolor +
                    " authorized your subscription request")

    def handleUnsubscribed(self, ffrom, success):
        """This function is called by the IMCom library when someone
        authorized you to remove them to your roster."""
        self.appendUserQueue(ffrom)
        if(success):
            nick = self.getNick(ffrom)
            self.output("Removed " + usercolor + nick + defaultcolor +
                        " from your roster")
        else:
            self.output("Removal process failed!")


    def handleUnsubscribe(self, ffrom):
        """This function is called by the IMCom library when someone
        is requesting to remove us from their roster."""
        self.imcom.sendUnsubscribed(ffrom)

    # Reports whether a change to the roster was successful or not
    def handleRosterUpdateCheck(self, successful, error):
        """This function is called by the IMCom library after a roster
        change has been made, telling us whether or not it was successful"""
        if(successful):
            self.output("Roster update successful")
        else:
            self.output("Roster update " + errorcolor + " Error: " +
                        error + defaultcolor)

    # Called when there has been a change to the roster
    def handleRosterUpdate(self, jid, name, subs, success):
        """This function is called by the IMCom library when another
        resource has made a change to our roster.
        """
        # update our roster lists based upon information in jid, name
        if(subs == "remove"):
            if(success):
                self.output("Removed " + usercolor + jid + defaultcolor +
                            " from our lists")
                return
            else:
                self.output("Removal process " + errorcolor +
                            "failed!" + defaultcolor)
                return

    def handleVCardSubmit(self, success):
        if success:
            self.output("Successfully submitted your vcard")
        else:
            self.output("Your VCard submission was unsuccessful.")

    def handleVCard(self, ffrom, fn, given, family, nickname, email):
        self.appendUserQueue(ffrom)
        if(ffrom == None):
            return
        self.output("Information on " + usercolor + ffrom +
                    defaultcolor + ": ")
        if(fn != None):
            self.output(keycolor + "Fullname: " + desccolor + fn +
                        defaultcolor)
        if(given != None):
            self.output(keycolor + "Given: " + desccolor + given +
                        defaultcolor)
        if(family != None):
            self.output(keycolor + "Family: " + desccolor +
                        family + defaultcolor)
        if(nickname != None):
            self.output(keycolor + "Nickname: " + desccolor +
                        nickname + defaultcolor)
        if(email != None):
            self.output(keycolor + "Email: " + desccolor +
                        email + defaultcolor)

    def handleNoVCard(self, ffrom):
        self.appendUserQueue(ffrom)
        if(ffrom == None):
            return
        self.output("There is no information (no vcard filed) for " +
                    usercolor + ffrom + defaultcolor + ". ")
        return

    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # -------------------------------------------------------------------------
    #  Utility Functions
    # -------------------------------------------------------------------------
    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++



    def addSocketWrapper(self, sw):
        self.socketlisteners.append(sw)
        self.output("A client socket started.")

    def removeSocketWrapper(self, sw):
        self.socketlisteners.remove(sw)
        self.output("A client socket disconnected.")


    def setColors(self):
        global usercolor, statuscolor, errorcolor, messagebodycolor,\
               timecolor, desccolor, sepcolor, defaultcolor, keycolor
        g = self.prefs.defaultcolors.colorsdict
        usercolor = g["user"]
        statuscolor = g["status"]
        errorcolor = g["error"]
        messagebodycolor = g["messagebody"]
        timecolor = g["time"]
        desccolor = g["desc"]
        sepcolor = g["sep"]
        defaultcolor = g["default"]
        keycolor = g["key"]

        t = self.profile.colors.colorsdict
        if t.has_key("user"):
            usercolor = t["user"]
        if t.has_key("status"):
            statuscolor = t["status"]
        if t.has_key("error"):
            errorcolor = t["error"]
        if t.has_key("messagebody"):
            messagebodycolor = t["messagebody"]
        if t.has_key("time"):
            timecolor = t["time"]
        if t.has_key("desc"):
            desccolor = t["desc"]
        if t.has_key("sep"):
            sepcolor = t["sep"]
        if t.has_key("default"):
            defaultcolor = t["default"]
        if t.has_key("key"):
            keycolor = t["key"]
        self.profile.switches['colors'][0] = "true"
        setCIColors(self.prefs, self.profile)

    def setNoColors(self):
        global usercolor, statuscolor, errorcolor, messagebodycolor,\
               timecolor, desccolor, sepcolor, defaultcolor, keycolor
        usercolor = ""
        statuscolor = ""
        errorcolor = ""
        messagebodycolor = ""
        timecolor = ""
        desccolor = ""
        sepcolor = ""
        defaultcolor = ""
        keycolor = ""
        self.profile.switches['colors'][0] = "false"


    def beginLine(self):
        if(self.profile.switches['colors'][0] == "true"):
            output=chr(27)+"[%dD"%80
            self.printText(output)

    def clearCommand(self):
        self.beginLine()
        if(self.profile.switches['colors'][0] == "true"):
            output=chr(27)+"[K"
            self.printText(output)

    def statusNag(self):
        if( self.currentStatus != "online" ):
            self.output( "Your current status is " +
                statuscolor + self.currentStatus + defaultcolor +
                ": " + desccolor +
                self.currentStatusReason + defaultcolor )

    def printText(self, st):
        try:
            sys.stdout.write(st.encode(self.profile.encoding)) #SafeStr(unicode(st)))
            sys.stdout.flush()
        except:
            traceback.print_exc()
            print "An exception occuring trying to print to terminal."
            print "This is most likely due to an extended ascii character."
            print "You may have missed a message or query response."

    def dumpQueue(self):
        for item in self.outputQueue:
            self.clearCommand()
            self.printText(item+"\n")
        self.outputQueue = []

    def output(self, st):
        for key in self.socketlisteners:
            key.sendData(st+"\n.\n")
        if(( self.mode == self.MULTILINE) and (  not self.allowInterrupt )):
            self.outputQueue.append(st)
            return
        self.clearCommand()
        self.printText(st+"\n")
        if(READLINE == 1 and self.gettingCommand):
            self.printText(self.prompt + readline.get_line_buffer())
        elif(self.gettingCommand):
            self.printText(self.prompt)


    def checkto(self, nick):
        if(-1 != string.find(nick,"@")):
            return 1
        if self.imcom.nickhash.has_key(nick):
            return 1
        if self.imcom.confnick.has_key(nick):
            return 1
        tmp = string.split(nick,".")
        if(len(tmp) > 1):
            return 1
        return 0

    def getTime(self):
        return time.strftime('%H:%M:%S',time.localtime(time.time()))

    def getDate(self):
        return time.strftime('%m/%d/%Y',time.localtime(time.time()))

    # begin add by ted
    def logMultiMessage(self, fnames, sender, ddate, ttime, text):
        for fname in fnames:
            self.logMessage(fname, sender, ddate, ttime, text)
    # end add by ted

    def logMessage(self, fname, sender, ddate, ttime, text):
        path = os.environ['HOME']+"/.imcom/"+self.profile.name+"/"

        if( sender == self.profile.user+"@"+self.profile.server ):
            self.lastToDict[fname] = text
        else:
            self.lastFromDict[sender] = text

        # strip filename of /'s from resource string..
        if(-1 != fname.find("/")):
            fname = fname[:fname.find("/")]

        # fix the text by replacing < with &lt; etc.

        text = self.imcom.normalize(text)

        # make sure path exists.
        try:
            os.makedirs(path[:-1],0700)
        except:
            pass
        try:
            if (fname != self.getNick(fname)):
                os.symlink(fname,path+self.getNick(fname))
        except:
            pass
        try:
            if(CODECS):
                f = codecs.open(path+fname,"ab+",'ISO-8859-1','replace')
            else:
                f = open(path+fname,"ab+")
            f.write("<message from='"+sender+"' date='"+ddate+
                    "' time='"+ttime+"'>")
            f.write(text)
            f.write("</message>\n")
            f.flush()
            f.close()
        except:
            print "There was an exception attempting to log the message"
            traceback.print_exc()


    def getJID(self, nick):
        result = nick
        resource = None
        if(nick[0] == "$" and len(nick) == 2):
            n = int(nick[1])
            if(len(self.userQueue)>n):
                result = self.userQueue[n]
                if(self.debug):
                    logDebug("$? Result is: " + result)
                return (result, None)
            else:
                result = nick
        if(nick != None and -1 != string.find(nick,"/")):
            resource = nick[string.find(nick,'/')+1:]
            nick = nick[:string.find(nick,'/')]
        else:
            resource = None
        if(self.imcom.nickhash.has_key(nick)):
            result = self.imcom.nickhash[nick]
        elif(self.imcom.confnick.has_key(nick)):
            result = self.imcom.confnick[nick]
        else:
            result = nick
        return (result, resource)

    def isAgent(self, jid):
        return self.imcom.agenthash.has_key(jid)

    def getNick(self, jid):
        result = jid
        if( string.find( jid, "@" ) == -1 ):
            if( self.imcom.agenthash.has_key(jid) ):
                return self.imcom.agenthash[jid]
        if(self.imcom.jidhash.has_key(jid) and
           self.imcom.jidhash[jid] != None):
            result = self.imcom.jidhash[jid]
        else:
            result = jid
        return result

    def updateKeyInHash(self, hash, oldkey, newkey):
        keys = hash.keys()
        for item in keys:
            if(item == oldkey):
                value = hash[item]
                hash[newkey]=value
                return

    def updateHash(self, hash, oldkey, newkey, value):
        keys = hash.keys()
        for item in keys:
            if(item == oldkey):
                del hash[item]
        hash[newkey] = value


    def appendUserQueue(self, ffrom):
        self.userQueue.insert(0,ffrom)
        if(len(self.userQueue) > 10):
            self.userQueue = self.userQueue[-10:]
        return











    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    # -------------------------------------------------------------------------
    #  Command Line Input, Execution Functions
    # -------------------------------------------------------------------------
    # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


    def run(self):
        self.running = 1
        try:
            while(self.running):
                if( len( self.commandQueue ) == 0 ):
                    self.gettingCommand = 1
                    self.line = raw_input(self.prompt)
                    self.line = unicode(self.line, self.profile.encoding)
                    self.gettingCommand = 0
                else:
                    self.line = self.commandQueue.pop( 0 )
                self.executeCommand(self.line)
        except EOFError:
            print
            self.executeCommand(self.prefs.getCommand(self.profile,"quit"))
        except KeyboardInterrupt:
            print
            self.executeCommand(self.prefs.getCommand(self.profile,"quit"))
            #except:
            #    print "something bad happened"
            #    print "Unexpected error:", sys.exc_info()[0]
            #    self.executeCommand(self.profile.commands.quit)

    def getAllText(self,prompt):
        l = ""
        t = ""
        saveprompt = ""

        self.mode = self.MULTILINE
        if(READLINE == 1):
            readline.parse_and_bind("set disable-completion on")
        self.gettingCommand = 1

        # save the current prompt before we change it
        saveprompt = self.prompt
        self.prompt = "[" + prompt + "]: "

        l = raw_input( self.prompt )
        while(l != "."):
            if(l == "#"):
                t = None
                break
            t = t + l + "\n"
            l = raw_input("[" + prompt + "]: ")
        self.gettingCommand = 0
        if(READLINE == 1):
            # turn completions back on
            readline.parse_and_bind("set disable-completion off")
        # show the messages that came in while they were busy
        self.dumpQueue()
        self.mode = 0
        # repair the prompt
        self.prompt = saveprompt
        # remove the last newline?
        if( t != None ):
            t = t[:-1]
            t = unicode(t, self.profile.encoding)
        return t

    def checkAlias(self, command, line, profile):
        if(profile.aliases.has_key(command)):
            return profile.aliases[command] + " " + line
        return None



    def executeCommand(self, line):
        line = SafeStr(line)
        p = self.prefs
        pr = self.profile
        temp = string.split(line,None,1)
        if(len(temp)==0):
            self.autostatus.setIdleTime( 0 )
            self.autostatus.doAutoStatus()
            return
        command = string.lower(temp[0])
        if(len(temp)>1):
            line = temp[1]
        else:
            line = ""
        line = string.strip(line)
        a = self.checkAlias(command, line, pr)
        if(a != None):
            self.executeCommand(a)
            return
        if(command == "/startserver"):
            self.socketserver = SocketWrapper.listener(self)
            self.socketserver.start()
            print "Started ths socket server"
            return
        if(command == "/stopserver"):
            self.socketserver.cli = None
            self.socketserver.running = 0
            print "Stopped the socket server"
            return
        if(command == "/eval"):
            try:
                pp.pprint(eval(line))
            except:
                print errorcolor + "ERROR: " + defaultcolor + \
                      "This doesn't evaluate well: <" + line + ">"
            return
        keys = pr.commands.commandsdict.keys()
        for key in keys:
            if command == pr.commands.commandsdict[key]:
                callstring = key + "Command(self, line)"
                eval(callstring)
                return
        print "No command found??"



    def findItem(self, dict, item):
        keys = dict.keys()
        for blah in keys:
            if(dict[blah] == item):
                return blah
        return None

    def printRoster(self):
        self.printRosterBackend( 0 );

    def printRosterBackend(self,onlineonly):
        trs = ""  # trs is theReturnString
        getTerminalWidth()
        trs = trs + sepcolor + \
              "----------------------------------------" + defaultcolor + "\n"
        trs = trs + "You are currently " + statuscolor + self.imcom.status + \
              defaultcolor + ":" + self.currentStatusReason + "\n"
        trs = trs + sepcolor + \
              "---------------Your Roster--------------" + defaultcolor + "\n"
        keys = self.imcom.jidhash.keys()
        mxn = 0
        mxs = 0
        mxt = 0
        nicks = {}
        for person in keys:
            if(not self.imcom.gjidhash.has_key(person)):
                continue
            glist = self.imcom.gjidhash[person]
            if(operator.contains(glist,"lurker")):
                continue
            nick = self.getNick(person)
            if not self.imcom.preshash.has_key(person):
                continue
            available,show,status = self.imcom.preshash[person]
            if(len(nick) > mxn):
                mxn = len(nick)
            if(len(show) > mxs):
                mxs = len(show)
            if(len(status) > mxt):
                mxt = len(status)
            nicks[nick] = (show, status, available)
        order = ("dnd","xa","away","chat","online")
        keys = nicks.keys()
        if( not onlineonly ):
            for person in keys:
                show,status,a = nicks[person]
                if(not a):
                    line = usercolor + string.ljust(person,mxn+4) + \
                            defaultcolor + statuscolor + \
                            string.ljust(show,mxs+2) + defaultcolor +\
                            desccolor + status + defaultcolor
                    trs = trs + line + "\n"
        for stat in order:
            for person in keys:
                show,status,a = nicks[person]
                if(a and show == stat):
                    resources = ""
                    if(self.imcom.reshash.has_key(self.imcom.nickhash[person])):
                        for res in self.imcom.reshash[self.imcom.nickhash[person]][2].keys():
                            ress = self.imcom.reshash[self.imcom.nickhash[person]][2][res][1][0]
                            if(resources == "" and res != ""):
                                resources = " from resources: (" + keycolor + res + \
                                            defaultcolor + ", " + statuscolor + ress + \
                                            defaultcolor + ")"
                            elif(resources != ""):
                                resources = resources + ", (" + keycolor + res + \
                                            defaultcolor + ", " + statuscolor + ress + \
                                            defaultcolor + ")"
                    line = usercolor + string.ljust(person,mxn+4) +\
                           defaultcolor + statuscolor +\
                           string.ljust(show,mxs+2) + defaultcolor +\
                           desccolor + status + defaultcolor + resources
                    trs = trs + line + "\n"
        self.output(trs)

    def printOnlineRoster(self):
        self.printRosterBackend( 1 );

    def printOnlineRosterOldDelete(self):
        getTerminalWidth()
        self.output(sepcolor +
              "----------------------------------------" + defaultcolor)
        self.output("You are currently " + statuscolor + self.imcom.status +
              defaultcolor + " : " + self.currentStatusReason)
        self.output(sepcolor +
              "--------------Users Online--------------" + defaultcolor)
        keys = self.imcom.jidhash.keys()
        mxn = 0
        mxs = 0
        mxt = 0
        nicks = {}
        for person in keys:
            if(not self.imcom.gjidhash.has_key(person)):
                continue
            glist = self.imcom.gjidhash[person]
            if(operator.contains(glist,"lurker")):
                continue
            nick = self.getNick(person)
            if(not nick):
                continue
            available,show,status = self.imcom.preshash[person]
            if(len(nick) > mxn):
                mxn = len(nick)
            if(len(show) > mxs):
                mxs = len(show)
            if(len(status) > mxt):
                mxt = len(status)
            nicks[nick] = (show, status, available)
        order = ("dnd","xa","away","chat","online")
        keys = nicks.keys()
        for stat in order:
            for person in keys:
                show,status,a = nicks[person]
                if(a and show == stat):
                    resources = ""
                    if(self.imcom.reshash.has_key(self.imcom.nickhash[person])):
                        for res in self.imcom.reshash[self.imcom.nickhash[person]][2].keys():
                            ress = self.imcom.reshash[self.imcom.nickhash[person]][2][res][1][0]
                            if(resources == "" and res != ""):
                                resources = " from resources: (" + keycolor + res + \
                                            defaultcolor + ", " + statuscolor + ress + \
                                            defaultcolor + ")"
                            elif(resources != ""):
                                resources = resources + ", (" + keycolor + res + \
                                            defaultcolor + ", " + statuscolor + ress + \
                                            defaultcolor + ")"
                    line = usercolor + string.ljust(person,mxn+4) +\
                           defaultcolor + statuscolor + \
                           string.ljust(show,mxs+2) + defaultcolor +\
                           desccolor + status + defaultcolor + resources
                    self.output(line)

    def showGroupMembers(self, groupname):
        print(sepcolor +
              "----------------------------------------" + defaultcolor)
        print(keycolor + string.center(groupname,40) + defaultcolor)
        print(sepcolor +
              "----------------------------------------" + defaultcolor)
        keys = self.imcom.grouphash[groupname]
        mxn = 0
        mxs = 0
        mxt = 0
        nicks = {}
        for person in keys:
            glist = self.imcom.gjidhash[person]
            if(groupname != "lurker" and
               operator.contains(glist,"lurker")):
                continue
            nick = self.getNick(person)
            available,show,status = self.imcom.preshash[person]
            if(len(nick) > mxn):
                mxn = len(nick)
            if(len(show) > mxs):
                mxs = len(show)
            if(len(status) > mxt):
                mxt = len(status)
            nicks[nick] = (show, status, available)
        order = ("dnd","xa","away","chat","online")
        keys = nicks.keys()
        for person in keys:
            show,status,a = nicks[person]
            if(not a):
                line = usercolor + string.ljust(person,mxn+4) + defaultcolor +\
                       statuscolor + string.ljust(show,mxs+2) + defaultcolor +\
                       desccolor + string.ljust(status,mxt) + defaultcolor
                print(line)
        for stat in order:
            for person in keys:
                show,status,a = nicks[person]
                if(a and show == stat):
                    resources = ""
                    if(self.imcom.reshash.has_key(self.imcom.nickhash[person])):
                        for res in self.imcom.reshash[self.imcom.nickhash[person]][2].keys():
                            ress = self.imcom.reshash[self.imcom.nickhash[person]][2][res][1][0]
                            if(resources == "" and res != ""):
                                resources = " from resources: (" + keycolor + \
                                            res + defaultcolor + ", " + \
                                            statuscolor + ress + \
                                            defaultcolor + ")"
                            elif(resources != ""):
                                resources = resources + ", (" + keycolor + \
                                            res + defaultcolor + ", " + \
                                            statuscolor + ress + \
                                            defaultcolor + ")"
                    line = usercolor + string.ljust(person,mxn+4) +\
                           defaultcolor + statuscolor +\
                           string.ljust(show,mxs+2) + defaultcolor +\
                           desccolor + status + defaultcolor + resources
                    print(line)

    def timeInRange( self, unixTime, beginTime, endTime ):
        curtime = time.localtime( unixTime )
        beginTime = 0, 0, 0, int( beginTime[0] ), int( beginTime[1] ), \
            0, 0, 0, curtime[8]
        endTime = 0, 0, 0, int( endTime[0] ), int( endTime[1] ), \
            0, 0, 0, curtime[8]
        curtime = 0, 0, 0, curtime[3], curtime[4], 0, 0, 0, curtime[8]

        if( ( time.mktime( curtime ) >= time.mktime( beginTime ) ) and
            ( time.mktime( curtime ) <= time.mktime( endTime ) ) ):
            return 1

        return 0

    def doAutoStatus( self, idleTime ):
        self.output( "Wow, michael just made a serious error. Please "\
            "message him at michael@www.cosby.dhs.org to "\
            "mention this." )
        return

def tabCompleter(text, state):
    global cli
    try:
        if(text == None):
            return None
        params = string.split(readline.get_line_buffer())
        if(len(params) == 1 and readline.get_line_buffer()[-1] != ' '):
            # We're tab completing a command
            r = completeFromHash(cli.profile.aliases,text,state)
            if(r == None):
                return completeFromHashValue(cli.profile.commands.commandsdict,
                                             text,state)
            else:
                return r
        if((len(params) == 1 and readline.get_line_buffer()[-1] == ' ') or
           (len(params) == 2 and readline.get_line_buffer()[-1] != ' ')):
            # We're probably completing a nick
            return completeFromHash(cli.imcom.nickhash,text,state)
        if((len(params) == 3 and readline.get_line_buffer()[-1] != ' ') or
           (len(params) == 2 and readline.get_line_buffer()[-1] == ' ')):
            # Command specific tab completion
            return None
    except:
        traceback.print_exc()
        a,b,c = sys.exc_info()
        print "An exception occured"
        print(a)
        print(b)
        print(c)

def completeFromHash(hash, text, state):
    keys = hash.keys()
    i=0
    for item in keys:
        if (item == None):
            continue
        if (text == item[:len(text)]):
            if(i == state):
                #print "Returning " + item
                return item
            else:
                i = i + 1
    #print "Returning None"
    return None

def completeFromHashValue(hash, text, state):
    #print "In completeFromHashValue"

    #print "hash is: ", hash
    keys = hash.keys()
    i = 0
    for item in keys:
        val = hash[item]
        if(text == val[:len(text)]):
            if(i == state):
                return val
            else:
                i = i + 1
    return None



def printStartup():
    print("IMCom is released under the BSD license.")
    print("Written by Casey Crabb")

def logDebug(s):
    print s.encode(sys.getdefaultencoding(),"replace")

# Patch from Jacob Lundqvist
def SafeStr( UnsafeStr ):
    '''Quick wrapper of high asci codes for imcom
    Since imcom only seems to support chars < 128
    We must wrap the international ones
    The codes commented out are the ones from
    htmlentitydefs.py
    I had to try and find the ones imcom uses wich
    happend to be quite different ones
    Unrecognised high chars are replaced by their
    ords
    '''
    safe_str=''
    safe_str=UnsafeStr.encode('ISO-8859-1','replace')
    return safe_str

def handler(signum, frame):
    #global cli
    #if cli.debug:
    print 'Signal handler called with signal', signum
    print 'Frame is ', frame
    pass

def quithandler(signum, frame):
    cli.executeCommand(cli.prefs.getCommand(cli.profile,"quit"))

if(__name__ == "__main__"):
    global cli
    # if LOCALE:
    #     locale.setlocale(locale.LC_ALL, "de") #("US","ISO-8859-1"))
    # signal.signal(signal.SIGWINCH,handler)
    # signal.signal(signal.SIGHUP,handler)
    signal.signal(signal.SIGINT,quithandler)
    # signal.signal(signal.SIGPIPE,handler)
    # signal.signal(signal.SIGUSR1,handler)
    # signal.signal(signal.SIGUSR2,handler)
    # signal.signal(signal.SIGCHLD,handler)
    # signal.signal(signal.SIGCONT,handler)
    # signal.signal(signal.SIGTRAP,handler)
    # signal.signal(signal.SIGIOT,handler)
    getTerminalWidth()
    printStartup()

    # remove the old debug log file
    try:
        os.remove(os.environ['HOME']+"/.imcom/debuglog.log")
    except:
        pass

    cli = CLI()
    if(len(sys.argv) > 1):
        profile = cli.prefs.getProfile(sys.argv[1])
        if(profile != None):
            cli.profile = profile

    i = cli.applyProfile()
    if(i):
        cli.applyPrefs()
        cli.start()
    else:
        cli.applyProfile()
        cli.applyPrefs()
        cli.start()

def yesnostr( val ):
    if( val ):
        return "yes"
    else:
        return "no"

def truefalsestr( val ):
    if( val ):
        return "true"
    else:
        return "false"

