#include "MessageHelper.h"
#include "terminal_utils.h"
#include "aes128.h"

#include <QDateTime>
#include <QDebug>

#define IUC_ASYNCHPOS_COINCOIDE_H               0x09
#define IUC_ASYNCHPOS_COINCOIDE_L               0x78
#define IUC_ASYNCHPOS_MAX_ARRAY_SIZE	        1024
#define IUC_ASYNCHPOS_MAX_TX_PACKET_SIZE	    300
#define IUC_ASYNCHPOS_MAX_RX_PACKET_SIZE	    10000   // 17000
#define IUC_ASYNCHPOS_MIN_PACKET_SIZE	        16
#define IUC_ASYNCHPOS_MIN_BASE_DATA_SIZE        32
#define IUC_ASYNCHPOS_MIN_BASE_BYTE_DATA_SIZE   16
#define IUC_ASYNCHPOS_POLYNOME	                0xedb88320	// 0x04C11DB7
#define IUC_ASYNCHPOS_POLYNOME_INITIAL	        0		    // 0xFFFFFFFF
#define IUC_ASYNCHPOS_PRINTTIMOUT               1000

#define PACKET_ID_SIZE      8
#define MAX_POSID_LENGTH    255

#define STX     ((char)0x01)
#define ETX1    ((char)0x02)
#define ETX2    ((char)0x03)
#define EOT     ((char)0x04)
#define ENQ     ((char)0x05)
#define ACK1    ((char)0x06)
#define ACK2    ((char)0x07)
#define DLE     ((char)0x10)
#define NAK     ((char)0x15)


#define DBG_HEADER "(" << __func__ << ":" << __LINE__ << ")"

#define DBG_EMERGENCY   (0) // System is unusable
#define DBG_ALERT       (1) // Action must be taken immediately
#define DBG_CRITICAL    (2) // Critical conditions
#define DBG_ERROR       (3) // Error conditions
#define DBG_WARNING     (4) // Warning conditions
#define DBG_NOTICE      (5) // Normal but significant conditions
                            // Conditions that are not error conditions, but that may require special handling
#define DBG_INFORMATION (6) // Informational messages
                            // Confirmation that the program is working as expected
#define DBG_DEBUG       (7) // Debug-level messages
                            // Messages that contain information normally of use only when debugging a program

static int DBG_LEVEL = DBG_INFORMATION;
//static int DBG_LEVEL = DBG_DEBUG;

struct MessageHeader {
    uint8_t packetType;
    uint8_t packetID[PACKET_ID_SIZE];
    uint8_t POSIDLength;
    uint8_t POSID[MAX_POSID_LENGTH];
};


MessageHelper::AsynchBillData MessageHelper::m_asyncBillData;
MessageHelper::AuthorizationResult MessageHelper::m_authorizationResult;

MessageHelper::MessageHelper(QString const &posID, QString const &apak)
  : m_posID(posID.toUtf8().constData())
  , m_posIDLength(m_posID.size())
  , m_messageHeaderPrefix(1 + PACKET_ID_SIZE + 1, 0x00)
  , m_rawPacket(IUC_ASYNCHPOS_MAX_TX_PACKET_SIZE, 0x00) {
    m_messageHeaderPrefix[9] = (uint8_t)m_posID.size();

    for (int p = 0; p < apak.size(); p+=2) {
        uint8_t n = strtoul(apak.mid(p, 2).toStdString().c_str(), nullptr, 16);
        m_apak.push_back(n);
    }

    if (DBG_LEVEL >= DBG_DEBUG) {
        qCritical() << DBG_HEADER << apak << m_apak.toHex(':');
        qCritical() << DBG_HEADER << m_posID.toHex(':');
        qCritical() << DBG_HEADER << m_messageHeaderPrefix.toHex(':');
    }
}

MessageHelper::MessageHelper(QByteArray const &posID, QString const &apak)
  : m_posID(posID)
  , m_posIDLength(m_posID.size())
  , m_messageHeaderPrefix(1 + PACKET_ID_SIZE + 1, 0x00)
  , m_rawPacket(IUC_ASYNCHPOS_MAX_TX_PACKET_SIZE, 0x00) {
    m_messageHeaderPrefix[9] = (uint8_t)m_posID.size();

    for (int p = 0; p < apak.size(); p+=2) {
        uint8_t n = strtoul(apak.mid(p, 2).toStdString().c_str(), nullptr, 16);
        m_apak.push_back(n);
    }

    if (DBG_LEVEL >= DBG_DEBUG) {
        qCritical() << DBG_HEADER << apak << m_apak.toHex(':');
        qCritical() << DBG_HEADER << m_posID.toHex(':');
        qCritical() << DBG_HEADER << m_messageHeaderPrefix.toHex(':');
    }
}

MessageHelper::~MessageHelper() {

}

