""" Protocollo di comunicazione per Allen Bradley.

    DF1 con controllo errore BCC su porta seriale.
    
    Esempio configurazione bus:

'1': {
    'name': 'ABbus',
    'driver': 'ab_df1',
    'timeout': 1.0,
    'retry': 3,
    'delay': 0.1,
    'settings': {'port': '/dev/ttyO1',
        'bauds': 19200,
        'bits': 8,
        'parity': 'N',
        'stopbit': 1
        }
    },

Esempio configurazione device:

    'name': 'SLC5',
        'devid': 1,
        'busid': 1,
        'groupid': 'demo',
        'proto_conf': { 'id': 1 }, 
        # Rawdata
        'rawdata': [
            {'priority': 5, 'address': 'B.3.0', 'size': 5 , 'tblock': 'w'},     # Ingressi digitali
            {'priority': 5, 'address': 'B.3.20', 'size': 10 , 'tblock': 'w'},   # Uscite digitali
            {'priority': 5, 'address': 'B.3.250', 'size': 4 , 'tblock': 'w'},   # Fasi ciclo, stato impianto
            {'priority': 5, 'address': 'B.9.0', 'size': 10 , 'tblock': 'w'},    # Allarmi
            {'priority': 5, 'address': 'B.10.0', 'size': 2 , 'tblock': 'w'},    # Warning
            {'priority': 5, 'address': 'N.7.0', 'size': 15 , 'tblock': 'w'},    # IO Analogici
            {'priority': 5, 'address': 'N.7.15', 'size': 15 , 'tblock': 'w'},   # IO Analogici
            {'priority': 10, 'address': 'N.7.50', 'size': 26 , 'tblock': 'w'},  # Set points
            {'priority': 10, 'address': 'N.7.76', 'size': 6 , 'tblock': 'w'},   # Set points
            {'priority': 10, 'address': 'N.7.100', 'size': 10 , 'tblock': 'w'}, # Set points
            {'priority': 5, 'address': 'N.7.150', 'size': 6 , 'tblock': 'w'},   # Comandi touch
            {'priority': 10, 'address': 'N.12.0', 'size': 26 , 'tblock': 'w'},   # Pid level
            {'priority': 10, 'address': 'N.12.30', 'size': 26 , 'tblock': 'w'},  # Pid FT
            {'priority': 10, 'address': 'N.12.60', 'size': 26 , 'tblock': 'w'},  # Pid PA 
            {'priority': 10, 'address': 'N.21.0', 'size': 26 , 'tblock': 'w'},  # View Pid
            {'priority': 10, 'address': 'N.21.30', 'size': 26 , 'tblock': 'w'},   # View Pid
            {'priority': 5, 'address': 'N.21.60', 'size': 26 , 'tblock': 'w'},   # Stato ciclo
            # only demo
            {'priority': 5, 'address': 'N.40.0', 'size': 26 , 'tblock': 'w'},   # Stato ciclo
            
        ],
        
        PVar e allarmi non differiscono da altri protocolli
        
"""


from zope.interface import implements
from twisted.python import components
import logging

import time

from twisted.internet import reactor
from twisted.internet.protocol import Protocol
from twisted.internet.defer import Deferred, inlineCallbacks, returnValue

from paytor.device.devices import Device
from paytor.protocol.decorator import timeout
from paytor.protocol.protocol import RSCommunicationServer, BasePProtocol, CommandError


log = logging.getLogger('pcore.protocol.df1')


class ExchangeError(Exception):
    """ Errore nel protocollo.
    """

class NotImplementedError(Exception):
    """ Comando non implementato.
    """

class EncodeError(Exception):
    """ Errore di codifica.
    """


