#include "widget.h"
#include "ui_widget.h"

#include <QMessageBox>
#include <QString>
#define SEND_COMBOBOX_NAME "txSendComboBox"
#define TYPE_COMBOBOX_NAME "txTypeComboBox"
#define CHECKSUMTYPE_COMBOBOX_NAME "txChecksumTypeComboBox"
#define ID_EDIT_NAME "txIdEdit"
#define CYCLE_EDIT_NAME "txCycleEdit"
#define LENGTH_EDIT_NAME "txLengthEdit"
#define COUNT_EDIT_NAME "txCountEdit"
#define DATA_EDIT_NAME "txDataEdit"
#define CHECKSUM_EDIT_NAME "txChecksumEdit"
#define Enable_Check "txEnableCheck"

#define MASTERTIMEOUT 100  //Unit in milliseconds
#define SLAVETIMEOUT  2000 //Unit in milliseconds
#define PAYLOADLENGTH 8

uint8_t calculatePID(uint8_t ucId)
{
    char chChecksum = ucId;
    char chMask[8], byte[8];
    memset(chMask, 0, sizeof(char) * 8);
    memset(byte, 0, sizeof(char) * 8);
    for (int nIndex = 0; nIndex < 8; nIndex++)
    {
        chMask[nIndex] = nIndex + 1;
        byte[nIndex] = ((chChecksum) >> nIndex) & 1;
    }

    char chP0, chP1;
    // chP0 = ID0+ID1+ID2+ID4
    chP0 = byte[0] ^ byte[1] ^ byte[2] ^ byte[4];


    // chP1 = ID1+ID3+ID4+ID5
    chP1 = byte[1] + byte[3] + byte[4] + byte[5];

    chP1 = ~chP1;

    //00010110 -> p00010110
    chP0 = chP0 << 6;
    chP1 = chP1 << 7;
    chChecksum = chChecksum + chP0 + chP1;
    ucId = chChecksum;
    return ucId;
}

uint8_t calculateChecksum(const uint8_t* xData, uint8_t xDataLen, uint8_t pId)
{
    uint16_t crc = pId;
    uint16_t ucMask = 0xFF;
    for (auto unIndex = 0U; unIndex < xDataLen; unIndex++)
    {
        uint8_t ucDatabyte = xData[unIndex];

        crc += ucDatabyte;
        uint8_t ucCarry = static_cast<unsigned char>(crc / ucMask);

        if (ucCarry != 0)
        {
            crc = crc - ucMask;
        }
    }
    crc = ~crc;
    return (crc & 0xFF);
}


Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
    , msgTableModel(NULL)
    , openedChannelCount(0)
{
    memset(channels, 0, sizeof(channels));
    ui->setupUi(this);
    ui->groupBox_rxFilter->setEnabled(true);
    ui->groupBox_txTable->setEnabled(false);
    ui->modeCombo->clear();
    ui->modeCombo->addItem(tr("Master"), (int)BM_LIN_MASTER_MODE);
    ui->modeCombo->addItem(tr("Slave"), (int)BM_LIN_SLAVE_MODE);

    msgTableModel = new LinMsgTableModel;
    ui->msgTableView->setModel(msgTableModel);
    ui->msgTableView->horizontalHeader()->setSectionResizeMode(ui->msgTableView->horizontalHeader()->count() - 1, QHeaderView::Stretch);
    ui->msgTableView->horizontalHeader()->setSectionResizeMode(ui->msgTableView->horizontalHeader()->count() - 2, QHeaderView::Stretch);

    rxTimer = new QTimer(this);
    connect(rxTimer, &QTimer::timeout, this, &Widget::readPendingMessages);

    connect(ui->txIdEdit_1, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txIdEdit_2, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txIdEdit_3, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txIdEdit_4, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txIdEdit_5, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txIdEdit_6, &QLineEdit::textChanged, this, &Widget::checksumChanged);

    connect(ui->txDataEdit_1, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txDataEdit_2, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txDataEdit_3, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txDataEdit_4, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txDataEdit_5, &QLineEdit::textChanged, this, &Widget::checksumChanged);
    connect(ui->txDataEdit_6, &QLineEdit::textChanged, this, &Widget::checksumChanged);

    connect(ui->txChecksumTypeComboBox_1, &QComboBox::currentTextChanged, this, &Widget::checksumChanged);
    connect(ui->txChecksumTypeComboBox_2, &QComboBox::currentTextChanged, this, &Widget::checksumChanged);
    connect(ui->txChecksumTypeComboBox_3, &QComboBox::currentTextChanged, this, &Widget::checksumChanged);
    connect(ui->txChecksumTypeComboBox_4, &QComboBox::currentTextChanged, this, &Widget::checksumChanged);
    connect(ui->txChecksumTypeComboBox_5, &QComboBox::currentTextChanged, this, &Widget::checksumChanged);
    connect(ui->txChecksumTypeComboBox_6, &QComboBox::currentTextChanged, this, &Widget::checksumChanged);

    BM_Init();
    enumerate();
}

