#include "isotp_interpreterwindow.h"
#include "ui_isotp_interpreterwindow.h"
#include "mainwindow.h"
#include "helpwindow.h"
#include "filterutility.h"

ISOTP_InterpreterWindow::ISOTP_InterpreterWindow(const QVector<CANFrame> *frames, QWidget *parent) :
    QDialog(parent),
    ui(new Ui::ISOTP_InterpreterWindow)
{
    ui->setupUi(this);
    setWindowFlags(Qt::Window);
    modelFrames = frames;

    decoder = new ISOTP_HANDLER;
    udsDecoder = new UDS_HANDLER;

    decoder->setReception(true);
    decoder->setProcessAll(true);

    udsDecoder->setReception(false);

    connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &ISOTP_InterpreterWindow::updatedFrames);
    connect(MainWindow::getReference(), &MainWindow::framesUpdated, decoder, &ISOTP_HANDLER::updatedFrames);
    connect(decoder, &ISOTP_HANDLER::newISOMessage, this, &ISOTP_InterpreterWindow::newISOMessage);
    connect(udsDecoder, &UDS_HANDLER::newUDSMessage, this, &ISOTP_InterpreterWindow::newUDSMessage);
    connect(ui->listFilter, &QListWidget::itemChanged, this, &ISOTP_InterpreterWindow::listFilterItemChanged);
    connect(ui->btnAll, &QPushButton::clicked, this, &ISOTP_InterpreterWindow::filterAll);
    connect(ui->btnNone, &QPushButton::clicked, this, &ISOTP_InterpreterWindow::filterNone);
    connect(ui->btnCaptured, &QPushButton::clicked, this, &ISOTP_InterpreterWindow::interpretCapturedFrames);

    connect(ui->tableIsoFrames, &QTableWidget::itemSelectionChanged, this, &ISOTP_InterpreterWindow::showDetailView);
    connect(ui->btnClearList, &QPushButton::clicked, this, &ISOTP_InterpreterWindow::clearList);
    connect(ui->btnSaveList, &QPushButton::clicked, this, &ISOTP_InterpreterWindow::saveList);
    connect(ui->cbUseExtendedAddressing, SIGNAL(toggled(bool)), this, SLOT(useExtendedAddressing(bool)));

    QStringList headers;
    headers << "Timestamp" << "ID" << "Bus" << "Dir" << "Length" << "Data";
    ui->tableIsoFrames->setColumnCount(6);
    ui->tableIsoFrames->setColumnWidth(0, 100);
    ui->tableIsoFrames->setColumnWidth(1, 50);
    ui->tableIsoFrames->setColumnWidth(2, 50);
    ui->tableIsoFrames->setColumnWidth(3, 50);
    ui->tableIsoFrames->setColumnWidth(4, 75);
    ui->tableIsoFrames->setColumnWidth(5, 200);
    ui->tableIsoFrames->setHorizontalHeaderLabels(headers);
    QHeaderView *HorzHdr = ui->tableIsoFrames->horizontalHeader();
    HorzHdr->setStretchLastSection(true);
    connect(HorzHdr, SIGNAL(sectionClicked(int)), this, SLOT(headerClicked(int)));

    decoder->setReception(true);
    decoder->setFlowCtrl(false);
    decoder->setProcessAll(true);
}

ISOTP_InterpreterWindow::~ISOTP_InterpreterWindow()
{
    delete decoder;
    delete ui;
}

void ISOTP_InterpreterWindow::showEvent(QShowEvent* event)
{
    QDialog::showEvent(event);
    readSettings();

    QProgressDialog progress(qApp->activeWindow());
    progress.setWindowModality(Qt::WindowModal);
    progress.setLabelText("Analyzing Frames...");
    progress.setCancelButton(nullptr);
    progress.setRange(0,0);
    progress.setMinimumDuration(0);
    progress.show();

    qApp->processEvents();

    decoder->updatedFrames(-2);
    //decoder->rapidFrames(nullptr, *modelFrames);

    progress.cancel();

    installEventFilter(this);
}

void ISOTP_InterpreterWindow::closeEvent(QCloseEvent *event)
{
    Q_UNUSED(event)
    removeEventFilter(this);
    writeSettings();
}

bool ISOTP_InterpreterWindow::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::KeyRelease) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        switch (keyEvent->key())
        {
        case Qt::Key_F1:
            HelpWindow::getRef()->showHelp("isotp_decoder.md");
            break;
        }
        return true;
    } else {
        // standard event processing
        return QObject::eventFilter(obj, event);
    }
    return false;
}