void MessageHelper::handleMessage(char const *pData) {

#if 0
    //unsigned char marker = pData[0];
//	unsigned int tagNameLength = pData[1];
    unsigned char tagName[IUC_ASYNCHPOS_MIN_BASE_DATA_SIZE];
//	unsigned int curElementLength = 0;
    //unsigned char curElement[IUC_ASYNCHPOS_MIN_BASE_DATA_SIZE];
    //unsigned char receiptData[IUC_ASYNCHPOS_MAX_ARRAY_SIZE + 1];
    unsigned char rxBuf[20];
//	unsigned char flags[129];
//	unsigned char docNr[32];
    //unsigned char messageType = pData[1];

    unsigned int uitmp = 0;
    unsigned int uitmp2 = 0;

    iuc_asynchpos_sub_initArray(tagName, IUC_ASYNCHPOS_MIN_BASE_DATA_SIZE);
    //iuc_asynchpos_sub_initArray(curElement, IUC_ASYNCHPOS_MIN_BASE_DATA_SIZE);

    uitmp = biox_FindStrInBufferInt("LoginResult",pData) | biox_FindStrInBuffer("LOGINRESULT",pData);
    if (uitmp) {
        asynchState = 0x02;	//eingeloggt
        /*rxBuf[0] = 0x45;
        rxBuf[1] = 0x00;
        rxBuf[2] = 0x00;
        vmc_SendWithBuffer(rxBuf,40,0x69,0);*/
    }

    uitmp = biox_FindStrInBufferInt("AuthorizationStarted",pData) | biox_FindStrInBuffer("AUTHORIZATIONSTARTED",pData);
    if (uitmp) {
        iuc_asynchpos_sub_initArray(rxBuf,5);
        rxBuf[0] = 0x53;
        rxBuf[1] = 0x00;
        rxBuf[2] = 0x00;
        vmc_SendWithBuffer(rxBuf,5,0x69,0);
    }

    uitmp = biox_FindStrInBufferInt("AuthorizationResult",pData) | biox_FindStrInBuffer("AUTHORIZATIONRESULT",pData);
    if (uitmp) {
        asynchState = 0x03; //successfully authorized
        iuc_asynchpos_sub_clear_message(0x00);
        uitmp = biox_FindStrInBufferInt("ID",pData);
        biox_CopyBlock(pData, uitmp + 2, asynchBill.id, 0, pData[uitmp + 1]);
        uitmp = biox_FindStrInBufferInt("DocNr",pData) | biox_FindStrInBuffer("DOCNR",pData);
        biox_CopyBlock(pData, uitmp + 2, asynchBill.docNr, 0, pData[uitmp + 1]);
        //uitmp = biox_FindStrInBuffer("Result",pData) | biox_FindStrInBuffer("RESULT",pData);
        //biox_CopyBlock(pData, uitmp + 2, asynchBill.result, 0, pData[uitmp + 1]);

        brd_SendDiagStr("->IUC ASYNCHPOS message ID: ",0);
        brd_SendDiagBlock(asynchBill.id,1,36);

        /*if (iuc_asynch_PrintControl) {
            brd_SendDiagStr("->IUC ASYNCHPOS print data send AUTH: ",1);
            vmc_SendWithBuffer(receiptData,IUC_ASYNCHPOS_MAX_ARRAY_SIZE,0x69,0);
            iuc_asynch_PrintControl = 0x00;
            //biox_delay_ms(750);
        }   else {
            brd_SendDiagStr("->IUC ASYNCHPOS sending authorization: ",1);
            iuc_asynch_PrintControl = 0x01;
        }*/

        uitmp = biox_FindStrInBufferInt("ERROR",pData);
        if (!uitmp/*biox_FindStrInBuffer("OK",pData) | biox_FindStrInBuffer("Approved",pData) | biox_FindStrInBuffer("APPROVED",asynchBill.result)*/) {
            iuc_asynch_PrintControl |= 0xF0;
            /*uitmp = biox_FindStrInBufferInt("Amount",pData) | biox_FindStrInBufferInt("AMOUNT",pData);
            //if (pData[uitmp + 1] <= 10) {
                biox_CopyBlock(pData, uitmp + 2, asynchBill.amount, 0, pData[uitmp + 1]);
            //} else {
                //biox_CopyBlock(pData, uitmp + 2, asynchBill.amount, 0, 10);
            //}*/

            //uitmp = biox_FindStrInBuffer("Token",pData);
            //biox_CopyBlock(pData, uitmp + 2, asynchBill.token, 0, pData[uitmp + 1]);
            /*uitmp = biox_FindStrInBufferInt("Authcode",pData) | biox_FindStrInBufferInt("AUTHCODE",pData);
            biox_CopyBlock(pData, uitmp + 2, asynchBill.authCode, 0, pData[uitmp + 1]);
            uitmp = biox_FindStrInBufferInt("RRN",pData);
            biox_CopyBlock(pData, uitmp + 2, asynchBill.rrn, 0, pData[uitmp + 1]);
            uitmp = biox_FindStrInBufferInt("STAN",pData);
            biox_CopyBlock(pData, uitmp + 2, asynchBill.stan, 0, pData[uitmp + 1]);
            uitmp = biox_FindStrInBufferInt("CardType",pData) | biox_FindStrInBufferInt("CARDTYPE",pData);
            biox_CopyBlock(pData, uitmp + 2, asynchBill.cardtype, 0, pData[uitmp + 1]);*/
            asynchState = 0x04;

            brd_SendDiagStr("->IUC ASYNCHPOS authorization confirmed.",1);

            /*iuc_asynchpos_sub_initArray(rxBuf,20);
            rxBuf[0] = 0x45;
            rxBuf[1] = 0x00;
            rxBuf[2] = 0x00;
            vmc_SendWithBuffer(rxBuf,20,0x69,0);*/

            //iuc_asynchpos_resetBuffers(0x01);
            //iuc_asynchpos_command_close_Document();
        } else {
            iuc_asynch_PrintControl |= 0xF0;
            //uitmp = biox_FindStrInBufferInt("ErrCode",pData) | biox_FindStrInBufferInt("ERRCODE",pData);
            //biox_CopyBlock(pData, uitmp + 2, asynchBill.errCode, 0, pData[uitmp + 1]);

            brd_SendDiagStr("->IUC ASYNCHPOS authorization failed.",1);

            /*iuc_asynchpos_sub_initArray(rxBuf,20);
            rxBuf[0] = 0x45;
            rxBuf[1] = 0xFF;
            biox_CopyBlock(pData, uitmp + 2, rxBuf, 2, pData[uitmp + 1]);
            vmc_SendWithBuffer(rxBuf,20,0x69,0);*/
            iuc_asynch_keepAlive = 0x00;
            //VendRequest=0;
        }
    }

    uitmp = biox_FindStrInBufferInt("PrintReceipt",pData) | biox_FindStrInBufferInt("PRINTRECEIPT",pData);
    if (uitmp) {
        asynchState = 0x03; //Customer receipt recieved
        //iuc_asynchpos_sub_initArray(flags,129);
        //uitmp = biox_FindStrInBufferInt("Flag",pData) | biox_FindStrInBufferInt("FLAG",pData) | biox_FindStrInBufferInt("Flags",pData) | biox_FindStrInBufferInt("FLAGS",pData);
        /*if (uitmp) {
            biox_CopyBlock(pData, uitmp + 2, flags, 0, pData[uitmp + 1]);
            uitmp = biox_FindStrInBufferInt("CD",flags) | biox_FindStrInBufferInt("LR",flags);
        }*/

        /*iuc_asynchpos_sub_clear_message(0x00);
        uitmp = biox_FindStrInBufferInt("ID",pData);
        biox_CopyBlock(pData, uitmp + 2, asynchBill.id, 0, pData[uitmp + 1]);
        uitmp = biox_FindStrInBufferInt("DocNr",pData) | biox_FindStrInBuffer("DOCNR",pData);
        biox_CopyBlock(pData, uitmp + 2, asynchBill.docNr, 0, pData[uitmp + 1]);*/
        iuc_asynchpos_sub_initArray(asynchBill.printId,129);
        uitmp = biox_FindStrInBufferInt("ID",pData);
        biox_CopyBlock(pData, uitmp + 2, asynchBill.printId, 0, pData[uitmp + 1]);

        //if(asynchState == 0x02/* && uitmp biox_FindStrInBufferInt("CD",flags) || biox_FindStrInBufferInt("LR",flags)*/) {
        if((!iuc_asynch_PRNrecieved) && (biox_FindStrInBufferInt("CD",pData) || biox_FindStrInBufferInt("LR",pData)) )	{
            iuc_asynch_PRNrecieved = 0x01;
            iuc_asynchpos_sub_initArray(receiptData,IUC_ASYNCHPOS_MAX_ARRAY_SIZE + 1);
            uitmp = /*biox_FindStrInBuffer("ReceiptText",pData) | */biox_FindStrInBufferInt("RECEIPTTEXT",pData);
            uitmp2 = (pData[uitmp] * 256) + pData[uitmp + 1];

            //if (uitmp2 <= IUC_ASYNCHPOS_MAX_ARRAY_SIZE) {
            if (uitmp2 > IUC_ASYNCHPOS_MAX_ARRAY_SIZE) {
                uitmp2 = IUC_ASYNCHPOS_MAX_ARRAY_SIZE;
                brd_SendDiagStr("->IUC ASYNCHPOS receipt: ERROR. Receipt too large! Cutting off.",1);
                /*receiptData[0] = 0x50;
                biox_CopyBlock(pData, uitmp + 2, receiptData, 1, uitmp2/*IUC_ASYNCHPOS_MAX_ARRAY_SIZE*);
                //uitmp += IUC_ASYNCHPOS_MAX_ARRAY_SIZE;
                //uitmp2 -= IUC_ASYNCHPOS_MAX_ARRAY_SIZE;

                brd_SendDiagStr("->IUC ASYNCHPOS receipt: ",0);
                brd_SendDiagBlock(receiptData,1,IUC_ASYNCHPOS_MAX_ARRAY_SIZE);
                iuc_asynch_PrintControl |= 0x0F;*/

                /*if (iuc_asynch_PrintControl) {
                    brd_SendDiagStr("->IUC ASYNCHPOS print data send: ",1);
                    vmc_SendWithBuffer(receiptData,IUC_ASYNCHPOS_MAX_ARRAY_SIZE,0x69,0);
                    iuc_asynch_PrintControl = 0x00;
                } else {
                    brd_SendDiagStr("->IUC ASYNCHPOS print data stored: ",1);
                    iuc_asynch_PrintControl = 0x01;
                }*/
                //biox_delay_ms(750);

                //iuc_asynchpos_resetBuffers(0x01);
                //iuc_asynchpos_command_close_Document();

                //iuc_asynchpos_resetBuffers(0x02);
                //iuc_asynchpos_command_print_Result(0x01);
            }

            receiptData[0] = 0x50;
            biox_CopyBlock(pData, uitmp + 2, receiptData, 1, uitmp2/*IUC_ASYNCHPOS_MAX_ARRAY_SIZE*/);

            brd_SendDiagStr("->IUC ASYNCHPOS receipt: ",0);
            brd_SendDiagBlock(receiptData,1,IUC_ASYNCHPOS_MAX_ARRAY_SIZE);
            iuc_asynch_PrintControl |= 0x0F;

            /* else {
                //receiptData[0] = 0x50;
                //iuc_asynchpos_sub_initZero(receiptData,1);
                brd_SendDiagStr("->IUC ASYNCHPOS receipt: ERROR. Receipt too large!",1);
                iuc_asynch_PrintControl |= 0x0E;
            } */
            /* else {
                //iuc_asynchpos_resetBuffers(0x02);
                iuc_asynchpos_command_print_Result(0x00);
            }*/
        }

        //++iuc_print_counter;

        //if(asynchState == 0x04 && iuc_asynch_PrintControl == 0) {
            //iuc_asynchpos_resetBuffers(0x01);
            //iuc_asynchpos_resetBuffers(0x00);
            //iuc_asynchpos_command_print_Result(0x01);
        /*} /else {
            //iuc_asynchpos_resetBuffers(0x02);
            iuc_asynchpos_command_print_Result(0x01);
        }*/
            //iuc_asynchpos_command_print_Result(0x01);

        /*while (uitmp2 > IUC_ASYNCHPOS_MAX_ARRAY_SIZE) {
            biox_CopyBlock(pData, uitmp + 2, receiptData, 0, IUC_ASYNCHPOS_MAX_ARRAY_SIZE);
            uitmp += IUC_ASYNCHPOS_MAX_ARRAY_SIZE;
            uitmp2 -= IUC_ASYNCHPOS_MAX_ARRAY_SIZE;
            vmc_SendWithBuffer(receiptData,IUC_ASYNCHPOS_MAX_ARRAY_SIZE,0x69,0);
        }

        //Rest des Packets
        biox_CopyBlock(pData, uitmp + 2, receiptData, 0, uitmp2);
        vmc_SendWithBuffer(receiptData,uitmp2,0x69,0);

        //iuc_asynchpos_resetBuffers(0x02);
        iuc_asynchpos_command_print_Result(0x01);*/
    }

    uitmp = biox_FindStrInBufferInt("VoidResult",pData) | biox_FindStrInBufferInt("VOIDRESULT",pData);
    if (uitmp) {
        asynchState = 0x01; //There was a cancel. Relogin and try again.
        uitmp = biox_FindStrInBufferInt("ERROR",pData);
        if (uitmp) {
            rxBuf[0] = 0x45;
            rxBuf[1] = 0x56;
            rxBuf[2] = 0x45;
            vmc_SendWithBuffer(rxBuf,3,0x69,0);
        }

        uitmp = biox_FindStrInBufferInt("REFUND",pData);
        if (uitmp) {
            rxBuf[0] = 0x45;
            rxBuf[1] = 0x56;
            rxBuf[2] = 0x52;
            vmc_SendWithBuffer(rxBuf,3,0x69,0);
            //TODO refund bill here. Should not trigger, but it might.
        }
    }

    uitmp = biox_FindStrInBufferInt("DocClosed",pData);
    if (uitmp) {
        asynchState = 0x01; //Transaction successful

        //if (VendRequest)
            GWstate.VendRequest=0;
    }
#endif
}

