#include "cemi_frame.h"
#include "bits.h"
#include "string.h"
#include <stdio.h>

/*
cEMI Frame Format

+---------+--------+--------+--------+--------+---------+---------+--------+---------+
    | Header  |  Msg   |Add.Info| Ctrl 1 | Ctrl 2 | Source  | Dest.   |  Data  |   APDU  |
    |         | Code   | Length |        |        | Address | Address | Length |         |
    +---------+--------+--------+--------+--------+---------+---------+--------+---------+
      6 bytes   1 byte   1 byte   1 byte   1 byte   2 bytes   2 bytes   1 byte   2 bytes
 
        Header          = See below the structure of a cEMI header
        Message Code    = See below. On Appendix A is the list of all existing EMI and cEMI codes
        Add.Info Length = 0x00 - no additional info
        Control Field 1 =
        Control Field 2 =
        Source Address  = 0x0000 - filled in by router/gateway with its source address which is
                          part of the KNX subnet
        Dest. Address   = KNX group or individual address (2 byte)
        Data Length     = Number of bytes of data in the APDU excluding the TPCI/APCI bits
        APDU            = Application Protocol Data Unit - the actual payload including transport
                          protocol control information (TPCI), application protocol control
                          information (APCI) and data passed as an argument from higher layers of
                          the KNX communication stack
 
Control Field 1
 
          Bit  |
         ------+---------------------------------------------------------------
           7   | Frame Type  - 0x0 for extended frame
               |               0x1 for standard frame
         ------+---------------------------------------------------------------
           6   | Reserved
               |
         ------+---------------------------------------------------------------
           5   | Repeat Flag - 0x0 repeat frame on medium in case of an error
               |               0x1 do not repeat
         ------+---------------------------------------------------------------
           4   | System Broadcast - 0x0 system broadcast
               |                    0x1 broadcast
         ------+---------------------------------------------------------------
           3   | Priority    - 0x0 system
               |               0x1 normal
         ------+               0x2 urgent
           2   |               0x3 low
               |
         ------+---------------------------------------------------------------
           1   | Acknowledge Request - 0x0 no ACK requested
               | (L_Data.req)          0x1 ACK requested
         ------+---------------------------------------------------------------
           0   | Confirm      - 0x0 no error
               | (L_Data.con) - 0x1 error
         ------+---------------------------------------------------------------
 
         Control Field 2
 
          Bit  |
         ------+---------------------------------------------------------------
           7   | Destination Address Type - 0x0 individual address
               |                          - 0x1 group address
         ------+---------------------------------------------------------------
          6-4  | Hop Count (0-7)
         ------+---------------------------------------------------------------
          3-0  | Extended Frame Format - 0x0 standard frame
         ------+---------------------------------------------------------------
*/ 

CemiFrame::CemiFrame(uint8_t* data, uint16_t length)
    : _npdu(data + data[1] + NPDU_LPDU_DIFF, *this), 
      _tpdu(data + data[1] + TPDU_LPDU_DIFF, *this), 
      _apdu(data + data[1] + APDU_LPDU_DIFF, *this)
{
    _data = data;
    _ctrl1 = data + data[1] + CEMI_HEADER_SIZE;
    _length = length;
}

CemiFrame::CemiFrame(uint8_t apduLength)
    : _data(buffer),
      _npdu(_data + NPDU_LPDU_DIFF, *this), 
      _tpdu(_data + TPDU_LPDU_DIFF, *this), 
      _apdu(_data + APDU_LPDU_DIFF, *this)
{
    _ctrl1 = _data + CEMI_HEADER_SIZE;
    _length = 0;

    memset(_data, 0, apduLength + APDU_LPDU_DIFF);
    _ctrl1[0] |= Broadcast;
    _npdu.octetCount(apduLength);
}

CemiFrame::CemiFrame(const CemiFrame & other)
    : _data(buffer),
      _npdu(_data + NPDU_LPDU_DIFF, *this),
      _tpdu(_data + TPDU_LPDU_DIFF, *this),
      _apdu(_data + APDU_LPDU_DIFF, *this)
{
    _ctrl1 = _data + CEMI_HEADER_SIZE; 
    _length = other._length;

    memcpy(_data, other._data, other.totalLenght());
}

