#!/usr/bin/python

# AmpClient.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--
#       June 30, 2003           bar
#       July 1, 2003            bar     socket.error
#       August 30, 2004         bar     DOTALL where it should be
#                                       pull all messages when do_it returns True
#       September 5, 2005       bar     cosmetic changes
#       September 8, 2005       bar     move __all__
#       September 23, 2006      bar     IIclient.py uses it
#       October 31, 2006        bar     check the right on_line(), not on_line in send_msg
#                                       ready
#       November 18, 2007       bar     turn on doxygen
#       November 27, 2007       bar     insert boilerplate copyright
#       May 17, 2008            bar     email adr
#       November 29, 2011       bar     pyflake cleanup
#       --eodstamps--
##      \file
#
#
#       Be a client to an AMP protocol server.
#
#

import  re
import  socket
import  select
from    time                    import  time, asctime
from    types                   import  ListType, TupleType

from    tgcmsg                  import  *


PING_TIME       =   181


class an_amp_client :

    def __init__(me, host = "localhost", port = None, user_name = None, password = None) :

        t                       = int(time())


        me.screen_info          = False

        me._host                = host
        me._port                = int(port)

        me.user_name            = user_name
        me.password             = password
        me._logon_sid           = "0"
        me.logged_on            = False
        me._ready               = False

        me._latest_cnct_time    = t - 7                     # used to stop us from being too fast/often about hitting a non-existent server
        me._told_cnct_state     = -1                        # what did we last print as our state? (0 = disconnected, 1 = connected)

        me.cnct_tries           = 0

        me.sock                 = None

        me._rx_buff             = ""

        me._ping_time           = t
        me._pong_time           = t

        me.sid                  = 1                         # message id's constantly incrementing

        me.msg_q                = []                        # queue of messages received from clients (array of refs to AMP msgs)

        me.welcome_hash         = None

        #   It's be nice, if there is no port, to understand host:port in the host name parameter



    def get_msg(me) :
        """
            Get the next message that the server has sent to us.
            Return None if there are no messages in our queue.
        """

        if  not len(me.msg_q)   :   return(None)

        return(me.msg_q.pop(0))



    def on_line(me) :
        """
            Are we on line with a server?
        """

        if  me.sock == None :
            return(False)

        return(True)


    def ready(me) :
        """
            Are we either welcomed, or, if we are to log on, logged on?
        """

        return(me._ready)


    def drop(me) :
        """
            Drop the connection - instantly.
        """

        me._ready       = False
        me.logged_on    = False

        me._logon_sid   = "0"
        me.welcome_hash = None

        if  me.on_line() :
            me.sock.close()
            me.sock     = None

        pass


    def send_msg(me, msg) :
        """
            Send an AMP message through our socket.
        """

        if  isinstance(msg, ListType) or isinstance(msg, TupleType) :
            msg       = tgc_msg_as_string(msg)

        me._ping_time = int(time())

        if  me.on_line() :
            try :
                me.sock.sendall(msg + "\r\n")
                # print "socket sent", msg
            except socket.error :
                me.drop()
                return(False)

            return(True)

        return(False)



    def sid_send(me, msg) :
        """
            Send a message to the client using and returning the next available SID.
        """

        sid          = me.sid
        me.sid       = me.sid + 1

        if  isinstance(msg, ListType) or isinstance(msg, TupleType) :
            msg      = tgc_msg_as_string(msg)

        msg          = str(sid) + " " + msg

        if  me.send_msg(msg) :
            return(sid)

        return(None)


    def send_ping(me) :
        me.sid_send([ "PING", asctime() ])              # wrong time format???? !!!!



    def do_it(me) :
        """
            Do whatever needs be done - connect, talk, queue up messages from the server.
        """

        t = int(time())

        if  not me.on_line() :
            if  t - me._latest_cnct_time >= 7 :
                me._rx_buff = ""

                me.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

                try :
                    me.sock.connect( ( me._host, me._port ) )
                except socket.error,    msg :
                    # if  me.screen_info    : print "error connecting to",    me._host, "port", me._port, msg
                    me.sock = None
                except socket.herror,   msg :
                    # if  me.screen_info    : print "herror connecting to",   me._host, "port", me._port, msg
                    me.sock = None
                except socket.gaierror, msg :
                    # if  me.screen_info    : print "gaierror connecting to", me._host, "port", me._port, msg
                    me.sock = None

                if  me.on_line() :
                    me.sock.setblocking(False)
                    if  me._told_cnct_state != 1 :
                        if  me.screen_info  : print "Connected to",         me._host, "port", me._port, asctime()
                        me._told_cnct_state = 1
                    me.cnct_tries           = 0
                    pass
                else :
                    if  me._told_cnct_state != 0 :
                        if  me.screen_info  : print "Cannot connect to",        me._host, "port", me._port, asctime()
                        me._told_cnct_state = 0
                    me.cnct_tries           = me.cnct_tries + 1



                me._latest_cnct_time = t
                me._ping_time        = t
                me._pong_time        = t

            pass


        retval = False
        if  me.on_line() :
            # print "Connected to", me._host, "port", me._port
            rs     = []
            try :
                rs = select.select([me.sock], [], [me.sock], 0)
            except select.error, msg        :
                if  me.screen_info          :   print "select error", me._host, "port", me._port, msg
                me.drop()

            if  len(rs[0]) :
                # print "read from", me._host, "port", me._port
                try :
                    r = me.sock.recv(8196)
                    if  not len(r) :
                        if  me.screen_info  :   print "dropping silent", me._host, "port", me._port
                        me.drop()
                    me._rx_buff = me._rx_buff + r
                    # print "RX:", r
                except socket.error, msg    :
                    if  me.screen_info      :   print "error connecting to", me._host, "port", me._port, msg
                    me.drop()

            while len(me._rx_buff) :
                # print "rsbuff", me._rx_buff
                retval = True

                s      = ""
                g      = re.match(r"(.*?)[\r\n]+(.*)$", me._rx_buff, re.DOTALL)
                if  not g :
                    if  not me.on_line() :
                        s           = me._rx_buff
                        me._rx_buff = ""
                    else :
                        break
                else :
                    s               = g.group(1)
                    me._rx_buff     = g.group(2)

                msg = parse_tgc_msg(s)
                if  len(msg) :
                    if  len(msg) < 2 :
                        me.send_msg([ msg[0], "no", "No verb" ])
                    else :
                        me._pong_time = t

                        sid = msg[0]
                        v   = msg[1].upper()

                        if  v == "WELCOME" :
                            me.welcome_hash = msg[2]
                            me.msg_q.append(msg)
                            #                                                               if  me.screen_info      :   print "welcome"
                            if  (me.user_name != None) and (me.password != None) :
                                me._logon_sid  = str(me.sid)
                                me.sid_send([ "logon", me.user_name, me.password ])
                                #                                                           if  me.screen_info      :   print "logging on"
                            else :
                                me._ready      = True
                                #                                                           if  me.screen_info      :   print "ready"
                            pass

                        elif v == "PING" :
                            me.send_msg([sid, "Pong", str(t)])

                        elif v == "PONG" :
                            pass

                        elif v == "YESLOGON" :
                            if  msg[0] == me._logon_sid :
                                me.msg_q.append(msg)
                                me._ready       = True
                                me.logged_on    = True
                                #                                                           if  me.screen_info      :   print "yeslogon"
                            pass

                        elif v == "ERRLOGON" :
                            if  msg[0] == me._logon_sid :
                                me.msg_q.append(msg)
                                me.logged_on = False
                            pass

                        else :
                            me.msg_q.append(msg)
                        pass
                    pass
                pass


            pass

        if  me.on_line() :
            if   t - me._ping_time > PING_TIME :
                me.send_ping()
            elif t - me._pong_time > PING_TIME * 2 + 3 :
                if  me.screen_info  :   print "Dropping", me._host, "port", me._port, "for PONG reasons", asctime()
                me.drop()
            pass


        return(retval)

    pass                                                    # end of class an_amp_client



#
#
#   Test code.
#
#
if __name__ == '__main__' :
    import  sys

    if  len(sys.argv) < 3 :
        print "Tell me a server and port to connect to!"
    else :
        clt             = an_amp_client(sys.argv[1], sys.argv[2])
        clt.screen_info = True

        while True :
            if  clt.do_it() :
                while True :
                    m      = clt.get_msg()
                    if  m == None : break

                    print tgc_msg(m)
                pass
            pass

    pass



__all__ = [
            'an_amp_client',
          ]


#
#
#
# eof