Widget::~Widget()
{
    BM_UnInit();
    delete ui;
}

int Widget::lengthToDlc(int n)
{
    int dlc = 0;
    if (n <= 8)
    {
        dlc = n;
    }
    else
    {
        dlc = 8;
    }
    return dlc;
}

void Widget::enumerate()
{
    int n = sizeof(channelinfos) / sizeof(channelinfos[0]);
    if (BM_ERROR_OK == BM_EnumerateByCap(channelinfos, &n, BM_LIN_CAP))
    {
        ui->channelCombo->clear();
        for (int i = 0; i < n; i++)
        {
            ui->channelCombo->appendItem(channelinfos[i].name, true);
        }
    }

}

void Widget::on_openButton_clicked(bool checked)
{
    bool success = true;
    if (ui->channelCombo->count())
    {
        QList<QComboBox*> comboBoxList = ui->groupBox_txTable->findChildren<QComboBox*>();
        for (auto& it : comboBoxList)
        {
            QString str = it->objectName();
            if (str.indexOf("txSendComboBox") >= 0 || str.indexOf("txTypeComboBox") >= 0)
            {
                it->clear();
            }
        }
        openedChannelCount = 0;
        if (checked)
        {
            uint64_t filterValue = 0;
            QList<QCheckBox*> checkBoxList = ui->groupBox_rxFilter->findChildren<QCheckBox*>();
            for (auto& it : checkBoxList)
            {
                if (it->isChecked())
                {
                    filterValue |= 1ULL << it->text().toUInt();
                }
            }

            BM_LinModeTypeDef mode = (BM_LinModeTypeDef)ui->modeCombo->currentData().value<int>();
            int i = 0;
            for (auto& it : comboBoxList)
            {
                QString str = it->objectName();
                if (str.indexOf("txTypeComboBox") >= 0)
                {
                    QString str = QString("_%1").arg(i + 1);
                    QLineEdit* txCycle = ui->groupBox_txTable->findChild<QLineEdit*>(CYCLE_EDIT_NAME + str);
                    if (mode == BM_LIN_MASTER_MODE)
                    {
                        txCycle->setText("1000");
                        txCycle->setEnabled(true);
                        it->addItem(tr("Master"), (int)LIN_MASTER_RESPONSE);
                        it->addItem(tr("Master-Receive"), (int)LIN_MASTER_RESPONSE_RECEIVE);
                    }
                    else if (mode == BM_LIN_SLAVE_MODE)
                    {
                        txCycle->setText("");
                        txCycle->setEnabled(false);
                        it->addItem(tr("Slave"), (int)LIN_SLAVE_RESPONSE);
                    }
                    i++;
                }
            }
            for (int i = 0; i < ui->channelCombo->count(); i++)
            {
                if (ui->channelCombo->isChecked(i) == true)
                {
                    BM_ChannelInfoTypeDef* channelinfo = &channelinfos[i]; // Iterate all enumerated ports here
                    for (auto& it : comboBoxList)
                    {
                        QString str = it->objectName();
                        if (str.indexOf("txSendComboBox") >= 0)
                        {
                            it->addItem(QString::number(openedChannelCount + 1)); // Show opened port index here
                        }
                    }
                    BM_TerminalResistorTypeDef tres = ui->tresCheck->isChecked() ? BM_TRESISTOR_PULLUP_1K : BM_TRESISTOR_DISABLED;

                    BM_RxFilterTypeDef rxfilter;
                    {
                        // Get the Filter
                        memset(&rxfilter, 0, sizeof(rxfilter));
                        rxfilter.id_mask = (uint32_t)(filterValue >> 32);
                        rxfilter.id_value = (uint32_t)filterValue;
                        rxfilter.type = BM_RXFILTER_BASIC;
                    }

                    BM_BitrateTypeDef bitrate;
                    {
                        memset(&bitrate, 0, sizeof(bitrate));
                        bitrate.nbitrate = (uint16_t)ui->nominalBitrateEdit->text().toUInt();
                    }
                    BM_StatusTypeDef status = BM_OpenEx(&channels[openedChannelCount], channelinfo, mode, tres, &bitrate, &rxfilter, 1);
                    if (status != BM_ERROR_OK)
                    {
                        char buffer[1024];
                        BM_GetErrorText(status, buffer, sizeof(buffer), 0);
                        QString prompt = QString(tr("Failed to open channel %1, error=%2(%3)."))
                                .arg(channelinfo->name)
                                .arg((int)status, 8, 16)
                                .arg(buffer);
                        QMessageBox::critical(this, tr("Error"), prompt);
                        success = false;
                        BM_TxTaskTypeDef tasks[NTXTASK];
                        memset(&tasks, 0, sizeof(BM_TxTaskTypeDef) * NTXTASK);
                        txTaskMsgList[i].clear();
                        BM_SetTxTasks(channels[i], (BM_TxTaskTypeDef*)tasks, NTXTASK);
                        //BM_ClearBuffer(channels[i]);
                        BM_Close(channels[i]);
                    }
                    openedChannelCount++;
                }
            }
        }

        if (checked && success)
        {
            msgTableModel->clearAllMessages();
            rxTimer->start(5);
            ui->openButton->setText(tr("Close"));
            ui->outputCheck->setEnabled(false);
            ui->modeCombo->setEnabled(false);
            ui->tresCheck->setEnabled(false);
            ui->nominalBitrateEdit->setEnabled(false);
            ui->groupBox_rxFilter->setEnabled(false);
            ui->groupBox_txTable->setEnabled(true);
            ui->enumerateButton->setEnabled(false);
            ui->txButton->setEnabled(true);
            ui->txButton->setText(tr("Transmit"));
            ui->txButton->setChecked(false);
            ui->channelCombo->setEnabled(false);
            stopSendMsg();
        }
        else
        {
            rxTimer->stop();
            ui->openButton->setChecked(false);
            ui->openButton->setText(tr("Open"));
            ui->outputCheck->setEnabled(true);
            ui->modeCombo->setEnabled(true);
            ui->tresCheck->setEnabled(true);
            ui->groupBox_rxFilter->setEnabled(true);
            ui->groupBox_txTable->setEnabled(false);
            ui->nominalBitrateEdit->setEnabled(true);
            ui->enumerateButton->setEnabled(true);
            ui->txButton->setEnabled(false);
            ui->txButton->setText(tr("Transmit"));
            ui->channelCombo->setEnabled(true);
            stopSendMsg();
        }
    }

    if (success == false)
    {
        QMessageBox::critical(this, tr("Error"), tr("Please plugin your Busmust device and press 'Enumerate'."));
    }
}