CemiFrame& CemiFrame::operator=(CemiFrame other)
{
    _length = other._length;
    _data = buffer;
    _ctrl1 = _data + CEMI_HEADER_SIZE;
    memcpy(_data, other._data, other.totalLenght());
    _npdu._data = _data + NPDU_LPDU_DIFF;
    _tpdu._data = _data + TPDU_LPDU_DIFF;
    _apdu._data = _data + APDU_LPDU_DIFF;
    return *this;
}

MessageCode CemiFrame::messageCode() const
{
    return (MessageCode)_data[0];
}

void CemiFrame::messageCode(MessageCode msgCode)
{
    _data[0] = msgCode;
}

uint16_t CemiFrame::totalLenght() const
{
    uint16_t tmp = 
    _npdu.length() + NPDU_LPDU_DIFF;
    return tmp;
}

uint16_t CemiFrame::telegramLengthtTP() const
{
    if (frameType() == StandardFrame)
        return totalLenght() - 2; /*-AddInfo -MsgCode - only one CTRL + CRC, */
    else
        return totalLenght() - 1; /*-AddInfo -MsgCode + CRC, */
}

void CemiFrame::fillTelegramTP(uint8_t* data)
{
    uint16_t len = telegramLengthtTP();
    
    if (frameType() == StandardFrame)
    {
        uint8_t octet5 = (_ctrl1[1] & 0xF0) | (_ctrl1[6] & 0x0F);
        data[0] = _ctrl1[0]; //CTRL
        memcpy(data + 1, _ctrl1 + 2, 4); // SA, DA
        data[5] = octet5; // LEN; Hopcount, ..
        memcpy(data + 6, _ctrl1 + 7, len - 7); // APDU
    }
    else
    {
        memcpy(data, _ctrl1, len - 1);
    }
    data[len - 1] = calcCrcTP(data, len - 1);
}

#ifdef USE_RF

uint16_t CemiFrame::telegramLengthtRF() const
{
    return totalLenght() - 3;
}

void CemiFrame::fillTelegramRF(uint8_t* data)
{
    uint16_t len = telegramLengthtRF();

    // We prepare the actual KNX telegram for RF here only.
    // The packaging into blocks with CRC16 (Format based on FT3 Data Link Layer (IEC 870-5))
    // is done in the RF Data Link Layer code.
    // RF always uses the Extended Frame Format. However, the length field is missing (right before the APDU)
    // as there is already a length field at the beginning of the raw RF frame which is also used by the 
    // physical layer to control the HW packet engine of the transceiver.

    data[0] = _ctrl1[1] & 0x0F; // KNX CTRL field for RF (bits 3..0 EFF only), bits 7..4 are set to 0 for asynchronous RF frames
    memcpy(data + 1, _ctrl1 + 2, 4); // SA, DA
    data[5] = (_ctrl1[1] & 0xF0) | ((_rfLfn & 0x7) << 1) | ((_ctrl1[0] & 0x10) >> 4); // L/NPCI field: AT, Hopcount, LFN, AET
    memcpy(data + 6, _ctrl1 + 7, len - 6); // APDU

    //printHex("cEMI_fill: ", &data[0], len);
}
#endif
uint8_t* CemiFrame::data()
{
    return _data;
}

uint16_t CemiFrame::dataLength()
{
    return _length;
}

uint8_t CemiFrame::calcCrcTP(uint8_t * buffer, uint16_t len)
{
    uint8_t crc = 0xFF;
    
    for (uint16_t i = 0; i < len; i++)
        crc ^= buffer[i];
    
    return crc;
}

FrameFormat CemiFrame::frameType() const
{
    return (FrameFormat)(_ctrl1[0] & StandardFrame);
}

void CemiFrame::frameType(FrameFormat type)
{
    _ctrl1[0] &= ~StandardFrame;
    _ctrl1[0] |= type;
}

