#!/usr/bin/python

# tz_jabber_client.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       December 4, 2007        bar
#       May 17, 2008            bar     email adr
#       December 13, 2010       bar     futz with
#       --eodstamps--
##      \file
#
#
#       Jabber client logic.
#
#       reconnect not tested and a lot of other things, too
#
#


import  Queue
import  re
import  threading
import  time

#       Note: For the xmpppy, dnspython needs to be installed so xmpp can import dns.resolver.    Or pydns needs to be installed, but I've not tested it for xmpp to import DNS
import  xmpp

import  tzlib



RECNCT_TIME     = 31.0                                  # don't try to re-auth any faster than this number of seconds


class a_callbacker(threading.Thread) :
    """ Class for calling back to owner. """

    def __init__(me, owner) :
        """ Constructor. """

        threading.Thread.__init__(me)

        me.owner    = owner
        me.q        = Queue.Queue()

        me._stop    = False

        me.setDaemon(True)                              # so that we can kick out of the program while the thread is running


    def run(me) :
        """ Callback to dequeue things (messages from other jabber users). """

        while not me._stop :
            try :
                node    = me.q.get(True)
            except Queue.Empty :
                node    = None

            if  node    :
                owner   = me.owner
                if  owner :
                    typ = node.getType()
                    frm = node.getFrom()
                    bdy = node.getBody()
                    tim = node.getTimestamp()
                    if  typ and frm and bdy :

                        # print "msg", node, "type=", typ

                        if      typ == 'chat' :
                                if  hasattr(owner, 'tz_jabber_message_recieved') :
                                    owner.tz_jabber_message_recieved(frm, bdy, tim)
                        elif    typ == 'error' :
                                if  hasattr(owner, 'tz_jabber_message_error') :
                                    owner.tz_jabber_message_error(   frm, bdy, tim)
                        else :
                                print "@@@@", typ, frm, tzlib.c_string(bdy), tim
                        pass
                    pass
                pass
            pass
        pass



    def stop(me) :
        """ Try to stop us. Effect is not immediate. """

        me.q.put(None)
        me._stop    = True
        me.owner    = None



    pass    # a_callbacker




def get_me_from_conn(conn) :
    """ Since the callbacks don't take a void *, so to speak, we'll fake it. """

    o   = conn._owner
    if  hasattr(o, '_me') :
        return(o._me)

    return(None)



def on_msg(conn, node) :
    """ A message has come in. """

    me      = get_me_from_conn(conn)
    if  me :
        me.callbacker.q.put(node, True)
    pass



def on_disconnect(conn, node) :
    """ Jabber got a disconnect. """

    me  = get_me_from_conn(conn)
    if  me :
        me._whack_clt()
    pass




def on_presence(conn, node) :
    """ We can monitor when buddies come on line here. """

    jid     = 'test@jabber.org'

    if node.getFrom().bareMatch(jid):
        pass    # print "test presence", node.GetType(), node
    pass



def on_iq(conn, node) :
    """ Our buddy list is sent to us when we request the "roster", for instance. """

    ns      = node.getQueryNS()
    fr      = node.getFrom()
    no_node = node.getQuerynode()           # None for some reason
    info    = node.getQueryPayload()        # for instance: array of simplexml.Node, one for each buddy
    # reply   =  xmpp.protocol.Iq('result', ns, to = fr)
    # conn.send(reply)

    # print "iq", node

    me      = get_me_from_conn(conn)
    if  me :
        fr  = str(node.getFrom())
        g   = re.search(r"/(\d+)$", fr)
        if  g :
            me.unique_id    = g.group(1)
        pass

    pass