void MessageHelper::handleCommand(AsyncPosCommand command, char status) {
    //r - registration, a - authorization, c - cancel, s - storno, k - kassenschnitt

#if 0
    UCHAR	tempBuf[3];
    UCHAR	rxBuf[8];
//	UINT	uitemp = 0;
UINT uitmp= 700;

    tempBuf[0] = 0x00;
    tempBuf[1] = 0x00;
    tempBuf[2] = 0x00;

    timeHoldISMAS = GWglobTime.SecOfDay;

    iuc_asynchpos_sub_initArray(rxBuf,8);
    //iuc_asynchpos_resetBuffers(0x00);

    switch (command) {
        case 0x72:	//registration
                    //iuc_asynchpos_init();
                    asynchState = 0x01;
                    iuc_asynchpos_command_Login();
                    GWstate.VendRequest=1;
                    break;
        case 0x61:	//authorisation
                    iuc_asynch_keepAlive = 0x01;
                    iuc_asynch_PrintControl = 0;
                    iuc_asynchpos_sub_clear_message(0x01);
                    //VendRequest=1;
                    iuc_asynchpos_resetBuffers(0x00);
                    ///#ifdef IUC_ASYCHNPOS_TESTMODE
                    //iuc_asynchpos_command_authorize(uitmp);
                    //#else
                    iuc_asynchpos_command_authorize(Vend.Amount);
                    //#endif
                    break;
        case 0x63:	//cancel
        case 0x73:	//storno
                    /*if (asynchState <= 0x02 != 0) {	//Authorization result recieved?
                        iuc_asynchpos_command_cancel_authorize();
                    } else {*/
                        iuc_asynchpos_command_close_Document(0x01);
                    //}
                    iuc_asynch_keepAlive = 0x00;
                    //VendRequest=0;
                    break;
        case 0x6B:	//kassenschnitt
                    iuc_asynchpos_command_Logout();
                    break;
        case 0x62:	//get last bill
                    //iuc_zvt_getLastBill(tempBuf);
                    break;
        case 0x01:	iuc_asynchpos_command_close_Document(0x00);
                    iuc_asynch_keepAlive = 0x00;
                    //VendRequest=0;
                    break;
        case 0x70:	iuc_asynchpos_command_ping_terminal();
                    break;
        default:
            break;
    }
#endif
}

