#include "dbccomparatorwindow.h"
#include "ui_dbccomparatorwindow.h"
#include "helpwindow.h"
#include <QProgressDialog>
#include <QSettings>
#include <qevent.h>

DBCComparatorWindow::DBCComparatorWindow(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::DBCComparatorWindow)
{
    ui->setupUi(this);
    setWindowFlags(Qt::Window);

    connect(ui->btnDBCFile1, SIGNAL(clicked(bool)), this, SLOT(loadFirstFile()));
    connect(ui->btnDBCFile2, SIGNAL(clicked(bool)), this, SLOT(loadSecondFile()));
    connect(ui->btnSaveDetails, SIGNAL(clicked(bool)), this, SLOT(saveDetails()));

    ui->lblFirstFile->setText("");
    ui->lblSecondFile->setText("");

    firstDBC = nullptr;
    secondDBC = nullptr;

    installEventFilter(this);
}

DBCComparatorWindow::~DBCComparatorWindow()
{
    removeEventFilter(this);
    delete ui;
}

void DBCComparatorWindow::showEvent(QShowEvent *)
{
    readSettings();
}

void DBCComparatorWindow::closeEvent(QCloseEvent *event)
{
    Q_UNUSED(event)
    writeSettings();
}

bool DBCComparatorWindow::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("filecomparison.md");
            break;
        }
        return true;
    } else {
        // standard event processing
        return QObject::eventFilter(obj, event);
    }
    return false;
}

void DBCComparatorWindow::readSettings()
{
    QSettings settings;
    if (settings.value("Main/SaveRestorePositions", false).toBool())
    {
        resize(settings.value("DBCComparator/WindowSize", QSize(720, 631)).toSize());
        move(Utility::constrainedWindowPos(settings.value("DBCComparator/WindowPos", QPoint(50, 50)).toPoint()));
    }
}

void DBCComparatorWindow::writeSettings()
{
    QSettings settings;

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

QString DBCComparatorWindow::loadDBC(DBCFile **file)
{
    QString filename;
    QFileDialog dialog;
    QSettings settings;

    QStringList filters;
    filters.append(QString(tr("DBC Files (*.dbc *.DBC)")));

    dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString());
    dialog.setFileMode(QFileDialog::ExistingFile);
    dialog.setNameFilters(filters);
    dialog.setViewMode(QFileDialog::Detail);

    if (dialog.exec() == QDialog::Accepted)
    {
        filename = dialog.selectedFiles()[0];

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

        qApp->processEvents();

        if (dialog.selectedNameFilter() == filters[0])
        {
            if (file[0]) delete file[0];
            file[0] = new DBCFile;
            file[0]->loadFile(filename);
            qDebug() << "Loaded the DBC file into first slot";
        }

        progress.cancel();
        return filename;
    }
    return QString();
}

void DBCComparatorWindow::loadFirstFile()
{
    QString filename;
    filename = loadDBC(&firstDBC);
    ui->lblFirstFile->setText(filename);
    if (firstDBC && secondDBC) calculateDetails();
}

void DBCComparatorWindow::loadSecondFile()
{
    QString filename;
    filename = loadDBC(&secondDBC);
    ui->lblSecondFile->setText(filename);
    if (firstDBC && secondDBC) calculateDetails();
}

