#!/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
#       May 27, 2012            bar     doxygen namespace
#       August 12, 2012         bar     put name in thread
#       October 15, 2013        bar     blow by no xmpp module
#       May 28, 2014            bar     put thread id in threads
#       --eodstamps--
##      \file
#       \namespace              tzpython.tz_jabber_client
#
#
#       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
try :
    import  xmpp
except ImportError :
    xmpp        = None

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, name = __file__ + '.a_callbacker')

        me.tid      = None

        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). """

        me.tid  = tzlib.get_tid()
        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. """

        if  not xmpp        :
            raise ValueError('No xmpp module available')

        threading.Thread.__init__(me, name = __file__ + '.a_client')

        me.tid              = None

        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.tid  = tzlib.get_tid()
        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