void MessageHelper::createRawPacket(PacketType packetType,
                                    QByteArray const &encryptedPacketID,
                                    QByteArray const &message) {
    if (createMessageHeaderPrefix(packetType, encryptedPacketID)) {
        QByteArray ba(m_messageHeaderPrefix);
        ba = ba.append(m_posID);
        ba = ba.append(message);

        uint16_t const size = ba.size();

        ba.push_front((char)size);
        ba.push_front((char)(size >> 8));

        m_rawPacket = ba;
    }
}

bool MessageHelper::setMessageHeaderPacketType(PacketType packetType) {

    switch (packetType) {
    case PacketType::POS_ECR:
    case PacketType::MESSAGE_RECEIVED_POSITIVE_ACK:
    case PacketType::MESSAGE_RECEIVED_NEGATIVE_ACK:
        m_messageHeaderPrefix[0] = (uint8_t)packetType;
        break;
    default:
        return false;
    }
    return true;
}

bool MessageHelper::createMessageHeaderPrefix(PacketType packetType, QByteArray const &encryptedPacketID) {
    if (encryptedPacketID.size() == PACKET_ID_SIZE) {
        if (setMessageHeaderPacketType(packetType)) {
            for (int i = 1; i <= 8; ++i) {
                m_messageHeaderPrefix[i] = (uint8_t)encryptedPacketID[i-1];
            }
            if (DBG_LEVEL >= DBG_DEBUG) {
                qCritical() << DBG_HEADER << m_messageHeaderPrefix.toHex(':');
            }
            return true;
        }
    }
    return false;
}


