/*
    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 "mainwindow.h"

namespace {

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    QString str;
    QString typeStr;
    bool isBad = false;

    switch (type) {
    case QtDebugMsg: typeStr = "DEBUG"; break;
        //    case QtInfoMsg: typeStr = "INFO"; break;
    case QtWarningMsg: typeStr = "WARNING"; isBad = true; break;
    case QtCriticalMsg: typeStr = "CRITICAL"; isBad = true; break;
    case QtFatalMsg: typeStr = "FATAL"; isBad = true; break;

    default:
        break;
    }

    str.sprintf("%s (%s:%u %s): %s", typeStr.toLocal8Bit().data(),
                context.file, context.line, context.function, localMsg.constData());

    if (PageDebugPrint::currentMsgHandler) {
        QString strTmp;
        if (isBad) {
            strTmp = "<font color=\"red\">" + str + "</font><br>";
        } else {
            strTmp = str + "<br>";
        }

        QMetaObject::invokeMethod(PageDebugPrint::currentMsgHandler, "printConsole",
                                  Qt::QueuedConnection, Q_ARG(QString, strTmp));
    }

    printf("%s\n", str.toLocal8Bit().data());
    fflush(stdout);
}
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    QLoggingCategory::setFilterRules("qt.network.ssl.warning=false");

    mVersion = QString::number(DT_VERSION);
    mDieBieMS = new BMSInterface(this);
    mStatusInfoTime = 0;
    mStatusLabel = new QLabel(this);
    ui->statusBar->addPermanentWidget(mStatusLabel);
    mTimer = new QTimer(this);
    mKeyLeft = false;
    mKeyRight = false;
    mbmsConfigRead = false;

    connect(mTimer, SIGNAL(timeout()),this, SLOT(timerSlot()));
    connect(mDieBieMS, SIGNAL(statusMessage(QString,bool)),this, SLOT(showStatusInfo(QString,bool)));
    connect(mDieBieMS, SIGNAL(messageDialog(QString,QString,bool,bool)),this, SLOT(showMessageDialog(QString,QString,bool,bool)));
    connect(mDieBieMS, SIGNAL(serialPortNotWritable(QString)),this, SLOT(serialPortNotWritable(QString)));
    connect(mDieBieMS->commands(), SIGNAL(bmsConfigCheckResult(QStringList)),this, SLOT(bmsConfigCheckResult(QStringList)));
    connect(ui->actionAboutQt, SIGNAL(triggered(bool)),qApp, SLOT(aboutQt()));

    // Remove the menu with the option to hide the toolbar
    ui->mainToolBar->setContextMenuPolicy(Qt::PreventContextMenu);
    ui->actionCanFwd->setVisible(false);
    ui->pageLabel->setVisible(false);
    ui->menuTerminal->menuAction()->setVisible(false);
    ui->menuTools->menuAction()->setVisible(false);
    ui->menuHelp->menuAction()->setVisible(false);

    mDieBieMS->bmsConfig()->loadParamsXml("://res/config.xml");
    mDieBieMS->infoConfig()->loadParamsXml("://res/info.xml");
    reloadPages();

    connect(mDieBieMS->bmsConfig(), SIGNAL(updated()),this, SLOT(bmsconfUpdated()));

    qInstallMessageHandler(myMessageOutput);

    mTimer->start(20);

    // Restore size and position
    if (mSettings.contains("mainwindow/size")) {
        resize(mSettings.value("mainwindow/size").toSize());
    }

    if (mSettings.contains("mainwindow/position")) {
        move(mSettings.value("mainwindow/position").toPoint());
    }

    if (mSettings.contains("mainwindow/maximized")) {
        bool maximized = mSettings.value("mainwindow/maximized").toBool();
        if (maximized) {
            showMaximized();
        }
    }

    mPageDebugPrint->printConsole("ENNOID-BMS Tool " + mVersion + " started<br>");
}

MainWindow::~MainWindow()
{
    // Save settings
    mSettings.setValue("version", mVersion);
    mSettings.setValue("mainwindow/position", pos());
    mSettings.setValue("mainwindow/maximized", isMaximized());

    if (!isMaximized()) {
        mSettings.setValue("mainwindow/size", size());
    }

    delete ui;
}

void MainWindow::timerSlot()
{
    // Update status label
    if (mStatusInfoTime) {
        mStatusInfoTime--;
        if (!mStatusInfoTime) {
            mStatusLabel->setStyleSheet(qApp->styleSheet());
        }
    } else {
        QString str = mDieBieMS->getConnectedPortName();
        if (str != mStatusLabel->text()) {
            mStatusLabel->setText(mDieBieMS->getConnectedPortName());
            static QString statusLast = "";
            if (str != statusLast) {
                mPageDebugPrint->printConsole("Status: " + str + "<br>");
                statusLast = str;
                if ( str != "Not connected" )
                {
                    ui->actionRtData->setChecked(true);
                }
            }
        }
    }

    // CAN fwd
    if (ui->actionCanFwd->isChecked() != mDieBieMS->commands()->getSendCan()) {
        ui->actionCanFwd->setChecked(mDieBieMS->commands()->getSendCan());
    }

    // RT value data only every 5 iterations. Cells, aux & expansion_temp only every 20 iterations
    if (ui->actionRtData->isChecked()) {
        static int values_cnt = 0;
        static int cells_cnt = 0;
        static int aux_cnt = 0;
        static int exp_temp_cnt = 0;

        values_cnt++;
        if(values_cnt >= 5) {
            values_cnt = 0;
            mDieBieMS->commands()->getValues();
        }

        cells_cnt++;
        if(cells_cnt >= 20) {
            cells_cnt = 0;
            mDieBieMS->commands()->getCells();
        }

        aux_cnt++;
        if(aux_cnt >= 20) {
            aux_cnt = 0;
            mDieBieMS->commands()->getAux();
        }

        exp_temp_cnt++;
        if(exp_temp_cnt >= 20) {
            exp_temp_cnt = 0;
            mDieBieMS->commands()->getExpansionTemp();
        }
    }

    // Send alive command once every 10 iterations
    if (ui->actionSendAlive->isChecked()) {
        static int alive_cnt = 0;
        alive_cnt++;
        if (alive_cnt >= 10) {
            alive_cnt = 0;
            mDieBieMS->commands()->sendAlive();
        }
    }

    // Read configuration it isn't read since starting ENNOID-BMS Tool
    if (mDieBieMS->isPortConnected()) {
        static int conf_cnt = 0;
        conf_cnt++;
        if (conf_cnt >= 20) {
            conf_cnt = 0;
            if (!mbmsConfigRead) {
                mDieBieMS->commands()->getBMSconf();
            }
        }
    }

    // Disable all data streaming when uploading firmware
    if (mDieBieMS->commands()->getFirmwareUploadProgress() > 0.1) {
        ui->actionSendAlive->setChecked(false);
        ui->actionRtData->setChecked(false);
    }

    // Run startup checks
    static bool has_run_start_checks = false;
    if (!has_run_start_checks) {
        has_run_start_checks = true;
        checkUdev();
        Utility::checkVersion(mDieBieMS);
    }
}

void MainWindow::showStatusInfo(QString info, bool isGood)
{
    if (isGood) {
        mStatusLabel->setStyleSheet("QLabel { background-color : lightgreen; color : black; }");
        mPageDebugPrint->printConsole("Status: " + info + "<br>");
    } else {
        mStatusLabel->setStyleSheet("QLabel { background-color : red; color : black; }");
        mPageDebugPrint->printConsole("<font color=\"red\">Status: " + info + "</font><br>");
    }

    mStatusInfoTime = 80;
    mStatusLabel->setText(info);
}

void MainWindow::showMessageDialog(const QString &title, const QString &msg, bool isGood, bool richText)
{
    (void)richText;

    if (isGood) {
        QMessageBox::information(this, title, msg);
    } else {
        QMessageBox::warning(this, title, msg);
    }
}

void MainWindow::serialPortNotWritable(const QString &port)
{
    (void)port;

#ifdef Q_OS_LINUX
    QMessageBox::StandardButton reply;
    reply = QMessageBox::warning(this,
                                 tr("Connect Serial Port"),
                                 tr("The serial port is not writable. This can usually be fixed by "
                                    "adding your user to the dialout, uucp and/or lock groups. Would "
                                    "you like to do that?"),
                                 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

    if (reply == QMessageBox::Yes) {
        QString name = qgetenv("USER");
        if (name.isEmpty()) {
            name = qgetenv("USERNAME");
        }

        bool hasDialout = !runCmd("getent", QStringList() << "group" << "dialout").isEmpty();
        bool hasUucp = !runCmd("getent", QStringList() << "group" << "uucp").isEmpty();
        bool hasLock = !runCmd("getent", QStringList() << "group" << "lock").isEmpty();

        QString grps;
        if (hasDialout) {
            grps += "dialout";
        }

        if (hasUucp) {
            if (!grps.isEmpty()) {
                grps += ",";
            }
            grps += "uucp";
        }

        if (hasLock) {
            if (!grps.isEmpty()) {
                grps += ",";
            }
            grps += "lock";
        }

        QProcess process;
        process.setEnvironment(QProcess::systemEnvironment());
        process.start("pkexec", QStringList() << "usermod" << "-aG" << grps << name);
        waitProcess(process);
        if (process.exitCode() == 0) {
            showMessageDialog(tr("Command Result"),
                              tr("Result from command:\n\n"
                                 "%1\n"
                                 "You have to reboot for this "
                                 "change to take effect.").
                              arg(QString(process.readAllStandardOutput())),
                              true, false);
        } else {
            showMessageDialog(tr("Command Result"),
                              tr("Running command failed."),
                              false, false);
        }
        process.close();
    }
#endif
}

void MainWindow::bmsconfUpdated()
{
    mbmsConfigRead = true;
}

void MainWindow::bmsConfigCheckResult(QStringList paramsNotSet)
{
    if (!paramsNotSet.isEmpty()) {
        ParamDialog::showParams(tr("Parameters truncated"),
                                tr("The following parameters were truncated because they were set outside "
                                   "of their allowed limits."),
                                mDieBieMS->bmsConfig(),
                                paramsNotSet,
                                this);
    }
}

void MainWindow::on_actionReconnect_triggered()
{
    mDieBieMS->reconnectLastPort();
}

void MainWindow::on_actionDisconnect_triggered()
{
    mDieBieMS->disconnectPort();
}

void MainWindow::on_actionReboot_triggered()
{
    mDieBieMS->commands()->reboot();
}

void MainWindow::on_actionReadBMScconf_triggered()
{
    mDieBieMS->commands()->getBMSconf();
}

void MainWindow::on_actionReadBMScconfDefault_triggered()
{
    mDieBieMS->commands()->getBMSconfDefault();
}

void MainWindow::on_actionWriteBMScconf_triggered()
{
    mDieBieMS->commands()->setBMSconf();
}

void MainWindow::on_actionSaveBMSConfXml_triggered()
{
    QString path;
    path = QFileDialog::getSaveFileName(this,
                                        tr("Choose where to save the motor configuration XML file"),
                                        ".",
                                        tr("Xml files (*.xml)"));

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

    if (!path.toLower().endsWith(".xml")) {
        path += ".xml";
    }

    bool res = mDieBieMS->bmsConfig()->saveXml(path, "bmsConfiguration");

    if (res) {
        showStatusInfo("Saved motor configuration", true);
    } else {
        showMessageDialog(tr("Save motor configuration"),
                          tr("Could not save motor configuration:<BR>"
                             "%1").arg(mDieBieMS->bmsConfig()->xmlStatus()),
                          false, false);
    }
}

void MainWindow::on_actionLoadBMSConfXml_triggered()
{
    QString path;
    path = QFileDialog::getOpenFileName(this,
                                        tr("Choose motor configuration file to load"),
                                        ".",
                                        tr("Xml files (*.xml)"));

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

    bool res = mDieBieMS->bmsConfig()->loadXml(path, "bmsConfiguration");

    if (res) {
        showStatusInfo("Loaded motor configuration", true);
    } else {
        showMessageDialog(tr("Load motor configuration"),
                          tr("Could not load motor configuration:<BR>"
                             "%1").arg(mDieBieMS->bmsConfig()->xmlStatus()),
                          false, false);
    }
}

void MainWindow::on_actionExit_triggered()
{
    qApp->exit();
}

void MainWindow::on_actionAbout_triggered()
{
    QMessageBox::about(this, "ENNOID-BMS Tool", Utility::aboutText());
}

void MainWindow::on_actionLibrariesUsed_triggered()
{
    QMessageBox::about(this, "Libraries Used",
                       tr("<b>Icons<br>"
                          "<a href=\"https://icons8.com/\">https://icons8.com/</a><br><br>"
                          "<b>Plotting<br>"
                          "<a href=\"http://qcustomplot.com/\">http://qcustomplot.com/</a>"));
}

void MainWindow::addPageItem(QString name, QString icon, QString groupIcon, bool bold, bool indented)
{
    QListWidgetItem *item = new QListWidgetItem();
    ui->pageList->addItem(item);
    PageListItem *li = new PageListItem(name, icon, groupIcon, this);
    li->setBold(bold);
    li->setIndented(indented);
    ui->pageList->setItemWidget(item, li);
}

void MainWindow::saveParamFileDialog(QString conf, bool wrapIfdef)
{
    ConfigParams *params = 0;

    if (conf.toLower() == "bmsconf") {
        params = mDieBieMS->bmsConfig();
    } else {
        qWarning() << "Invalid conf" << conf;
        return;
    }

    QString path;
    path = QFileDialog::getSaveFileName(this,tr("Choose where to save the configuration header file"),".",tr("h files (*.h)"));

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

    if (path.contains(" ")) {
        showMessageDialog(tr("Save header"),tr("Spaces are not allowed in the filename."),false, false);
        return;
    }

    if (!path.toLower().endsWith(".h")) {
        path += ".h";
    }

    bool res = params->saveCDefines(path, wrapIfdef);

    if (res) {
        showStatusInfo("Saved C header", true);
    } else {
        showMessageDialog(tr("Save header"),
                          tr("Could not save header"),
                          false, false);
    }
}

void MainWindow::showPage(const QString &name)
{
    for (int i = 0;i < ui->pageList->count();i++) {
        PageListItem *p = (PageListItem*)(ui->pageList->itemWidget(ui->pageList->item(i)));
        if (p->name() == name) {
            ui->pageList->setCurrentRow(i);
            break;
        }
    }
}

void MainWindow::reloadPages()
{
    // Remove pages (if any)
    ui->pageList->clear();
    while (ui->pageWidget->count() != 0) {
        QWidget* widget = ui->pageWidget->widget(0);
        ui->pageWidget->removeWidget(widget);
        widget->deleteLater();
    }

#if 0
    mPageWelcome = new PageWelcome(this);
    mPageWelcome->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageWelcome);
    addPageItem(tr("Welcome"), "://res/icons/Home-96.png", "", true);
#endif

    mPageConnection = new PageConnection(this);
    mPageConnection->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageConnection);
    addPageItem(tr("Connection"), "://res/icons/Connected-96.png", "", true);

    mPageFirmware = new PageFirmware(this);
    mPageFirmware->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageFirmware);
    addPageItem(tr("Firmware"), "://res/icons/Electronics-96.png", "", true);

    mPageMasterSettings = new PageMasterSettings(this);
    mPageMasterSettings->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageMasterSettings);
    addPageItem(tr("Settings"), "://res/icons/Outgoing Data-96.png", "", true);

#if 0 // disable
    mPageMasterGeneral = new PageMasterGeneral(this);
    mPageMasterGeneral->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageMasterGeneral);
    addPageItem(tr("General"), "://res/icons/Horizontal Settings Mixer-96.png","", false, true);
#endif

#if 0 // disable
    mPageMasterCell = new PageMasterCell(this);
    mPageMasterCell->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageMasterCell);
    addPageItem(tr("Cell Management"), "://res/icons/batteries-96.png","", false, true);
#endif

#if 0 // disable
    mPageMasterSwitch = new PageMasterSwitch(this);
    mPageMasterSwitch->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageMasterSwitch);
    addPageItem(tr("Switch"), "://res/icons/Toggle Off-96_2.png","", false, true);
#endif

#if 0 // disable
    mPageMasterSignals = new PageMasterSignals(this);
    mPageMasterSignals->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageMasterSignals);
    addPageItem(tr("Signals"), "://res/icons/bldc.png","", false, true);
#endif

#if 0
    mPageMasterDisplay = new PageMasterDisplay(this);
    mPageMasterDisplay->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageMasterDisplay);
    addPageItem(tr("Display"), "://res/icons/Calculator-96.png","", false, true);
#endif

/*
    mSlaveSettings = new PageSlaveSettings(this);
    mSlaveSettings->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mSlaveSettings);
    addPageItem(tr("Expansion Board Settings"), "://res/icons/Outgoing Data-96.png", "", true);

    mPageSlaveGeneral = new PageSlaveGeneral(this);
    mPageSlaveGeneral->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageSlaveGeneral);
    addPageItem(tr("General"), "://res/icons/Horizontal Settings Mixer-96.png","", false, true);

    mPageSlaveSwitch = new PageSlaveSwitch(this);
    mPageSlaveSwitch->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageSlaveSwitch);
    addPageItem(tr("Switch"), "://res/icons/Toggle Off-96_2.png","", false, true);

    mPageSlaveIO = new PageSlaveIO(this);
    mPageSlaveIO->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageSlaveIO);
    addPageItem(tr("IO"), "://res/icons/Rs 232 Male-96.png","", false, true);

    mPageSlaveFANs = new PageSlaveFAN(this);
    mPageSlaveFANs->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageSlaveFANs);
    addPageItem(tr("FANs"), "://res/icons/fan-96.png","", false, true);
*/
    mPageDataAnalysis = new PageDataAnalysis(this);
    mPageDataAnalysis->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageDataAnalysis);
    addPageItem(tr("Data Analysis"), "://res/icons/Line Chart-96.png", "", true);

    mPageRtData = new PageRtData(this);
    mPageRtData->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageRtData);
    addPageItem(tr("Realtime Data"), "://res/icons/rt_off.png", "", false, true);

    mPageTerminal = new PageTerminal(this);
    mPageTerminal->setDieBieMS(mDieBieMS);
    ui->pageWidget->addWidget(mPageTerminal);
    addPageItem(tr("Terminal"), "://res/icons/Console-96.png", "", true);

    mPageDebugPrint = new PageDebugPrint(this);
    ui->pageWidget->addWidget(mPageDebugPrint);