class df1_protocol(BasePProtocol):

    binary = True

    # input - I
    # output - O
    # binary - B
    # timer - T
    # counter - C
    # integer - N
    # floating - F
    # status - S

    DataToCode = {'I' : 0x8C,
                  'O' : 0x8B,
                  'B' : 0x85,
                  'T' : 0x86,
                  'C' : 0x87,
                  'N' : 0x89,
                  'R' : '',
                  'F' : 0x8A,
                  'S' : 0x84}

    CodeToData = {0x8C: 'I',
                   0x8B: 'O',
                   0x85: 'B',
                   0x86: 'T',
                   0x87: 'C',
                   0x89: 'N',
                   0x8A: 'F',
                   0x84: 'S'}

    # Symbol DF1 Protocol
    ABSymbol = {'ack': '\x10\x06',
                'eot': '\x10\x04',
                'soh': '\x10\x01',
                'stx': '\x10\x02',
                'etx': '\x10\x03',
                'enq': '\x10\x05',
                'nak': '\x10\x15'}

    WAIT_ACK, WAIT_RECV = range(2)


    def newMessage(self):
        # Reset particolari del protocollo allen bradley
        self.state = self.WAIT_ACK

    def checkCode(self, frame):
        # Calcolo BCC Master
        # STN + DST + SRC + CMD + STS + TNS1 + TNS2 + DATA
        # Calcolo BCC Slave
        # DST + SRC + CMD + STS + TNS1 + TNS2 + DATA
        bcc = 0
        for i in frame:
            bcc += ord(i)
        bcc = chr(((0xFF - bcc) + 1) & 0xFF)
        return bcc

    def addescape(self, frame):
        #return frame with escape char - master request
        count = 0 # escape counter
        ret = frame[:2]
        if frame[2] == chr(0x10):
            ret = ret + chr(0x10) + chr(0x10)
        else:
            ret = frame[:3]
        return ret

    def delescape(self, frame):
        #return frame without escape char - slave data response
        escape = False
        out = ''
        for i in frame:
            if (i == '\x10') and (escape == False):
                    escape = True
            else:
                    escape = False
                    out += i
        return out

    def dataReceived(self, data):
        if not self.onRecv:
            # Probabilmente errore di trasmissione o bug
            log.info('%s: Not syncro recv message.' %self.factory.logprefix)
            return

        while data:
            if self.state == self.WAIT_ACK:
                # prima ricezione aspetta l'ack
                if data[:2] == self.ABSymbol['ack']:
                    # ack ricevuto aspetta il resto dei dati
                    self.buffer, data = data[:2], data[2:]
                    self.state = self.WAIT_RECV
                else:
                    # errore di ricezione e' capitato a Vado se ignorato va tutto
                    self.buffer, data = data[:2], data[2:]
                    self.state = self.WAIT_RECV
            elif self.state == self.WAIT_RECV:
                self.buffer += data
                # completa il messaggio
                st = self.buffer.rfind(self.ABSymbol['etx'])

                if (st > -1) and (st < (len(self.buffer) - 2)):
                    count = 0
                    for i in range(st - 1, 0, -1):
                        if (self.buffer[i] == '\x10'):
                            count += 1
                        else:
                            break
                    if (count % 2):
                        #non e' ancora il carattere di fine frame, ma sono tutti escape
                        break
                    #se trova carattere di fine frame invia tutto al main e manda l'ack al device
                    self.buffer = self.buffer[:st + 4]
                    self.send(self.ABSymbol['ack'])
                    self.recv(self.buffer)
                break

    def read(self, uuid, address, tblock, size, counter):
        tns = counter


        devices = self.factory.devices

        devid = devices.getProtoConf(uuid)['id']

        if tblock in ('i', 'u', 'w'):
            nblock = 1
        elif tblock == 'b':
            nblock = 0
        elif tblock in ('f', 'd'):
            nblock = 2

        if nblock != 1:
            raise EncodeError("I can read only word data.")

        datatype = address.split(".")
        datatype, numberfile, laddress = self.DataToCode[datatype[0]], int(datatype[1]), int(datatype[2])

        # *2 perche' espresso in byte
        size = size * 2

        frame = ''

        # Three field address
        frame += chr(devid)     #DST
        if id == 0x10:
            frame += chr(0x10)  #DLE - Aggiunto escape
        frame += chr(0x00)      #SRC
        frame += chr(0x0F)      #CMD
        frame += chr(0x00)      #STS
        frame += chr(tns & 0xFF)   #TNS LSB
        if (tns & 0xFF) == 0x10:
            frame += chr(0x10)  #DLE - Aggiunto escape
        frame += chr((tns / 256) & 0xFF) #TNS MSB
        if ((tns / 256) & 0xFF) == 0x10:
            frame += chr(0x10)  #DLE - Aggiunto escape
        frame += chr(0xA2)      #FNC
        frame += chr(size)      #BYTE SIZE
        if size == 0x10:
            frame += chr(0x10)      #DLE - Aggiunto escape
        frame += chr(numberfile)    #FILE NO
        if numberfile == 0x10:
            frame += chr(0x10)  #DLE - Aggiunto escape
        frame += chr(datatype)       #FILE TYPE - BIT
        frame += chr(laddress)       #EL NUMBER (ADDRESS)
        if laddress == 0x10:
            frame += chr(0x10)  #DLE - Aggiunto escape
        frame += chr(0x00)       #SUB-EL NUMBER

        # -- end threefield address request

        # errorCode
        errorCode = self.checkCode(self.delescape(frame))

        return self.ABSymbol['stx'] + frame + self.ABSymbol['etx'] + errorCode

    def write(self, uuid, address, tblock, value, counter):
        tns = counter

        devices = self.factory.devices

        devid = devices.getProtoConf(uuid)['id']

        if tblock in ('i', 'u', 'w'):
            nblock = 1
        elif tblock == 'b':
            # write bit

            datatype = address.split(".")
            datatype, numberfile, laddress, bit = self.DataToCode[datatype[0]], int(datatype[1]), int(datatype[2]), int(datatype[3])
            size = 2
            frame = ''

            value = bool(value)

            # toglie il bit dall'indirizzo
            laddress = ".".join(address.split(".")[:-1])
            # recupera il valore attuale dell'area di memoria
            oldvalue = devices.getData(uuid, 'data', laddress, 'w')

            # Non avendo la scrittura a bit diretta calcolo la word equivalente per settare o resettare un bit
            if value:
                if not (oldvalue & 2**bit):
                    value = oldvalue + 2 ** bit
            else:
                if (oldvalue & 2**bit):
                    value = oldvalue - 2 ** bit

            tblock = 'w'
            nblock = 1

        elif tblock in ('f', 'd'):
            nblock = 2

        if tblock in ('u', 'i', 'w'):
            datatype = address.split(".")
            datatype, numberfile, laddress = self.DataToCode[datatype[0]], int(datatype[1]), int(datatype[2])
            size = nblock * 2
            frame = ''
            value = int(value)

            # write word
            # Three field address
            frame += chr(devid)     #DST
            if id == 0x10:
                frame += chr(0x10)  #DLE - Aggiunto escape
            frame += chr(0x00)      #SRC
            frame += chr(0x0F)      #CMD
            frame += chr(0x00)      #STS
            frame += chr(tns & 0xFF)   #TNS LSB
            if (tns & 0xFF) == 0x10:
                frame += chr(0x10)  #DLE - Aggiunto escape
            frame += chr((tns / 256) & 0xFF) #TNS MSB
            if ((tns / 256) & 0xFF) == 0x10:
                frame += chr(0x10)  #DLE - Aggiunto escape
            frame += chr(0xAA)      #FNC
            frame += chr(size)      #BYTE SIZE
            if size == 0x10:
                frame += chr(0x10)      #DLE - Aggiunto escape
            frame += chr(numberfile)    #FILE NO
            if numberfile == 0x10:
                frame += chr(0x10)      #DLE - Aggiunto escape
            frame += chr(datatype)      #FILE TYPE - BIT
            frame += chr(laddress)       #EL NUMBER (ADDRESS)
            if laddress == 0x10:
                frame += chr(0x10)      #DLE - Aggiunto escape
            frame += chr(0x00)          #SUB-EL NUMBER

            frame += chr(value & 0xFF)                      #DATA WRITE LSB
            if (value & 0xFF) == 0x10:
                frame += chr(0x10)      #DLE - Se l'indirizzo e' uguale a 0x10 deve essere aggiunto il carattere di escape
            frame += chr((value / 256) & 0xFF)              #DATA WRITE MSB
            if ((value / 256) & 0xFF) == 0x10:
                frame += chr(0x10)      #DLE - Se l'indirizzo e' uguale a 0x10 deve essere aggiunto il carattere di escape
            if size == 4:
                frame += chr((value / 0x10000) & 0xFF)      #DATA WRITE LMSB
                if ((value / 0x10000) & 0xFF) == 0x10:
                    frame += chr(0x10)  #DLE - Se l'indirizzo e' uguale a 0x10 deve essere aggiunto il carattere di escape
                frame += chr((value / 0x1000000) & 0xFF)    #DATA WRITE MMSB
                if ((value / 0x1000000) & 0xFF) == 0x10:
                    frame += chr(0x10)  #DLE - Se l'indirizzo e' uguale a 0x10 deve essere aggiunto il carattere di escape

            # errorCode
            errorCode = self.checkCode(self.delescape(frame))

            return self.ABSymbol['stx'] + frame + self.ABSymbol['etx'] + errorCode

    def encode(self, command, **kwargs):
        """ Parametri specifici del protocollo Allen Bradley

        @type uuid: int
        @param uuid: id univoco del dispositivo

        """

        if command == 'r':
            return self.read(**kwargs)

        elif command == 'w':
            return self.write(**kwargs)

        else:
            raise NotImplementedError("Command %s not implemented." % command)

    def decode(self, recv, message):
        """ Decodifico il frame ritornato dal device e standardizzo la risposta.
        """

        uuid = message['send']['uuid']
        address = message['send']['address']
        tblock = message['send']['tblock']

        command = message['msg']['command']
        sndraw = message['msg']['sndraw']

        if command == 'a':
            retstatus = True
            retlog = 'raw'
            ret=recv
            return retstatus, retlog, ret
        elif command not in ('r', 'w'):
            raise CommandError('Command %s not exist' %command)

        devices = self.factory.devices

        devid = devices.getDevId(uuid)

        if len(recv) == 2:
            # Ricezione di uno dei simboli di controllo
            # Scartare il frame e continuare con la comunicazione
            if (recv == self.ABSymbol['ack']):
                retstatus, retlog, ret =  False, 'ack', None
            elif (recv == self.ABSymbol['nak']):
                retstatus, retlog, ret =  False, 'nak', None
            elif (recv == self.ABSymbol['enq']):
                retstatus, retlog, ret =  False, 'enq', None
            else:
                retstatus, retlog, ret =  False, 'unknow', None
                return retstatus, retlog, ret
        else:
            dstRecv = ''
            srcRecv = ''
            cmdRecv = '\x4f'
            stsRecv = '\x00'
            tnsRecv = ''
            errRecv = ''
            dataRecv = ''

            # Frame Recv
            # if first 2 char is ack and rest is answer frame
            #if (recv[:2] == self.__symbolAB['ack']) or (recv[:2] == '\x91\xf0') or (recv[:2] == '\x64\xfc'):
            recv = recv[2:]

            # stx
            # errorCode disattivato
            errRecv = self.checkCode(self.delescape(recv[2:-3]))

            if recv[:2] != self.ABSymbol['stx']:
                retstatus, retlog, ret =  False, 'Recv Header unknown stx', None
                return retstatus, retlog, ret

            recv = recv[2:]

            # src
            if recv[0] == '\x10':
                srcRecv = recv[1]
                recv = recv[2:]
            else:
                srcRecv = recv[0]
                recv = recv[1:]

            # dst
            if recv[0] == '\x10':
                dstRecv = recv[1]
                recv = recv[2:]
            else:
                dstRecv = recv[0]
                recv = recv[1:]

            # cmd
            if recv[0] != '\x4f':
                retstatus, retlog, ret =  False, 'Recv Command unknown', None
                return retstatus, retlog, ret

            cmdRecv = '\x4f'
            recv = recv[1:]

            # sts
            if recv[0] != '\x00':
                retstatus, retlog, ret =  False, 'STS Error', None
                return retstatus, retlog, ret

            stsRecv = '\x00'
            recv = recv[1:]

            # tns Recv
            if recv[0] == '\x10':
                tnsRecv = tnsRecv + recv[1]
                recv = recv[2:]
            else:
                tnsRecv = tnsRecv + recv[0]
                recv = recv[1:]
            if recv[0] == '\x10':
                tnsRecv = tnsRecv + recv[1]
                recv = recv[2:]
            else:
                tnsRecv = tnsRecv + recv[0]
                recv = recv[1:]

            # etx
            if recv[-3:-1] != self.ABSymbol['etx']:
                retstatus, retlog, ret =  False, 'Recv Footer unknown etx', None
                return retstatus, retlog, ret

            dataRecv = recv[:-3]

            # Frame Send
            dstSend = ''
            srcSend = ''
            cmdSend = '\x0f'
            stsSend = '\x00'
            tnsSend = ''
            fncSend = ''

            frameSend = sndraw

            # stx or ack or enq or nak
            frameSend = frameSend[2:]

            # dst
            if frameSend[0] == '\x10':
                dstSend = frameSend[1]
                frameSend = frameSend[2:]
            else:
                dstSend = frameSend[0]
                frameSend = frameSend[1:]
            # src
            if frameSend[0] == '\x10':
                srcSend = frameSend[1]
                frameSend = frameSend[2:]
            else:
                srcSend = frameSend[0]
                frameSend = frameSend[1:]

            # cmd
            cmdSend = '\x0f'
            frameSend = frameSend[1:]
            # sts
            stsSend = '\x00'
            frameSend = frameSend[1:]
            # tns Send
            if frameSend[0] == '\x10':
                tnsSend = tnsSend + frameSend[1]
                frameSend = frameSend[2:]
            else:
                tnsSend = tnsSend + frameSend[0]
                frameSend = frameSend[1:]
            if frameSend[0] == '\x10':
                tnsSend = tnsSend + frameSend[1]
                frameSend = frameSend[2:]
            else:
                tnsSend = tnsSend + frameSend[0]
                frameSend = frameSend[1:]
            # fnc
            fncSend = frameSend[0]
            frameSend = frameSend[1:]
            # toggle etx and bcc
            frameSend = frameSend[:-4]
            # Toggle escape from sendFrame
            frameSend = self.delescape(frameSend)
            # Byte Size
            byteSize = frameSend[0]
            # File No
            numberfile = str(ord(frameSend[1]))
            # File Type
            datatype = self.CodeToData[ord(frameSend[2])]
            # Element number
            address = ord(frameSend[3])


            # Sub Element number 0 - frameSend[4]

            # Check code control
            if errRecv != recv[-1]:
                retstatus, retlog, ret =  False, 'Recv Frame BCC Error', None
                return retstatus, retlog, ret
            if tnsSend != tnsRecv:
                retstatus, retlog, ret =  False, 'TNS Error Resync', None
                return retstatus, retlog, ret

            # Frame ok
            if len(dataRecv) == 0:
                # Frame di scrittura
                retstatus, retlog, ret =  True, 'Write ok', None
            else:
                # Frame di lettura
                # LSB - MSB: Word composite
                retstatus, retlog, ret = True, 'Read ok', {}
                dataRecv = self.delescape(dataRecv)
                count = address
                for i in range(len(dataRecv)):
                    if not (i%2):
                        s = "%s.%s.%s" %(datatype, numberfile, count)
                        ret[s] = ord(dataRecv[i]) + (ord(dataRecv[i+1]) * 256)
                        count += 1

                devices.updateData(uuid, ret, message)

            return retstatus, retlog, ret



class df1_device(Device):
    """ Classe device per plc slc 5/0X
    
    proto_conf = {
        id: 1
    }
    """

    endian = 'l'
    sblock = 16
    
    
class df1_server(RSCommunicationServer):
    """ Classe per server di comunicazione specifico allen bradley
    """

    protocol = df1_protocol
    device = df1_device