QByteArray const &MessageHelper::generateUniqueTransactionID(QString const &machineNr,
                                                             QString const &customerNr) {

    // TODO: wieder entfernen
    QDateTime dt(QDateTime::fromString("2024-06-18T12:00:00", Qt::ISODate));

    uint64_t const transID = MessageHelper::secsSinceJan2017(dt);

    m_uniqueTransactionID.clear();
    m_uniqueTransactionID = m_uniqueTransactionID.append(QByteArray(std::to_string(transID).c_str(), 9).rightJustified(10, '0'));
    m_uniqueTransactionID = m_uniqueTransactionID.append(QByteArray(machineNr.toStdString().c_str()).rightJustified(5, '0'));
    m_uniqueTransactionID = m_uniqueTransactionID.append(QByteArray(customerNr.toStdString().c_str()).rightJustified(4, '0'));

    return m_uniqueTransactionID;
}

// actual payment. amount to pay is known.
void MessageHelper::createAuthorizeMessage() {
    m_authorizeMessage.clear();

    m_authorizeMessage.push_back((char)0x89); // 9 in 0x89 is the size
    m_authorizeMessage = m_authorizeMessage.append(QByteArray("Authorize"));

    QString const &price= m_price.setNum(1000);

    m_authorizeMessage.push_back((char)0x06);
    m_authorizeMessage = m_authorizeMessage.append(QByteArray("Amount"));
    m_authorizeMessage.push_back((char)0x00);
    m_authorizeMessage.push_back((char)price.size());
    m_authorizeMessage = m_authorizeMessage.append(QByteArray(price.toStdString().c_str()));

    m_authorizeMessage.push_back((char)0x04);
    m_authorizeMessage = m_authorizeMessage.append(QByteArray("Cash"));
    m_authorizeMessage.push_back((char)0x00);
    m_authorizeMessage.push_back((char)0x01);
    m_authorizeMessage.push_back((char)0x30);

    m_authorizeMessage.push_back((char)0x08);
    m_authorizeMessage = m_authorizeMessage.append(QByteArray("Currency"));
    m_authorizeMessage.push_back((char)0x00);
    m_authorizeMessage.push_back((char)0x03);
    m_authorizeMessage = m_authorizeMessage.append(QByteArray("978"));

    m_authorizeMessage.push_back((char)0x05);
    m_authorizeMessage = m_authorizeMessage.append(QByteArray("DocNr"));
    m_authorizeMessage.push_back((char)0x00);
    m_authorizeMessage.push_back((char)0x13);
    m_authorizeMessage = m_authorizeMessage.append(generateUniqueTransactionID("1000", "100"));

    m_authorizeMessage.push_back((char)0x04);
    m_authorizeMessage = m_authorizeMessage.append(QByteArray("Time"));
    m_authorizeMessage.push_back((char)0x00);
    m_authorizeMessage.push_back((char)0x13);

    QDateTime current = QDateTime::currentDateTime();
    // TODO: wieder entfernen
    current.setTime(QTime(12, 0, 0));
    current.setDate(QDate(2024, 6, 18));

    QByteArray time(current.toString(Qt::ISODate).toStdString().c_str());
    time[10] = ' ';
    m_authorizeMessage = m_authorizeMessage.append(QByteArray(time));

    m_authorizeMessage.push_back((char)0x04);
    m_authorizeMessage = m_authorizeMessage.append(QByteArray("Lang"));
    m_authorizeMessage.push_back((char)0x00);
    m_authorizeMessage.push_back((char)0x02);
    m_authorizeMessage = m_authorizeMessage.append(QByteArray("lt"));

    m_authorizeMessage.push_back((char)0x00);
}