void DBCComparatorWindow::calculateDetails()
{
    QProgressDialog progress(this);
    progress.setWindowModality(Qt::WindowModal);
    progress.setLabelText("Calculating differences");
    progress.setCancelButton(nullptr);
    progress.setRange(0,0);
    progress.setMinimumDuration(0);
    progress.show();

    qApp->processEvents();

    firstDBC->sort();
    secondDBC->sort();


    ui->treeDetails->clear();

    //Find nodes missing in one of the DBC files

    QTreeWidgetItem *nodeDiffRoot = new QTreeWidgetItem();
    nodeDiffRoot->setText(0, "Node Differences");
    QTreeWidgetItem *nodesMissingDBCFirst = new QTreeWidgetItem();
    nodesMissingDBCFirst->setText(0, "Nodes not found in first DBC");
    QTreeWidgetItem *nodesMissingDBCSecond = new QTreeWidgetItem();
    nodesMissingDBCSecond->setText(0, "Nodes not found in second DBC");
    nodeDiffRoot->addChild(nodesMissingDBCFirst);
    nodeDiffRoot->addChild(nodesMissingDBCSecond);
    ui->treeDetails->addTopLevelItem(nodeDiffRoot);

    for (int i = 0; i < firstDBC->dbc_nodes.count(); i++)
    {
        QString nodeName = firstDBC->dbc_nodes[i].name;
        DBC_NODE *node = secondDBC->findNodeByName(nodeName);
        if (!node)
        {
            QTreeWidgetItem *missingNodeItem = new QTreeWidgetItem();
            missingNodeItem->setText(0, nodeName);
            nodesMissingDBCSecond->addChild(missingNodeItem);
        }
    }

    for (int i = 0; i < secondDBC->dbc_nodes.count(); i++)
    {
        QString nodeName = secondDBC->dbc_nodes[i].name;
        DBC_NODE *node = firstDBC->findNodeByName(nodeName);
        if (!node)
        {
            QTreeWidgetItem *missingNodeItem = new QTreeWidgetItem();
            missingNodeItem->setText(0, nodeName);
            nodesMissingDBCFirst->addChild(missingNodeItem);
        }
    }



    //Find messages that are missing in one of the DBC files
    //If a message exists in both then check for missing signals

    QTreeWidgetItem *msgDiffRoot = new QTreeWidgetItem();
    msgDiffRoot->setText(0, "Message Differences");
    QTreeWidgetItem *msgMissingDBCFirst = new QTreeWidgetItem();
    msgMissingDBCFirst->setText(0, "Messages not found in first DBC");
    QTreeWidgetItem *msgMissingDBCSecond = new QTreeWidgetItem();
    msgMissingDBCSecond->setText(0, "Messages not found in second DBC");
    QTreeWidgetItem *msgSignalsDiff = new QTreeWidgetItem();
    msgSignalsDiff->setText(0, "Messages with missing signals");
    QTreeWidgetItem *sigDiffOne = new QTreeWidgetItem();
    sigDiffOne->setText(0, "Missing from first DBC");
    QTreeWidgetItem *sigDiffTwo = new QTreeWidgetItem();
    sigDiffTwo->setText(0, "Missing from second DBC");
    QTreeWidgetItem *sigModifiedRoot = new QTreeWidgetItem();
    sigModifiedRoot->setText(0, "Modified Signals");
    msgSignalsDiff->addChild(sigDiffOne);
    msgSignalsDiff->addChild(sigDiffTwo);

    msgDiffRoot->addChild(msgMissingDBCFirst);
    msgDiffRoot->addChild(msgMissingDBCSecond);
    msgDiffRoot->addChild(msgSignalsDiff);
    msgDiffRoot->addChild(sigModifiedRoot);

    ui->treeDetails->addTopLevelItem(msgDiffRoot);

    QTreeWidgetItem *msgItem {};
    QTreeWidgetItem *sigTemp {};

    for (int i = 0; i < firstDBC->messageHandler->getCount(); i++)
    {
        DBC_MESSAGE *thisMsg = firstDBC->messageHandler->findMsgByIdx(i);
        QString msgName = thisMsg->name;
        DBC_MESSAGE *otherMsg = secondDBC->messageHandler->findMsgByName(msgName);
        if (!otherMsg)
        {
            QTreeWidgetItem *missingMsgItem = new QTreeWidgetItem();
            missingMsgItem->setText(0, msgName+ " (" + Utility::formatCANID(thisMsg->ID) + ")");
            msgMissingDBCSecond->addChild(missingMsgItem);
        }
        else //both have Msg. Check sigs for missing
        {
            bool thisMsgHasMissing = false;
            bool thisMsgHasMods = false;

            for (int i = 0; i < thisMsg->sigHandler->getCount(); i++)
            {
                DBC_SIGNAL *thisSig = thisMsg->sigHandler->findSignalByIdx(i);
                QString sigName = thisSig->name;
                DBC_SIGNAL *otherSig = otherMsg->sigHandler->findSignalByName(sigName);
                if (!otherSig)
                {
                    QTreeWidgetItem *missingSigItem = new QTreeWidgetItem();
                    missingSigItem->setText(0, sigName);
                    if (!thisMsgHasMissing)
                    {
                        thisMsgHasMissing = true;
                        msgItem = new QTreeWidgetItem();
                        msgItem->setText(0, msgName + " (" + Utility::formatCANID(thisMsg->ID) + ")");
                        sigDiffTwo->addChild(msgItem);
                    }
                    if (msgItem)
                        msgItem->addChild(missingSigItem);
                }
                else //signal exists on both sides. See if it as changed position or length
                {
                    bool didChange = false;
                    QTreeWidgetItem *sigItem = new QTreeWidgetItem();
                    sigItem->setText(0, sigName);
                    if (thisSig->startBit != otherSig->startBit)
                    {
                        didChange = true;
                        QTreeWidgetItem *sigStartBit = new QTreeWidgetItem();
                        sigStartBit->setText(0, "Start Bit       First DBC: " + QString::number(thisSig->startBit) + "     Second: " + QString::number(otherSig->startBit));
                        sigItem->addChild(sigStartBit);
                    }
                    if (thisSig->signalSize != otherSig->signalSize)
                    {
                        didChange = true;
                        QTreeWidgetItem *sigSize = new QTreeWidgetItem();
                        sigSize->setText(0, "Size          First DBC: " + QString::number(thisSig->signalSize) + "      Second: " + QString::number(otherSig->signalSize));
                        sigItem->addChild(sigSize);
                    }
                    if (thisSig->bias != otherSig->bias)
                    {
                        didChange = true;
                        QTreeWidgetItem *sigBias = new QTreeWidgetItem();
                        sigBias->setText(0, "Bias          First DBC: " + QString::number(thisSig->bias) + "      Second: " + QString::number(otherSig->bias));
                        sigItem->addChild(sigBias);
                    }
                    if (thisSig->factor != otherSig->factor)
                    {
                        didChange = true;
                        QTreeWidgetItem *sigFactor = new QTreeWidgetItem();
                        sigFactor->setText(0, "Factor        First DBC: " + QString::number(thisSig->factor) + "      Second: " + QString::number(otherSig->factor));
                        sigItem->addChild(sigFactor);
                    }
                    if (didChange)
                    {
                        if (!thisMsgHasMods)
                        {
                            thisMsgHasMods = true;
                            sigTemp = new QTreeWidgetItem();
                            sigTemp->setText(0, msgName + " (" + Utility::formatCANID(thisMsg->ID) + ")");
                            sigModifiedRoot->addChild(sigTemp);
                        }
                        if (sigTemp)
                            sigTemp->addChild(sigItem);
                    }
                }
            }
            thisMsgHasMissing = false;
            for (int i = 0; i < otherMsg->sigHandler->getCount(); i++)
            {
                DBC_SIGNAL *thisSig = otherMsg->sigHandler->findSignalByIdx(i);
                QString sigName = thisSig->name;
                DBC_SIGNAL *otherSig = thisMsg->sigHandler->findSignalByName(sigName);
                if (!otherSig)
                {
                    QTreeWidgetItem *missingSigItem = new QTreeWidgetItem();
                    missingSigItem->setText(0, sigName);
                    if (!thisMsgHasMissing)
                    {
                        thisMsgHasMissing = true;
                        msgItem = new QTreeWidgetItem();
                        msgItem->setText(0, msgName + " (" + Utility::formatCANID(thisMsg->ID) + ")");
                        sigDiffOne->addChild(msgItem);
                    }
                    msgItem->addChild(missingSigItem);
                }
            }
        }
    }

    for (int i = 0; i < secondDBC->messageHandler->getCount(); i++)
    {
        DBC_MESSAGE *origMsg = secondDBC->messageHandler->findMsgByIdx(i);
        QString msgName = origMsg->name;
        DBC_MESSAGE *msg = firstDBC->messageHandler->findMsgByName(msgName);
        if (!msg)
        {
            QTreeWidgetItem *missingMsgItem = new QTreeWidgetItem();
            missingMsgItem->setText(0, msgName + " (" + Utility::formatCANID(origMsg->ID) + ")");
            msgMissingDBCFirst->addChild(missingMsgItem);
        }
    }

    QSettings settings;
    if (settings.value("InfoCompare/AutoExpand", false).toBool())
    {
        ui->treeDetails->expandAll();
    }

    progress.cancel();

    qApp->processEvents();
}

void DBCComparatorWindow::saveDetails()
{
    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("FileComparator/LoadSaveDirectory", dialog.directory().path()).toString());

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

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

        QTreeWidget *tree = ui->treeDetails;


        QTreeWidgetItemIterator it(tree);
        while (*it) {
          QTreeWidgetItem *item = *it;
          QString itemText = item->text(0);
          while (item->parent())
          {
              outFile->write("   ");
              item = item->parent();
          }
          outFile->write(itemText.toUtf8() + "\n");
          ++it;
        }

        outFile->close();

    }
}