class   a_client(threading.Thread) :
    """
        Class to be a jabber client.

        Calls back to owner.msg(frm, msg, timestamp = None)
    """

    def __init__(me, owner, server = None, port = None, user_name = "", password = "") :
        """ Constructor. """

        threading.Thread.__init__(me)

        g                   = re.match(r"\s*(\S+)\s*@\s*(\S+)\s*", user_name)
        if  g :
            if  not server  :
                server      = g.group(2)
            user_name       = g.group(1)

        me.user_name        = user_name
        me.password         = password

        if  server :
            server              = str(server)
            if  port :
                server         += ":" + str(port)
            pass
        else :
            raise ValueError('No server')

        me.server           = server

        me.unique_id        = ""

        me.clt              = None
        me.auth             = False

        me.mq               = Queue.Queue()
        me.latest_auth      = tzlib.elapsed_time() - RECNCT_TIME

        me._stop            = False

        me.callbacker       = a_callbacker(owner)

        me.setDaemon(True)                              # so that we can kick out of the program while the thread is running


    def run(me) :
        """ Owner object called start on us. Do the thread. """

        me.callbacker.start()


        while not me._stop :

            if  (not me.clt) and (tzlib.elapsed_time() - me.latest_auth >= RECNCT_TIME) :

                me.latest_auth  = tzlib.elapsed_time()

                why = 0
                clt = xmpp.Client(me.server, debug = [])
                if  clt :
                    why = 1
                    if  clt.connect() :
                        why = 2
                        if  clt.auth(me.user_name, me.password) :
                            clt._me = me

                            me.clt  = clt

                            me.auth = True

                            me.clt.RegisterHandler('presence',      on_presence)
                            me.clt.RegisterHandler('iq',            on_iq)
                            me.clt.RegisterHandler('message',       on_msg)
                            me.clt.RegisterHandler('disconnect',    on_disconnect)

                            me.clt.sendInitPresence()                               # send roster (buddy list?) request and put us on line
                        pass
                    if  not me.clt :
                        del(clt)
                    pass

                if  not me.clt :
                    owner   = me.callbacker.owner
                    if  owner :
                        if hasattr(owner, 'tz_jabber_logon_failed') :
                            owner.tz_jabber_logon_failed(why)
                        pass
                    pass

                pass


            if  me.auth :
                try :
                    msg = me.mq.get_nowait()
                    if  msg :
                        me.clt.send(msg)
                    pass
                except Queue.Empty :
                    pass

                me.clt.Process(1)
            pass

        me._whack_clt()



    def _whack_clt(me) :
        """ Delete our client. We are disonnected. """

        me.auth = False

        clt     = me.clt
        me.clt  = None
        if  clt :
            clt._me  = None

            if  clt.isConnected() :
                clt.disconnect()

            del(clt)
        pass


    def stop(me) :
        """ Try to stop us. Effect is not immediate. """

        me.callbacker.stop()
        me._stop            = True



    def session_name(me) :
        """ Return our full session name if we have it. """

        if  not me.unique_id :
            return(None)

        return(me.user_name + "@" + me.server + "/" + me.unique_id)



    def is_on_line(me) :
        """ Are we on line and ready to send messages? """

        clt = me.clt
        if  me.auth and clt and clt.isConnected() :
            return(True)

        return(False)



    def send_msg(me, to, msg) :
        """ Queue a message for sending, whether we're on line or not. """

        me.mq.put(xmpp.protocol.Message(to, msg), True)     # blocking, but the queue should always be ready for data



    def delete_unsent_msgs(me) :
        """ Forget any messages we're waiting to send when we go back on line. """

        while not me.mq.empty() :
            try :
                me.mq.get(False)
            except Queue.Empty :
                pass
            pass
        pass



    pass    # a_client




if  __name__ == '__main__' :

    import  sys
    import  getpass

    import  TZCommandLineAtFile
    import  TZKeyReady


    del(sys.argv[0])
    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)


    user    = ""
    pw      = ""
    to_whom = ""

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--help", "-h", "/h", "-?", "/?" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        print """ options:
--user      user        Who to sign in as. e.g. user.name@example.com
--password  password    Password
--to        user        Who to send messages to. e.g. to_user@example.com
"""
        sys.exit(254)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--user", "-u", "/u" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        user    = sys.argv.pop(oi)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--password", "-p", "/p" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        pw      = sys.argv.pop(oi)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--to_whom", "--to", "-t", "/t" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        to_whom = sys.argv.pop(oi)


    print user, pw, to_whom

    if  not user :
        if  not sys.argv :
            print "No user name!"
            sys.exit(101)
        user    = sys.argv.pop(0)

    if  not pw :
        if  sys.argv :
            pw  = sys.argv.pop(0)
        else :
            print user
            pw  = getpass.getpass()
        pass

    if  not to_whom :
        if  not sys.argv :
            print "No user to send messages to!"
            sys.exit(102)
        to_whom = sys.argv.pop(0)



    class   an_owner :
        def __init__(me) :
           pass

        def tz_jabber_message_recieved(me, frm, msg, timestamp) :
            print frm, tzlib.c_string(msg), timestamp
        pass

        def tz_jabber_message_error(me, frm, msg, timestamp) :
            print "Error:", frm, tzlib.c_string(msg), timestamp
        pass

        def tz_jabber_logon_failed(me, why) :
            print "Logon failed - will try again!", why
        pass

        pass    # an_owner

    me  = an_owner()



    jc      = a_client(me, user_name = user, password = pw)
    jc.start()

    on_line = False
    li      = ""

    print """
Hit Control Q or X to exit
Control W to change who to send to to the input line
Control A to find out our unique name
"""

    while True :
        if  jc.is_on_line() :
            if  not on_line :
                print "On line"
            on_line = True
        elif on_line :
            print "Off line"
            on_line = False

        k   = TZKeyReady.key_ready()

        if  k == chr(ord('q') & 0x1f) :
            break
        if  k == chr(ord('x') & 0x1f) :
            break

        if  k >= ' ' :
            li += k
            sys.stdout.write(k)
        elif k == chr(ord('w') & 0x1f) :
            if  li :
                to_whom = li.strip()
                li      = ""
                print
                print
                print "Now sending to", to_whom
                print
            pass
        elif k == chr(ord('a') & 0x1f) :
            sys.stdout.write("\b \b" * len(li))
            li  = ""
            print
            print "Our name:", jc.session_name()
            print
        elif k == '\r' :
            sys.stdout.write("\r\n")
            jc.send_msg(to_whom, li)
            li  = ""
        elif k == '\n' :
            sys.stdout.write("\r\n")
            jc.send_msg(to_whom, li)
            li  = ""
        elif k == '\b' :
            li  = li[0:-1]
            sys.stdout.write(k)
            sys.stdout.write(' ')
            sys.stdout.write(k)
        elif k == chr(27) :
            sys.stdout.write("\b \b" * len(li))
            li  = ""

        else :
            time.sleep(0.1)
        pass

    jc.stop()
    jc.join(0.1)

    print
    print


#
#
#
# eof