void MessageHelper::createCancelAuthorizeMessage() {
    m_cancelAuthorizeMessage.clear();

    m_cancelAuthorizeMessage.push_back((char)0x96); // 0x80 + 0x16
    m_cancelAuthorizeMessage = m_cancelAuthorizeMessage.append(QByteArray("AuthorizationCancelled"));

    m_cancelAuthorizeMessage.push_back((char)0x02);
    m_cancelAuthorizeMessage = m_authorizeMessage.append(QByteArray("ID"));

    m_cancelAuthorizeMessage.push_back((char)0x00);
    m_cancelAuthorizeMessage.push_back((char)QByteArray(m_asyncBillData.id).size());
    m_cancelAuthorizeMessage = m_cancelAuthorizeMessage.append(QByteArray(m_asyncBillData.id));

    m_cancelAuthorizeMessage.push_back((char)0x00);
}

void MessageHelper::createPingMessage() {
    m_pingMessage.clear();

    m_pingMessage.push_back((char)0x84); // 4 in 0x84 is the size
    m_pingMessage = m_pingMessage.append(QByteArray("Ping"));

    m_pingMessage.push_back((char)0x04);
    m_pingMessage = m_authorizeMessage.append(QByteArray("Time"));
    m_pingMessage.push_back((char)0x00);
    m_pingMessage.push_back((char)0x13);

    QDateTime current = QDateTime::currentDateTime();
    // TODO: wieder entfernen
    current.setTime(QTime(12, 0, 0));
    current.setDate(QDate(2024, 6, 18));

    QByteArray time(current.toString(Qt::ISODate).toStdString().c_str());
    time[10] = ' ';
    m_pingMessage = m_pingMessage.append(QByteArray(time));

    m_pingMessage.push_back((char)0x00);
}

void MessageHelper::createCloseDocumentMessage(bool storno) {
    m_closeDocumentMessage.clear();

    m_closeDocumentMessage.push_back((char)0x89); // 9 in 0x89 is the size
    m_closeDocumentMessage = m_closeDocumentMessage.append(QByteArray("DocClosed"));

    m_closeDocumentMessage.push_back((char)0x05);
    m_closeDocumentMessage = m_closeDocumentMessage.append(QByteArray("DocNr"));

    uint16_t const docNrSize = m_uniqueTransactionID.size();

    m_closeDocumentMessage.push_back((char)(docNrSize >> 8));
    m_closeDocumentMessage.push_back((char)(docNrSize));
    m_closeDocumentMessage = m_closeDocumentMessage.append(m_uniqueTransactionID);

    if (!storno) {
        m_closeDocumentMessage.push_back((char)0x06);
        m_closeDocumentMessage = m_closeDocumentMessage.append(QByteArray("AuthID"));

        QByteArray ba(m_authorizationResult.m_id.toStdString().c_str());
        uint16_t const authIdSize = ba.size();

        m_closeDocumentMessage.push_back((char)(authIdSize >> 8));
        m_closeDocumentMessage.push_back((char)(authIdSize));

        m_closeDocumentMessage = m_closeDocumentMessage.append(ba);
    }

    m_closeDocumentMessage.push_back((char)0x00);
}

void MessageHelper::createPrintResultMessage() {
    m_printResultMessage.clear();
}

uint32_t MessageHelper::secsSinceJan2017(QDateTime const &dt) {
    return QDateTime(QDateTime::fromString("2017-01-01T00:00:00", Qt::ISODate)).secsTo(dt);
}