void Widget::on_enumerateButton_clicked()
{
    enumerate();
}


void Widget::readPendingMessages(void)
{
    BM_DataTypeDef msg;
    bool newMsgInserted = false;
    for (int i = 0; i < openedChannelCount; i++)
    {
        if (channels[i] && msgTableModel)
        {
            while (BM_Read(channels[i], &msg) == BM_ERROR_OK)
            {
                BM_LinMessageTypeDef* linMessage = (BM_LinMessageTypeDef*)msg.payload;
                int count = 0;
                BM_LinModeTypeDef mode = (BM_LinModeTypeDef)ui->modeCombo->currentData().value<int>();
                if ((msg.header.type & BM_ACK_DATA))
                {
                    if (linMessage->ctrl.lin.ISTXTASK && txTaskMsgList[i].size())
                    {
                        int count = txTaskMsgList[i][linMessage->ctrl.lin.SEQ & 0x3FU].txTask.nrounds;
                        if (linMessage->id == txTaskMsgList[i][linMessage->ctrl.lin.SEQ & 0x3FU].txTask.lin.ID)
                        {
                            txTaskMsgList[i][linMessage->ctrl.lin.SEQ & 0x3FU].txTask.nrounds  = count -1;
                        }
                    }
                }
                if (txTaskMsgList[i].size() == 0)
                {
                    return;
                }
                Q_FOREACH(MsgTxtask data, txTaskMsgList[i])
                {
                    count += data.txTask.nrounds;
                }
                if (count <= 0)
                {
                    on_txButton_clicked(false);
                }
                uint8_t pid = 0;
                if (linMessage->ctrl.lin.ENHANCED_CHECKSUM)
                {
                    pid = calculatePID(linMessage->id);
                }
                int checksum = calculateChecksum(linMessage->payload, linMessage->ctrl.lin.DLC, pid);
                int state = 0;
                if (checksum != linMessage->ctrl.lin.CHECKSUM)
                {
                    state = LIN_CHECKSUM_ERROR;
                }
                if (!(msg.header.type & BM_ACK_DATA))
                {
                    if (mode == BM_LIN_MASTER_MODE)
                    {
                        if (linMessage->ctrl.lin.ERRORS == BM_LIN_TRANSMIT_TIMEOUT)
                        {
                            state = LIN_TIMEOUT_ERROR;
                        }
                    }
                }
                msgTableModel->insertMessage(msg, i + 1, state, (int)mode);
                newMsgInserted = true;
            }

        }
    }
    if (newMsgInserted)
    {
        ui->msgTableView->scrollToBottom();
    }
}
void Widget::writePendingMessages(void)
{
}



