#! /usr/bin/env python

import sys
import urllib
import string
import SocketServer
import time

HDR_LEN = 13
CHK_LEN = 2
SEPARATOR = '/'

strn = 0

class EmiMessage:
    """Representation of an EMI2 message in a more user-friendly way.
    It can be used both to read and write EMI2 packets.
    """
    
    # Headers present in all EMI2 messages
    headers = [ 'TRN', 'LEN', 'OR', 'OT' ]

# List of fields for messages of type 50
    O50 = [ 'AdC', 'OAdC', 'AC', 'NRq', 'NAdC', 'NT', 'NPID',
            '', '', '', 'DD', 'DDT', 'VP', 'RPID', 'SCTS',
            'DSt', 'Rsn', 'DSCTS', 'MT', 'NB', 'Msg', 'MMS',
            '', 'DCS', 'MCI', '', '', '', 'OTOA', '', 'XSer', '', '' ]
    Rack50 = [ 'ACK', 'MVP', 'SM' ]
    Rnack50 = [ 'ACK', 'EC', 'SM' ]
    
    
    def __init__(self, data=None):
        "Create an new EMI2 message. If data is present, parse it."
        self.raw = ''
        self.header = { 'TRN': 0, 'LEN': 0, 'OR': '', 'OT': '' }
        self.data = {}
        self.chksum = 0
        for d in self.O50:
            self.data[d] = ''
        if data:
            self.parse(data)
    
    def get(self,name,default):
        "Get the value of field name from message or default if not found."
        if name in self.data:
            return self.data[name]
        else:
            return default
    
    def __getitem__(self,name):
        "Indexation operator so you can use an EmiMessage like a dict."
        if ((name in self.headers) or (name in self.O50) or
            (name in self.Rack50) or (name in self.Rnack50)):
            return self.data[name]
        else:
            raise AttributeError
    
    def __setitem__(self,name,value):
        "Indexation operator so you can use an EmiMessage like a dict."
        if ((name in self.headers) or (name in self.O50) or
            (name in self.Rack50) or (name in self.Rnack50)):
            self.data[name] = value
        else:
            raise AttributeError
    
    def parse(self,data):
        """Parse a byte stream and set the corresponding field in this object.
        Beware that is does _not_ clear the additional fields that might be
        present.
        """
        self.raw = data
        chunks = string.split(data,'/')
        for h,i in zip(self.headers,range(len(self.headers))):
            self[h] = chunks[i]
        if self['OR'] == 'O':
            names = self.O50
        elif self['OR'] == 'R':
            if chunks[len(self.headers)] == 'A':
                names = self.Rack50
            else:
                names = self.Rnack50
        else:
            raise 'Bad OR value'
        for c,i in zip(names,range(len(names))):
            self[c] = chunks[i+len(self.headers)]
        self.chksum = eval('0x'+chunks[-1])
    
    def pack(self):
        "Convert this message into its raw byte stream form."
        data = ''
        if self['OR'] == 'O':
            chunks = self.O50
        elif self['OR'] == 'R':
            if self['ACK'] == 'A':
                chunks = self.Rack50
            else:
                chunks = self.Rnack50
        else:
            raise AttributeError
        for c in chunks:
            data += '/'+self.get(c,'')
        data += '/'
        self['LEN'] = 13+len(data)+2 # 13 for header, and 2 for chksum
        head = '%02d/%05d/%s/%s' % (self['TRN'],self['LEN'],self['OR'],self['OT'])
        self.chksum = 0
        for c in head+data:
            self.chksum += ord(c)
        self.chksum = (self.chksum & 0xff)
        print "Checksum: %X"%self.chksum
        return head+data #+'%02X'%self.chksum don't add it for the time beeing, emi is not used everywhere.


class EMIRequestHandler(SocketServer.StreamRequestHandler):
    "The TCP request processor."
    
    def handle(self):
        "Process a request."
        self.count = 0 # This is used to generate DLR or NACK messages.
        while 1:
            stx = self.rfile.read(1)
            assert stx == '\x02'
            header = self.rfile.read(HDR_LEN)
            trn = header[:2]
            assert header[2] == SEPARATOR
            l = header[3:8]
            assert header[8] == SEPARATOR
            _or = header[9:10]
            assert header[10] == SEPARATOR
            ot = header[11:13]
            print "REQUEST: trn:%s, len:%s, or:%s, ot:%s" % (trn,l,_or,ot)
            l = int(l) - HDR_LEN - CHK_LEN
            data = self.rfile.read(l)
            assert data[0] == SEPARATOR
            assert data[-1] == SEPARATOR
            data = data[1:-1]
            print "ASCII"
            print data
            chk = self.rfile.read(CHK_LEN)
            etx = self.rfile.read(1)
            assert etx == '\x03'
            print "ANSWER"
            answers = []
            # Not all the EMI2 operation codes are supported.
            if ot == '60':
                # Login
                answers.append('%s/00019/R/60/A//'%trn)
                strn = 0
            if ot == '31':
                # Keep alive
                answers.append('%s/00019/R/31/A//'%trn)
            if ot == '51':
                # Send. An acknowledge containing a timestamp is returned.
                emi = EmiMessage(header+'/'+data+'/'+chk)
                stamp = time.strftime('%d%m%y%H%M%S')
                ans = EmiMessage()
                ans['TRN'] = int(trn)
                ans['OR'] = 'R'
                ans['OT'] = '51'
                if self.count == -1:
                    # Change the expression in the if to generate NACK msgs.
                    ans['ACK'] = 'N'
                    ans['EC'] = '06'
                    ans['SM'] = emi['AdC']+':'+stamp
                else:
                    # If not NACK then ACK :-)
                    ans['ACK'] = 'A'
                    ans['SM'] = emi['AdC']+':'+stamp
                answers.append(ans.pack())
                self.count += 1
                if False:
                    # If True: create a DLR in addition to the normal answer.
                    ans = EmiMessage()
                    ans['TRN'] = strn
                    ans['OR'] = 'O'
                    ans['OT'] = '53'
                    ans['AdC'] = '2488'
                    ans['OAdC'] = emi['AdC']
                    ans['SCTS'] = ans['DSCTS'] = stamp
                    ans['DSt'] = '1'
                    ans['Rsn'] = '1'
                    ans['MT'] = '3'
                    ans['Msg'] = '39383736353433323130'
                    answers.append(ans.pack())
                # Next transaction.
                strn += 1
            for msg in answers:
                # Send back all the messages produced.
                s = sum(map(ord,list(msg)))%256
                self.wfile.write('\x02%s%02X\x03'%(msg,s))
                print '%s%02X'%(msg,s)

class Server(SocketServer.TCPServer):
    allow_reuse_address = True
    def handle_error(self, request, client_address):
        request.shutdown(2)
        raise
        sys.exit(0)

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print 'Usage: %s listen_ip listen_port' % sys.argv[0]
        sys.exit(0)
    server = Server((sys.argv[1],int(sys.argv[2])), EMIRequestHandler)
    server.serve_forever()
