/*
    Original copyright 2018 Benjamin Vedder benjamin@vedder.se and the VESC Tool project ( https://github.com/vedderb/vesc_tool )

    Forked to:
    Copyright 2018 Danny Bokma github@diebie.nl (https://github.com/DieBieEngineering/DieBieMS-Tool)

    Now forked to:
    Copyright 2019 - 2020 Kevin Dionne kevin.dionne@ennoid.me (https://github.com/EnnoidMe/ENNOID-BMS-Tool)

    This file is part of ENNOID-BMS Tool.

    ENNOID-BMS Tool is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ENNOID-BMS Tool is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "pagefirmware.h"
#include "ui_pagefirmware.h"
#include "widgets/helpdialog.h"
#include "utility.h"
#include <QMessageBox>
#include <QFileDialog>
#include <QDirIterator>

PageFirmware::PageFirmware(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::PageFirmware)
{
    ui->setupUi(this);
    layout()->setContentsMargins(0, 0, 0, 0);
    ui->cancelButton->setEnabled(false);
    mDieBieMS = 0;

    updateHwList();
    updateBlList();

    connect(ui->hwList, SIGNAL(currentRowChanged(int)),
            this, SLOT(updateFwList()));
    connect(ui->showNonDefaultBox, SIGNAL(toggled(bool)),
            this, SLOT(updateFwList()));
}

PageFirmware::~PageFirmware()
{
    delete ui;
}

BMSInterface *PageFirmware::bms() const
{
    return mDieBieMS;
}

void PageFirmware::setDieBieMS(BMSInterface *dieBieMS)
{
    mDieBieMS = dieBieMS;

    if (mDieBieMS) {
        ui->display->setText(mDieBieMS->commands()->getFirmwareUploadStatus());

        QStringList fws = mDieBieMS->getSupportedFirmwares();
        QString str;
        for (int i = 0;i < fws.size();i++) {
            str.append(fws.at(i));
            if (i < (fws.size() - 1)) {
                str.append(", ");
            }
        }
        ui->supportedLabel->setText(str);

        connect(mDieBieMS, SIGNAL(fwUploadStatus(QString,double,bool)),
                this, SLOT(fwUploadStatus(QString,double,bool)));
        connect(mDieBieMS->commands(), SIGNAL(fwVersionReceived(int,int,QString,QByteArray)),
                this, SLOT(fwVersionReceived(int,int,QString,QByteArray)));
    }
}

void PageFirmware::fwUploadStatus(const QString &status, double progress, bool isOngoing)
{
    if (isOngoing) {
        ui->display->setText(tr("%1 (%2 %)").
                             arg(status).
                             arg(progress * 100, 0, 'f', 1));
    } else {
        ui->display->setText(status);
    }

    ui->display->setValue(progress * 100.0);
    ui->uploadButton->setEnabled(!isOngoing);
    ui->cancelButton->setEnabled(isOngoing);
}

void PageFirmware::fwVersionReceived(int major, int minor, QString hw, QByteArray uuid)
{
    QString fwStr;
    QString strUuid = Utility::uuid2Str(uuid, true);

    if (!strUuid.isEmpty()) {
        fwStr += ", UUID: " + strUuid;
    }

    if (major >= 0) {
        fwStr.sprintf("Fw: %d.%d", major, minor);
        if (!hw.isEmpty()) {
            fwStr += ", Hw: " + hw;
        }

        if (!strUuid.isEmpty()) {
            fwStr += "\n" + strUuid;
        }
    }

    ui->currentLabel->setText(fwStr);
    updateHwList(hw);
    updateBlList(hw);
    update();
}

void PageFirmware::updateHwList(QString hw)
{
    ui->hwList->clear();

    QDirIterator it("://res/firmwares");
    while (it.hasNext()) {
        QFileInfo fi(it.next());
        QStringList names = fi.fileName().split("_o_");

        if (fi.isDir() && (hw.isEmpty() || names.contains(hw, Qt::CaseInsensitive))) {
            QListWidgetItem *item = new QListWidgetItem;

            QString name = names.at(0);
            for(int i = 1;i < names.size();i++) {
                name += " & " + names.at(i);
            }

            item->setText(name);
            item->setData(Qt::UserRole, fi.absoluteFilePath());
            ui->hwList->insertItem(ui->hwList->count(), item);
        }
    }

    if (ui->hwList->count() > 0) {
        ui->hwList->setCurrentRow(0);
    }

    updateFwList();
}

void PageFirmware::updateFwList()
{
    ui->fwList->clear();
    QListWidgetItem *item = ui->hwList->currentItem();
    if (item != 0) {
        QString hw = item->data(Qt::UserRole).toString();

        QDirIterator it(hw);
        while (it.hasNext()) {
            QFileInfo fi(it.next());
            if (ui->showNonDefaultBox->isChecked() ||
                    fi.fileName().toLower() == "ennoid-bms.bin") {
                QListWidgetItem *item = new QListWidgetItem;
                item->setText(fi.fileName());
                item->setData(Qt::UserRole, fi.absoluteFilePath());
                ui->fwList->insertItem(ui->fwList->count(), item);
            }
        }
    }

    if (ui->fwList->count() > 0) {
        ui->fwList->setCurrentRow(0);
    }
}

void PageFirmware::updateBlList(QString hw)
{
    ui->blList->clear();

    QDirIterator it("://res/bootloaders");
    while (it.hasNext()) {
        QFileInfo fi(it.next());
        QStringList names = fi.fileName().replace(".bin", "").split("_o_");

        if (!fi.isDir() && (hw.isEmpty() || names.contains(hw, Qt::CaseInsensitive))) {
            QListWidgetItem *item = new QListWidgetItem;

            QString name = names.at(0);
            for(int i = 1;i < names.size();i++) {
                name += " & " + names.at(i);
            }

            item->setText(name);
            item->setData(Qt::UserRole, fi.absoluteFilePath());
            ui->blList->insertItem(ui->blList->count(), item);
        }
    }

    if (ui->blList->count() == 0) {
        QFileInfo generic("://res/bootloaders/generic.bin");
        if (generic.exists()) {
            QListWidgetItem *item = new QListWidgetItem;
            item->setText("generic");
            item->setData(Qt::UserRole, generic.absoluteFilePath());
            ui->blList->insertItem(ui->blList->count(), item);
        }
    }

    if (ui->blList->count() > 0) {
        ui->blList->setCurrentRow(0);
    }
}

void PageFirmware::on_chooseButton_clicked()
{
    QString filename = QFileDialog::getOpenFileName(this,
                                                    tr("Choose Firmware File"), ".",
                                                    tr("Binary files (*.bin)"));

    if (filename.isNull()) {
        return;
    }

    ui->fwEdit->setText(filename);
}

void PageFirmware::on_uploadButton_clicked()
{
    if (mDieBieMS) {
        if (!mDieBieMS->isPortConnected()) {
            QMessageBox::critical(this,
                                  tr("Connection Error"),
                                  tr("The ENNOID-BMS is not connected. Please connect it."));
            return;
        }

        QFile file;

        if (ui->fwTabWidget->currentIndex() == 0) {
            QListWidgetItem *item = ui->fwList->currentItem();
            if (item) {
                file.setFileName(item->data(Qt::UserRole).toString());
            } else {
                if (ui->hwList->count() == 0) {
                    QMessageBox::warning(this,
                                         tr("Upload Error"),
                                         tr("This version of DieBie does not include any firmware "
                                            "for your hardware version. You can either "
                                            "upload a custom file or look for a later version of ENNOID-BMS "
                                            "Tool that might support your hardware."));
                } else {
                    QMessageBox::warning(this,
                                         tr("Upload Error"),
                                         tr("No firmware is selected."));
                }

                return;
            }
        } else if (ui->fwTabWidget->currentIndex() == 1) {
            file.setFileName(ui->fwEdit->text());

            QFileInfo fileInfo(file.fileName());
            if (!(fileInfo.fileName().startsWith("ENNOID")) || !fileInfo.fileName().endsWith(".bin")) {
                QMessageBox::critical(this,tr("Upload Error"),tr("The selected file name seems to be invalid."));
                return;
            }
        } else {
            QListWidgetItem *item = ui->blList->currentItem();

            if (item) {
                file.setFileName(item->data(Qt::UserRole).toString());
            } else {
                if (ui->blList->count() == 0) {
                    QMessageBox::warning(this,
                                         tr("Upload Error"),
                                         tr("This version of ENNOID-BMS does not include any bootloader "
                                            "for your hardware version."));
                } else {
                    QMessageBox::warning(this,
                                         tr("Upload Error"),
                                         tr("No bootloader is selected."));
                }

                return;
            }
        }

        if (!file.open(QIODevice::ReadOnly)) {
            QMessageBox::critical(this,
                                  tr("Upload Error"),
                                  tr("Could not open file. Make sure that the path is valid."));
            return;
        }

        if (file.size() > 400000) {
            QMessageBox::critical(this,
                                  tr("Upload Error"),
                                  tr("The selected file is too large to be a firmware."));
            return;
        }

        QMessageBox::StandardButton reply;
        bool isBootloader = false;

        if (ui->fwTabWidget->currentIndex() == 0 && ui->hwList->count() == 1) {
            reply = QMessageBox::warning(this,
                                         tr("Warning"),
                                         tr("Uploading new firmware will clear all settings on your ENNOID-BMS "
                                            "and you have to do the configuration again. Do you want to "
                                            "continue?"),
                                         QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
        } else if ((ui->fwTabWidget->currentIndex() == 0 && ui->hwList->count() > 1) || ui->fwTabWidget->currentIndex() == 1) {
            reply = QMessageBox::warning(this,
                                         tr("Warning"),
                                         tr("Uploading firmware for the wrong hardware version "
                                            "WILL damage the ENNOID-BMS for sure. Are you sure that you have "
                                            "chosen the correct hardware version?"),
                                         QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
        } else if (ui->fwTabWidget->currentIndex() == 2) {
            reply = QMessageBox::warning(this,
                                         tr("Warning"),
                                         tr("This will attempt to upload a bootloader to the connected ENNOID-BMS. "
                                            "If the connected ENNOID-BMS already has a bootloader this will destroy "
                                            "the bootloader and firmware updates cannot be done anymore. Do "
                                            "you want to continue?"),
                                         QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
            isBootloader = true;
        } else {
            reply = QMessageBox::No;
        }

        if (reply == QMessageBox::Yes) {
            QByteArray data = file.readAll();
            mDieBieMS->commands()->startFirmwareUpload(data, isBootloader);

            QMessageBox::warning(this,
                                 tr("Warning"),
                                 tr("The firmware upload is now ongoing. After the upload has finished you must wait at least "
                                    "10 seconds before unplugging power. Otherwise the firmware will get corrupted and your "
                                    "ENNOID-BMS will become bricked. If that happens you need a SWD programmer to recover it."));
        }
    }
}

void PageFirmware::on_readVersionButton_clicked()
{
    if (mDieBieMS) {
        mDieBieMS->commands()->getFwVersion();
    }
}

void PageFirmware::on_cancelButton_clicked()
{
    if (mDieBieMS) {
        mDieBieMS->commands()->cancelFirmwareUpload();
    }
}

void PageFirmware::on_changelogButton_clicked()
{
    HelpDialog::showHelp(this, "Firmware Changelog", Utility::fwChangeLog());
}