void Widget::checksumChanged()
{
    QString str = sender()->objectName().right(2);
    QComboBox* txChecksumType = ui->groupBox_txTable->findChild<QComboBox*>(CHECKSUMTYPE_COMBOBOX_NAME + str);
    QLineEdit* txId = ui->groupBox_txTable->findChild<QLineEdit*>(ID_EDIT_NAME + str);
    QLineEdit* txData = ui->groupBox_txTable->findChild<QLineEdit*>(DATA_EDIT_NAME + str);
    QLineEdit* txChecksum = ui->groupBox_txTable->findChild<QLineEdit*>(CHECKSUM_EDIT_NAME + str);
    QLineEdit* txDlc = ui->groupBox_txTable->findChild<QLineEdit*>(LENGTH_EDIT_NAME + str);
    int checksumType = txChecksumType->currentIndex() + 1;
    uint id = txId->text().toUInt(NULL, 16);
    QString payload = txData->text().remove(QLatin1Char(' '));
    int j = 0;
    char payloadValue[PAYLOADLENGTH];
    memset(payloadValue, 0, sizeof(char) * PAYLOADLENGTH);
    while (j * 2 < payload.length() && j < PAYLOADLENGTH)
    {
        payloadValue[j] = payload.mid(j * 2, 2).toUInt(NULL, 16);
        j++;
    }
    txDlc->setText(QString::number(j));
    uint8_t pid = 0;
   if (checksumType == BM_LIN_ENHANCED_CHECKSUM)
    {
        pid = calculatePID(id);
    }
    int checksum = calculateChecksum((uint8_t*)payloadValue, j, pid);
    txChecksum->setText(QString::number(checksum, 16).toUpper());
}


void Widget::on_selectAllButton_clicked()
{
    QList<QCheckBox*> checkBoxList = ui->groupBox_rxFilter->findChildren<QCheckBox*>();
    for (auto& it : checkBoxList)
    {
        it->setChecked(true);
    }
}

void Widget::on_reverseButton_clicked()
{
    QList<QCheckBox*> checkBoxList = ui->groupBox_rxFilter->findChildren<QCheckBox*>();
    for (auto& it : checkBoxList)
    {
        it->setChecked(!it->isChecked());
    }
}

void Widget::stopSendMsg()
{
    BM_TxTaskTypeDef tasks[NTXTASK];
    memset(&tasks, 0, sizeof(BM_TxTaskTypeDef) * NTXTASK);
    for (int i = 0; i < ui->channelCombo->count(); i++)
    {
        txTaskMsgList[i].clear();
        BM_SetTxTasks(channels[i], (BM_TxTaskTypeDef*)tasks, NTXTASK);
        //BM_ClearBuffer(channels[i]);
    }
}