#if 0
    addPageItem(tr("Debug Console"), "://res/icons/Bug-96.png", "", true);
#endif

#if 0
    mPageSettings = new PageSettings(this);
    ui->pageWidget->addWidget(mPageSettings);
    addPageItem(tr("Tool Settings"), "://res/icons/Settings-96.png", "", true);
#endif

    // Adjust sizes
    QFontMetrics fm(this->font());
    int width = fm.width("Welcome & Wizards++++++");
    int height = fm.height();

    for(int i = 0; i < ui->pageList->count(); i++) {
        QListWidgetItem *item = ui->pageList->item(i);
        item->setSizeHint(QSize(item->sizeHint().width(), height));
    }

    ui->pageList->setMinimumWidth(width);
    ui->pageList->setMaximumWidth(width);
    ui->pageLabel->setMaximumWidth(width);
    ui->pageLabel->setMaximumHeight((394 * width) / 1549);

    ui->pageList->setCurrentRow(0);
    ui->pageWidget->setCurrentIndex(0);
}

void MainWindow::checkUdev()
{
    // Check if udev rules for modemmanager are installed
#ifdef Q_OS_LINUX
    QFileInfo fi_mm("/lib/udev/rules.d/77-mm-usb-device-blacklist.rules");
    if (fi_mm.exists()) {
        QFileInfo fi_vesc("/lib/udev/rules.d/45-vesc.rules");
        if (!fi_vesc.exists()) {
            QMessageBox::StandardButton reply;
            reply = QMessageBox::information(this,
                                             tr("Modemmenager"),
                                             tr("It looks like modemmanager is installed on your system, and that "
                                                "there are no ENNOID-BMS udev rules installed. This will cause a delay "
                                                "from when you plug in the ENNOID-BMS until you can use it. Would you like "
                                                "to add a udev rule to prevent modemmanager from grabbing the ENNOID-BMS?"),
                                             QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

            if (reply == QMessageBox::Yes) {
                QFile f_vesc(QDir::temp().absoluteFilePath(fi_vesc.fileName()));
                if (!f_vesc.open(QIODevice::WriteOnly | QIODevice::Text)) {
                    showMessageDialog(tr("Create File Error"),
                                      f_vesc.errorString(),
                                      false, false);
                    return;
                }

                f_vesc.write("# Prevent modemmanager from grabbing the ENNOID-BMS\n"
                             "ATTRS{idVendor}==\"0483\", ATTRS{idProduct}==\"5740\", ENV{ID_MM_DEVICE_IGNORE}=\"1\"\n");
                f_vesc.close();

                QFileInfo fi_new(f_vesc);
                QProcess process;
                process.setEnvironment(QProcess::systemEnvironment());
                process.start("pkexec", QStringList() <<
                              "mv" <<
                              fi_new.absoluteFilePath() <<
                              fi_vesc.absolutePath());
                waitProcess(process);

                if (process.exitCode() == 0) {
                    QMessageBox::StandardButton reply;
                    reply = QMessageBox::information(this,
                                                     tr("Modemmenager"),
                                                     tr("The udev rule was created successfully. Would you like "
                                                        "to reload udev to apply the new rule?"),
                                                     QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

                    if (reply == QMessageBox::Yes) {
                        QProcess process;
                        process.setEnvironment(QProcess::systemEnvironment());
                        process.start("pkexec", QStringList() <<
                                      "udevadm" <<
                                      "control" <<
                                      "--reload-rules");
                        waitProcess(process);

                        if (process.exitCode() == 0) {
                            showMessageDialog(tr("Command Result"),
                                              tr("Reloaded udev rules sucessfully."),
                                              true, false);
                        } else {
                            QString out = process.readAll();

                            if (out.isEmpty()) {
                                showMessageDialog(tr("Command Result"),
                                                  tr("Could not reload udev rules. A reboot is probably "
                                                     "required for this change to take effect."),
                                                  false, false);
                            }
                        }
                        process.close();
                    }
                } else {
                    showMessageDialog(tr("Command Result"),
                                      tr("Could not move rules file:\n\n"
                                         "%1").
                                      arg(QString(process.readAllStandardOutput())),
                                      false, false);
                }
                process.close();
            }
        }
    }
#endif
}

bool MainWindow::waitProcess(QProcess &process, bool block, int timeoutMs)
{
    bool wasEnables = isEnabled();
    bool killed = false;

    if (block) {
        setEnabled(false);
    }

    process.waitForStarted();

    QEventLoop loop;
    QTimer timeoutTimer;
    timeoutTimer.setSingleShot(true);
    timeoutTimer.start(timeoutMs);
    connect(&process, SIGNAL(finished(int)), &loop, SLOT(quit()));
    connect(&timeoutTimer, SIGNAL(timeout()), &loop, SLOT(quit()));
    loop.exec();

    if (process.state() == QProcess::Running) {
        process.kill();
        process.waitForFinished();
        killed = true;
    }

    setEnabled(wasEnables);

    return !killed;
}

QString MainWindow::runCmd(QString cmd, QStringList args)
{
    QProcess process;
    process.setEnvironment(QProcess::systemEnvironment());
    process.start(cmd, args);
    waitProcess(process);
    QString res = process.readAllStandardOutput();
    process.close();
    return res;
}

void MainWindow::on_pageList_currentRowChanged(int currentRow)
{
    if (currentRow >= 0) {
        ui->pageWidget->setCurrentIndex(currentRow);
    }
}

void MainWindow::on_actionParameterEditorBMScconf_triggered()
{
    ParameterEditor *p = new ParameterEditor(this);
    p->setAttribute(Qt::WA_DeleteOnClose);
    p->setParams(mDieBieMS->bmsConfig());
    p->show();
}

void MainWindow::on_actionParameterEditorInfo_triggered()
{
    ParameterEditor *p = new ParameterEditor(this);
    p->setAttribute(Qt::WA_DeleteOnClose);
    p->setParams(mDieBieMS->infoConfig());
    p->show();
}

void MainWindow::on_actionSaveBMSConfigurationHeader_triggered()
{
    saveParamFileDialog("bmsconf", false);
}

void MainWindow::on_actionSaveBMSConfigurationHeaderWrap_triggered()
{
    saveParamFileDialog("bmsconf", true);
}

void MainWindow::on_actionTerminalPrintFaults_triggered()
{
    mDieBieMS->commands()->sendTerminalCmd("faults");
    showPage("DieBieMS Terminal");
}

void MainWindow::on_actionTerminalShowHelp_triggered()
{
    mDieBieMS->commands()->sendTerminalCmd("help");
    showPage("DieBieMS Terminal");
}

void MainWindow::on_actionTerminalClear_triggered()
{
    mPageTerminal->clearTerminal();
    showPage("DieBieMS Terminal");
}

void MainWindow::on_actionTerminalPrintThreads_triggered()
{
    mDieBieMS->commands()->sendTerminalCmd("threads");
    showPage("DieBieMS Terminal");
}

void MainWindow::on_actionCanFwd_toggled(bool arg1)
{
    mDieBieMS->commands()->setSendCan(arg1);
}

void MainWindow::on_actionSafetyInformation_triggered()
{
    HelpDialog::showHelp(this, mDieBieMS->infoConfig(), "wizard_startup_usage");
}

void MainWindow::on_actionWarrantyStatement_triggered()
{
    HelpDialog::showHelp(this, mDieBieMS->infoConfig(), "wizard_startup_warranty");
}

void MainWindow::on_actionDieBieMSToolChangelog_triggered()
{
    HelpDialog::showHelp(this, "ENNOID-BMS Tool Changelog", Utility::vescToolChangeLog());
}

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

void MainWindow::on_actionLicense_triggered()
{
    HelpDialog::showHelp(this, mDieBieMS->infoConfig(), "gpl_text");
}

void MainWindow::on_actionStoreBMScconf_triggered()
{
    mDieBieMS->commands()->storeBMSConfig();
}

void MainWindow::on_actionCalibrateZero_triggered()
{
    if ( mDieBieMS == NULL ) {
        return;
    }

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

    mDieBieMS->commands()->sendTerminalCmd("setZeroCurrent");
    QTimer::singleShot(1000, [this](){
        this->mDieBieMS->commands()->sendTerminalCmd("config_write");
    });
}