void ISOTP_InterpreterWindow::readSettings()
{
    QSettings settings;
    if (settings.value("Main/SaveRestorePositions", false).toBool())
    {
        resize(settings.value("ISODecodeWindow/WindowSize", this->size()).toSize());
        move(Utility::constrainedWindowPos(settings.value("ISODecodeWindow/WindowPos", QPoint(50, 50)).toPoint()));
    }
}

void ISOTP_InterpreterWindow::writeSettings()
{
    QSettings settings;

    if (settings.value("Main/SaveRestorePositions", false).toBool())
    {
        settings.setValue("ISODecodeWindow/WindowSize", size());
        settings.setValue("ISODecodeWindow/WindowPos", pos());
    }
}

//erase current list then repopulate as if all the previously captured frames just came in again.
void ISOTP_InterpreterWindow::interpretCapturedFrames()
{
    clearList();
    decoder->updatedFrames(-2);
}

void ISOTP_InterpreterWindow::listFilterItemChanged(QListWidgetItem *item)
{
    if (item)
    {
        int id = FilterUtility::getIdAsInt(item);
        bool state = item->checkState();
        //qDebug() << id << "*" << state;
        idFilters[id] = state;
    }
}

void ISOTP_InterpreterWindow::filterAll()
{
    for (int i = 0 ; i < ui->listFilter->count(); i++)
    {
        ui->listFilter->item(i)->setCheckState(Qt::Checked);
        //idFilters[ui->listFilter->item(i)->text().toInt(nullptr, 16)] = true;
    }
}

void ISOTP_InterpreterWindow::filterNone()
{
    for (int i = 0 ; i < ui->listFilter->count(); i++)
    {
        ui->listFilter->item(i)->setCheckState(Qt::Unchecked);
        //idFilters[ui->listFilter->item(i)->text().toInt(nullptr, 16)] = false;
    }
}

void ISOTP_InterpreterWindow::clearList()
{
    qDebug() << "Clearing the table";
    ui->tableIsoFrames->clearContents();
    ui->tableIsoFrames->model()->removeRows(0, ui->tableIsoFrames->rowCount());
    messages.clear();
    //idFilters.clear();
}

/*
 * A bit complicated as the list doesn't have the detailed analysis in it. Have to take each list entry
 * and process it to get the details then save those details to the file as well.
 */
void ISOTP_InterpreterWindow::saveList()
{
    QString buildString;
    QString filename;
    QFileDialog dialog(this);
    QSettings settings;

    QStringList filters;
    filters.append(QString(tr("Text File (*.txt)")));

    dialog.setFileMode(QFileDialog::AnyFile);
    dialog.setNameFilters(filters);
    dialog.setViewMode(QFileDialog::Detail);
    dialog.setAcceptMode(QFileDialog::AcceptSave);
    dialog.setDirectory(settings.value("FrameInfo/LoadSaveDirectory", dialog.directory().path()).toString());

    if (dialog.exec() == QDialog::Accepted)
    {
        settings.setValue("FrameInfo/LoadSaveDirectory", dialog.directory().path());
        filename = dialog.selectedFiles()[0];
        if (!filename.contains('.')) filename += ".txt";
        if (dialog.selectedNameFilter() == filters[0])
        {
            QFile *outFile = new QFile(filename);

            if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
            {
                delete outFile;
                return;
            }

            int rows = messages.count();
            for (int r = 0 ; r < rows; r++)
            {
                ISOTP_MESSAGE msg = messages.at(r);
                const unsigned char *data = reinterpret_cast<const unsigned char *>(msg.payload().constData());
                int dataLen = msg.payload().length();

                if (msg.reportedLength != dataLen)
                {
                    continue;
                }

                buildString.append(QString::number(msg.timeStamp().microSeconds()) + " " + QString::number(msg.frameId(), 16) + " ");

                //buildString.append(tr("Raw Payload: "));

                for (int i = 0; i < dataLen; i++)
                {
                    buildString.append(Utility::formatNumber(data[i]));
                    buildString.append(" ");
                }
                buildString.append("\n\n");

                UDS_MESSAGE udsMsg;
                bool result;
                udsMsg = udsDecoder->tryISOtoUDS(msg, &result);
                if (result)
                    buildString.append(udsDecoder->getDetailedMessageAnalysis(udsMsg));
                buildString.append("\n*********************************************************\n");
                outFile->write(buildString.toUtf8());
            }

            outFile->close();
            delete outFile;
        }
    }
}