void Widget::on_txButton_clicked(bool checked)
{
    if (checked)
    {
        bool success = false;
        for (int i = 0; i < NTXTASK; i++)
        {
            txTaskMsgList[i].clear();
            QString str = QString("_%1").arg(i + 1);
            QCheckBox* txData = ui->groupBox_txTable->findChild<QCheckBox*>(Enable_Check + str);
            if (txData->isChecked())
            {
                QComboBox* txSend = ui->groupBox_txTable->findChild<QComboBox*>(SEND_COMBOBOX_NAME + str);
                QComboBox* txType = ui->groupBox_txTable->findChild<QComboBox*>(TYPE_COMBOBOX_NAME + str);
                QComboBox* txChecksumType = ui->groupBox_txTable->findChild<QComboBox*>(CHECKSUMTYPE_COMBOBOX_NAME + str);

                QLineEdit* txId = ui->groupBox_txTable->findChild<QLineEdit*>(ID_EDIT_NAME + str);
                QLineEdit* txLength = ui->groupBox_txTable->findChild<QLineEdit*>(LENGTH_EDIT_NAME + str);
                QLineEdit* txCycle = ui->groupBox_txTable->findChild<QLineEdit*>(CYCLE_EDIT_NAME + str);
                QLineEdit* txCount = ui->groupBox_txTable->findChild<QLineEdit*>(COUNT_EDIT_NAME + str);
                QLineEdit* txData = ui->groupBox_txTable->findChild<QLineEdit*>(DATA_EDIT_NAME + str);

                int port = txSend->currentIndex();
                if (port >= 0 && checked && channels[port] && port < int(sizeof(channels) / sizeof(channels[0])))
                {
                    MsgTxtask msg;
                    memset(&msg, 0, sizeof(msg));
                    BM_TxTaskTypeDef txTask;
                    memset(&txTask, 0, sizeof(txTask));
                    uint id = txId->text().toUInt(NULL, 16);
                    uint length = txLength->text().toUInt();
                    int dlc = lengthToDlc(length);
                    memset(txTask.payload, 0, sizeof(txTask.payload));
                    QString payload = txData->text().remove(QLatin1Char(' '));
                    int j = 0;
                    if (txType->currentData().value<int>() == LIN_MASTER_RESPONSE_RECEIVE)
                    {
                        txData->setText("00 00 00 00 00 00 00 00");
                    }
                    else
                    {
                        txTask.flags = BM_LIN_MESSAGE_FLAGS_TRANSMIT;
                        while (j * 2 < payload.length())
                        {
                            txTask.payload[j] = payload.mid(j * 2, 2).toUInt(NULL, 16);
                            j++;
                        }
                        payload = "";
                        QString formattedString = "";
                        for(int i = 0; i < j; i++)
                        {
                            formattedString = formattedString.sprintf("%02x", txTask.payload[i]);
                            if (i == 0)
                            {
                                payload += formattedString;
                            }
                            else
                            {
                                payload = payload + " " + formattedString;
                            }

                        }
                        txData->setText(payload);
                    }
                    int checksumType = txChecksumType->currentIndex() + 1;
                    uint8_t pid = 0;
                    if (checksumType == BM_LIN_ENHANCED_CHECKSUM)
                    {
                        pid = calculatePID(id);
                        txTask.flags |= BM_LIN_MESSAGE_FLAGS_ENHANCED_CHECKSUM;
                    }
                    txTask.lin.CHECKSUM = calculateChecksum(txTask.payload, dlc, pid);
                    txTask.lin.ID = id;
                    txTask.length = dlc;
                    txTask.cycle = txCycle->text().toUInt();
                    txTask.type = BM_TXTASK_FIXED;
                    txTask.nmessages = 1;
                    txTask.nrounds = txCount->text().toUInt();
                    msg.txTask = txTask;
                    msg.txTaskCount = txTask.nrounds;
                    msg.channel = port;
                    txTaskMsgList[port].append(msg);
                    success = true;
                }
            }
        }
        if (success)
        {
            for (int i = 0; i < openedChannelCount; i++)
            {
                BM_TxTaskTypeDef tasks[NTXTASK];
                memset(&tasks, 0, sizeof(BM_TxTaskTypeDef) * NTXTASK);
                int j = 0;
                Q_FOREACH(MsgTxtask value, txTaskMsgList[i])
                {
                    tasks[j] = value.txTask;
                    j++;
                }
                //BM_ClearBuffer(channels[i]);
                BM_SetTxTasks(channels[i], (BM_TxTaskTypeDef*)tasks, NTXTASK);
            }
            ui->txButton->setText(tr("Stop"));
            ui->groupBox_txTable->setEnabled(false);
        }
    }
    else
    {
        ui->txButton->setChecked(false);
        ui->txButton->setText(tr("Transmit"));
        ui->groupBox_txTable->setEnabled(true);
        stopSendMsg();
    }
}