void MessageHelper::createLoginMessage() {
    m_loginMessage.clear();

    m_loginMessage.push_back((char)0x85); // 5 in 0x85 is the size
    m_loginMessage = m_loginMessage.append(QByteArray("Login"));

    m_loginMessage.push_back((char)0x04);
    m_loginMessage = m_loginMessage.append(QByteArray("Time"));
    m_loginMessage.push_back((char)0x00);
    m_loginMessage.push_back((char)0x13);

    QDateTime current = QDateTime::currentDateTime();
    // TODO: wieder entfernen
    current.setTime(QTime(12, 0, 0));
    current.setDate(QDate(2024, 6, 18));

    QByteArray time(current.toString(Qt::ISODate).toStdString().c_str());
    time[10] = ' ';
    m_loginMessage = m_loginMessage.append(time);

    m_loginMessage.push_back((char)0x05);
    m_loginMessage = m_loginMessage.append(QByteArray("Flags"));
    m_loginMessage.push_back((char)0x00);
    m_loginMessage.push_back((char)0x06);
    m_loginMessage = m_loginMessage.append(QByteArray("AP3|LR"));

    m_loginMessage.push_back((char)0x00);

    if (DBG_LEVEL >= DBG_INFORMATION) {
        qCritical() << DBG_HEADER << "loginMessage" << m_loginMessage.toHex(':');
    }
}

void MessageHelper::createLogoutMessage() {
    m_logoutMessage.clear();

    m_logoutMessage.push_back((char)0x86); // 6 in 0x86 is the size
    m_logoutMessage = m_loginMessage.append(QByteArray("Logout"));

    m_loginMessage.push_back((char)0x04);
    m_loginMessage = m_loginMessage.append(QByteArray("Time"));
    m_loginMessage.push_back((char)0x00);
    m_loginMessage.push_back((char)0x13);

    QDateTime current = QDateTime::currentDateTime();
    // TODO: wieder entfernen
    current.setTime(QTime(12, 0, 0));
    current.setDate(QDate(2024, 6, 18));

    QByteArray time(current.toString(Qt::ISODate).toStdString().c_str());
    time[10] = ' ';
    m_logoutMessage = m_logoutMessage.append(time);

    m_loginMessage.push_back((char)0x00);

    if (DBG_LEVEL >= DBG_INFORMATION) {
        qCritical() << DBG_HEADER << "loginMessage" << m_logoutMessage.toHex(':');
    }
}

QByteArrayList const &MessageHelper::createMessageChunksToSend(AsyncPosCommand cmd, char etx) {
    QByteArray encryptedPacketID(QByteArray("\x01\x02\x03\x04\x05\x06\x07\x08"));

    m_messageChunkList.clear();

    switch (cmd) {
    case (int)MessageHelper::AsyncPosCommand::LOGIN:
        createLoginMessage();
        createRawPacket(PacketType::POS_ECR, encryptedPacketID, m_loginMessage);
        break;
    case (int)MessageHelper::AsyncPosCommand::LOGOUT:
        createLogoutMessage();
        createRawPacket(PacketType::POS_ECR, encryptedPacketID, m_logoutMessage);
        break;
    case (int)MessageHelper::AsyncPosCommand::AUTHORIZE:
        createAuthorizeMessage();
        createRawPacket(PacketType::POS_ECR, encryptedPacketID, m_authorizeMessage);
        break;
    case (int)MessageHelper::AsyncPosCommand::CLOSE_DOCUMENT:
        createCloseDocumentMessage(); // actung: hier default parameter
        createRawPacket(PacketType::POS_ECR, encryptedPacketID, m_closeDocumentMessage);
        break;
    default:;
    }

    if (DBG_LEVEL >= DBG_DEBUG) {
        qCritical() << DBG_HEADER << m_rawPacket.toHex(':');
    }

    QByteArray const &ba = m_rawPacket.mid(11);

    if (DBG_LEVEL >= DBG_DEBUG) {
        qCritical() << DBG_HEADER << ba.toHex(':');
    }

    // calculate crc32 on message starting from (including) POSID length
    uint32_t crc = TU::crc32(ba);

    if (DBG_LEVEL >= DBG_DEBUG) {
        qCritical() << DBG_HEADER << "crc32" << hex << crc;
    }

    unsigned char cipherText[256];
    memset(cipherText, 0, sizeof(cipherText));

    // XOR crc32-value (4 bytes) with APAK starting from left
    // (rest of APAK untouched)
    QByteArray clearText(m_apak);
    clearText[0] = clearText[0] ^ ((uint8_t)(crc >> 24));
    clearText[1] = clearText[1] ^ ((uint8_t)(crc >> 16));
    clearText[2] = clearText[2] ^ ((uint8_t)(crc >>  8));
    clearText[3] = clearText[3] ^ ((uint8_t)(crc >>  0));

    if (DBG_LEVEL >= DBG_DEBUG) {
        qCritical() << DBG_HEADER << "clearText" << clearText.toHex();
    }

    // encrypt XOR result with APAK using AES129, ECB mode
    aes_encrypt((uint8_t *)clearText.data(),
                (uint8_t *)cipherText,
                (uint8_t *)m_apak.toStdString().c_str());

    // 8 left bytes of encryption result is signature (Packet ID)
    encryptedPacketID = QByteArray((const char *)cipherText, 8);

    if (DBG_LEVEL >= DBG_INFORMATION) {
        qCritical() << DBG_HEADER << "cipherText (new PacketID)" << encryptedPacketID.toHex(':');
    }

    // insert PacketID in packet
    if (insertEncryptedPacketID(encryptedPacketID)) {

        // build chunks to be sent over serial line

        int const chunks = m_rawPacket.size() / IUC_ASYNCHPOS_MIN_PACKET_SIZE;

        if (DBG_LEVEL >= DBG_DEBUG) {
            qCritical() << DBG_HEADER << "nr of chunks" << chunks;
        }

        int i = 0;
        for (; i < chunks; ++i) {
            QByteArray messageChunk = m_rawPacket.mid(IUC_ASYNCHPOS_MIN_PACKET_SIZE*i,
                                                      IUC_ASYNCHPOS_MIN_PACKET_SIZE);
            if (DBG_LEVEL >= DBG_DEBUG) {
                qCritical() << DBG_HEADER << i << "unmasked" << messageChunk.toHex(':');
            }

            messageChunk = MessageHelper::mask(messageChunk);

            if (DBG_LEVEL >= DBG_DEBUG) {
                qCritical() << DBG_HEADER << i << "  masked" << messageChunk.toHex(':');
            }

            messageChunk.push_back(etx == ACK1 ? ETX2 : ETX1); // etx must be ACK1 or ACK2

            char const lrc = TU::lrc(messageChunk);
            messageChunk.push_back(lrc);
            messageChunk.push_front(STX);

            if (DBG_LEVEL >= DBG_INFORMATION) {
                qCritical() << DBG_HEADER << "chunk to send" << messageChunk.toHex(':');
            }

            m_messageChunkList += messageChunk;
        }

        int const rest = m_rawPacket.size() % IUC_ASYNCHPOS_MIN_PACKET_SIZE;
        if (rest) {
            QByteArray messageChunk = m_rawPacket.mid(IUC_ASYNCHPOS_MIN_PACKET_SIZE*chunks, rest);

            if (DBG_LEVEL >= DBG_DEBUG) {
                qCritical() << DBG_HEADER << i << "unmasked" << messageChunk.toHex(':');
            }

            messageChunk = mask(messageChunk);

            if (DBG_LEVEL >= DBG_DEBUG) {
                qCritical() << DBG_HEADER << i << "  masked" << messageChunk.toHex(':');
            }

            messageChunk.push_back(etx == ACK1 ? ETX2 : ETX1); // etx must be ACK1 or ACK2

            char const lrc = TU::lrc(messageChunk);
            messageChunk.push_back(lrc);
            messageChunk.push_front(STX);


            if (DBG_LEVEL >= DBG_INFORMATION) {
                qCritical() << DBG_HEADER << "chunk to send" << messageChunk.toHex(':');
            }

            m_messageChunkList += messageChunk;
        }
    }

    return m_messageChunkList;
}