void ISOTP_InterpreterWindow::useExtendedAddressing(bool checked)
{
    decoder->setExtendedAddressing(checked);
    this->interpretCapturedFrames();
}

void ISOTP_InterpreterWindow::updatedFrames(int numFrames)
{
    if (numFrames == -1) //all frames deleted. Kill the display
    {
        clearList();
    }
    else if (numFrames == -2) //all new set of frames. Reset
    {
        clearList();
    }
    else //just got some new frames. See if they are relevant.
    {
    }
}

void ISOTP_InterpreterWindow::headerClicked(int logicalIndex)
{
    ui->tableIsoFrames->setSortingEnabled(false);
    ui->tableIsoFrames->sortByColumn(logicalIndex, Qt::SortOrder::AscendingOrder);
}

void ISOTP_InterpreterWindow::showDetailView()
{
    QString buildString;
    ISOTP_MESSAGE *msg;
    int rowNum = ui->tableIsoFrames->currentRow();

    ui->txtFrameDetails->clear();
    if (rowNum == -1) return;

    msg = &messages[rowNum];

    const unsigned char *data = reinterpret_cast<const unsigned char *>(msg->payload().constData());
    int dataLen = msg->payload().length();

    if (msg->reportedLength != dataLen)
    {
        buildString.append("Message didn't have the correct number of bytes.\rExpected "
                           + QString::number(msg->reportedLength) + " got "
                           + QString::number(dataLen) + "\r\r");
    }

    buildString.append(tr("Raw Payload: "));

    for (int i = 0; i < dataLen; i++)
    {
        buildString.append(Utility::formatNumber(data[i]));
        buildString.append(" ");
    }
    buildString.append("\r\r");

    ui->txtFrameDetails->setPlainText(buildString);

    //pass this frame to the UDS decoder to see if it feels it could be a UDS related message
    udsDecoder->gotISOTPFrame(messages[rowNum]);
}

void ISOTP_InterpreterWindow::newUDSMessage(UDS_MESSAGE msg)
{
    //qDebug() << "Got UDS message in ISOTP Interpreter";
    QString buildText;

    buildText = ui->txtFrameDetails->toPlainText();

    buildText.append(udsDecoder->getDetailedMessageAnalysis(msg));

    ui->txtFrameDetails->setPlainText(buildText);
}

void ISOTP_InterpreterWindow::newISOMessage(ISOTP_MESSAGE msg)
{
    int rowNum;
    QString tempString;

    const unsigned char *data = reinterpret_cast<const unsigned char *>(msg.payload().constData());
    int dataLen = msg.payload().length();

    if ((msg.reportedLength != dataLen) && !ui->cbShowIncomplete->isChecked()) return;

    if (idFilters.find(msg.frameId()) == idFilters.end())
    {
        idFilters.insert(msg.frameId(), true);

        FilterUtility::createCheckableFilterItem(msg.frameId(), true, ui->listFilter);
    }
    if (!idFilters[msg.frameId()]) return;
    messages.append(msg);

    rowNum = ui->tableIsoFrames->rowCount();

    ui->tableIsoFrames->insertRow(rowNum);

    QTableWidgetItem *item = new QTableWidgetItem;
    item->setData(Qt::EditRole, Utility::formatTimestamp(msg.timeStamp().microSeconds()));
    //ui->tableIsoFrames->setItem(rowNum, 0, (double)msg.timestamp, Utility::formatTimestamp(msg.timestamp)));
    ui->tableIsoFrames->setItem(rowNum, 0, item);
    ui->tableIsoFrames->setItem(rowNum, 1, new QTableWidgetItem(QString::number(msg.frameId(), 16)));
    ui->tableIsoFrames->setItem(rowNum, 2, new QTableWidgetItem(QString::number(msg.bus)));
    if (msg.isReceived) ui->tableIsoFrames->setItem(rowNum, 3, new QTableWidgetItem("Rx"));
    else ui->tableIsoFrames->setItem(rowNum, 3, new QTableWidgetItem("Tx"));
    ui->tableIsoFrames->setItem(rowNum, 4, new QTableWidgetItem(QString::number(msg.payload().length())));

    for (int i = 0; i < dataLen; i++)
    {
        tempString.append(Utility::formatNumber(data[i]));
        tempString.append(" ");
    }
    ui->tableIsoFrames->setItem(rowNum, 5, new QTableWidgetItem(tempString));
}