Repetition CemiFrame::repetition() const
{
    return (Repetition)(_ctrl1[0] & RepetitionAllowed);
}

void CemiFrame::repetition(Repetition rep)
{
    _ctrl1[0] &= ~RepetitionAllowed;
    _ctrl1[0] |= rep;
}

SystemBroadcast CemiFrame::systemBroadcast() const
{
    return (SystemBroadcast)(_ctrl1[0] & Broadcast);
}

void CemiFrame::systemBroadcast(SystemBroadcast value)
{
    _ctrl1[0] &= ~Broadcast;
    _ctrl1[0] |= value;
}

Priority CemiFrame::priority() const
{
    return (Priority)(_ctrl1[0] & LowPriority);
}

void CemiFrame::priority(Priority value)
{
    _ctrl1[0] &= ~LowPriority;
    _ctrl1[0] |= value;
}

AckType CemiFrame::ack() const
{
    return (AckType)(_ctrl1[0] & AckRequested);
}

void CemiFrame::ack(AckType value)
{
    _ctrl1[0] &= ~AckRequested;
    _ctrl1[0] |= value;
}

Confirm CemiFrame::confirm() const
{
    return (Confirm)(_ctrl1[0] & ConfirmError);
}

void CemiFrame::confirm(Confirm value)
{
    _ctrl1[0] &= ~ConfirmError;
    _ctrl1[0] |= value;
}

AddressType CemiFrame::addressType() const
{
    return (AddressType)(_ctrl1[1] & GroupAddress);
}

void CemiFrame::addressType(AddressType value)
{
    _ctrl1[1] &= ~GroupAddress;
    _ctrl1[1] |= value;
}

uint8_t CemiFrame::hopCount() const
{
    return ((_ctrl1[1] >> 4) & 0x7);
}

void CemiFrame::hopCount(uint8_t value)
{
    _ctrl1[1] &= ~(0x7 << 4);
    _ctrl1[1] |= ((value & 0x7) << 4);
}

uint16_t CemiFrame::sourceAddress() const
{
    uint16_t addr;
    popWord(addr, _ctrl1 + 2);
    return addr;
}

void CemiFrame::sourceAddress(uint16_t value)
{
    pushWord(value, _ctrl1 + 2);
}

uint16_t CemiFrame::destinationAddress() const
{
    uint16_t addr;
    popWord(addr, _ctrl1 + 4);
    return addr;
}

void CemiFrame::destinationAddress(uint16_t value)
{
    pushWord(value, _ctrl1 + 4);
}
#ifdef USE_RF
uint8_t* CemiFrame::rfSerialOrDoA() const
{
    return _rfSerialOrDoA;
}

void CemiFrame::rfSerialOrDoA(const uint8_t* rfSerialOrDoA)
{
    _rfSerialOrDoA = (uint8_t*)rfSerialOrDoA;
}

uint8_t CemiFrame::rfInfo() const
{
    return _rfInfo;
}

void CemiFrame::rfInfo(uint8_t rfInfo)
{
    _rfInfo = rfInfo;
}

uint8_t CemiFrame::rfLfn() const
{
    return _rfLfn;
}

void CemiFrame::rfLfn(uint8_t rfLfn)
{
    _rfLfn = rfLfn;
}
#endif
NPDU& CemiFrame::npdu()
{
    return _npdu;
}

TPDU& CemiFrame::tpdu()
{
    return _tpdu;
}

APDU& CemiFrame::apdu()
{
    return _apdu;
}

bool CemiFrame::valid() const
{
    uint8_t addInfoLen = _data[1];
    uint8_t apduLen = _data[_data[1] + NPDU_LPDU_DIFF];

    if (_length != 0 && _length != (addInfoLen + apduLen + NPDU_LPDU_DIFF + 2))
        return false;
    
    if ((_ctrl1[0] & 0x40) > 0 // Bit 6 has do be 0
        || (_ctrl1[1] & 0xF) > 0 // only standard or extended frames
        || _npdu.octetCount() == 0xFF // not allowed
        || (_npdu.octetCount() > 15 && frameType() == StandardFrame)
        )
        return false;

    return true;
}