QByteArrayList MessageHelper::createLoginMessageChunksToSend(char etx) {
    return createMessageChunksToSend(AsyncPosCommand::LOGIN, etx);
}

QByteArrayList MessageHelper::createLogoutMessageChunksToSend(char etx) {
    return createMessageChunksToSend(AsyncPosCommand::LOGOUT, etx);
}

QByteArrayList MessageHelper::createAuthorizeMessageChunksToSend(char etx) {
    return createMessageChunksToSend(AsyncPosCommand::AUTHORIZE, etx);
}

QByteArrayList MessageHelper::createCloseDocumentMessageChunksToSend(char etx) {
    return createMessageChunksToSend(AsyncPosCommand::CLOSE_DOCUMENT, etx);
}

bool MessageHelper::insertEncryptedPacketID(QByteArray const &encryptedPacketID) {
    if (encryptedPacketID.size() == PACKET_ID_SIZE) {
        // m_rawPacket has already full length
        for (int i = 0; i < PACKET_ID_SIZE; ++i) {
            m_messageHeaderPrefix[i+1] = encryptedPacketID[i];
            m_rawPacket[i+3] = encryptedPacketID[i];
        }

        if (DBG_LEVEL >= DBG_DEBUG) {
            qCritical() << DBG_HEADER << m_messageHeaderPrefix.toHex(':');
            qCritical() << DBG_HEADER << m_rawPacket.toHex(':');
        }

        return true;
    }
    return false;
}

QByteArray const &MessageHelper::mask(QByteArray &messageChunk) {
    QByteArray ba;
    for (int i = 0; i < messageChunk.size(); ++i) {
        char const c = messageChunk[i];
        switch(c) {
        case STX:   __attribute__((fallthrough));
        case ETX1:  __attribute__((fallthrough));
        case ETX2:  __attribute__((fallthrough));
        case EOT:   __attribute__((fallthrough));
        case ENQ:   __attribute__((fallthrough));
        case ACK1:  __attribute__((fallthrough));
        case ACK2:  __attribute__((fallthrough));
        case DLE:   __attribute__((fallthrough));
        case NAK:
            ba.push_back(char(DLE));
            ba.push_back(c + 0x30);
        break;
        default:
            ba.push_back(c);
        }
    }

    messageChunk = ba;
    return messageChunk;
}

QByteArray const &MessageHelper::unMask(QByteArray &messageChunk) {
    QByteArray ba;
    for (int i = 0; i < messageChunk.size(); ++i) {
        char c = messageChunk[i];
        if (c == (char)0x10) /* DEL */ {
            if ((i+1) < messageChunk.size()) {
                c = messageChunk[i+1] - (char)0x30;
            }
        }
        ba.push_back(c);
    }
    messageChunk = ba;
    return messageChunk;
}