#include "QLbootDialog.h"
#include "ui_QLbootDialog.h"
#include "QLbootError.h"
#include <QMessageBox>
#include <QFileDialog>
#include <QTime>
#include "QLbootOutRecord.h"
#include <QDialogButtonBox>
#include <QPushButton>
#include <QStringBuilder>
#include <QMessageBox>
#include <QTimer>


QLbootDialog::QLbootDialog(QWidget *parent, const QStringList &args, unsigned finishWaitTime,
                           const QString &lbootExec) :
    QDialog{parent},
    m_lbootProcess{nullptr},
    m_args{args},
    m_finishWaitTime{finishWaitTime},
    m_lboot_exec{lbootExec},
    m_ui{new Ui::QLbootDialog} {

    m_ui->setupUi(this);
    setOutTextVisible(false);

    qRegisterMetaType<QSharedPointer<QLbootOutRecord> >("QSharedPointer<QLbootOutRecord>");

    connect(m_ui->btnDetails, &QPushButton::clicked, this, &QLbootDialog::showDetail);

    m_args.prepend(QStringLiteral("--output-fmt=dsv"));
}

QLbootDialog::~QLbootDialog() {

}

void QLbootDialog::setTerminateWarnMsg(const QString &msg) {
    m_terminate_warn_msg  = msg;
}

void QLbootDialog::startBoot() {
    lbootClearProccess();
    m_ui->bootProgress->setValue(0);
    lbootProcTerminate();
    lbootProcStart();
}

int QLbootDialog::exec() {
    /* если lboot не был запущен, то запускаем его по exec,
     * при этом добавляем задержку таймером, чтобы диалог успел появится
     * к моменту запуска lboot, чтобы можно было вывести сообщение об ошибке,
     * привязанное к окну */
    if (!m_lbootProcess) {        
        QTimer::singleShot(100, this, &QLbootDialog::startBoot);
    }
    return QDialog::exec();
}


void QLbootDialog::lbootProcStart() {
    m_lbootProcess = new QProcess{this};
    connect(m_lbootProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
               this, &QLbootDialog::onProcFinished);
    connect(m_lbootProcess, &QProcess::readyReadStandardOutput, this, &QLbootDialog::onProcDataReady);
    connect(m_lbootProcess, &QProcess::errorOccurred, this, &QLbootDialog::onError);
    m_lbootProcess->start(m_lboot_exec, m_args);
}

void QLbootDialog::lbootProcTerminate() {
    lbootClearProccess(true);
}

void QLbootDialog::lbootClearProccess(bool terminate) {
    if (m_lbootProcess) {
        disconnect(m_lbootProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
                   this, &QLbootDialog::onProcFinished);
        disconnect(m_lbootProcess, &QProcess::readyReadStandardOutput, this, &QLbootDialog::onProcDataReady);
        disconnect(m_lbootProcess, &QProcess::errorOccurred, this, &QLbootDialog::onError);
        if (terminate) {
            m_lbootProcess->terminate();
        }
        m_lbootProcess = nullptr;
    }
}

void QLbootDialog::onProcFinished(int exitCode, QProcess::ExitStatus) {
    QProcess *proc = qobject_cast<QProcess *>(sender());
    if (proc == m_lbootProcess) {
        if (exitCode == 0) {
            m_ui->bootProgress->setValue(firm_start_perc);
            QTime dieTime = QTime::currentTime().addMSecs( m_finishWaitTime );
            while( QTime::currentTime() < dieTime ) {
                QCoreApplication::processEvents( QEventLoop::AllEvents, 100 );
            }

            m_ui->stageLbl->setText(QLbootOutRecord::tr("Done successfully"));
            m_ui->bootProgress->setValue(100);
            Q_EMIT successUpdateFinished();
        }
        m_ui->buttonBox->setEnabled(true);
        m_ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
        lbootClearProccess();
    }
}

void QLbootDialog::onProcDataReady() {
    QProcess *proc = qobject_cast<QProcess *>(sender());
    if (proc) {
        bool restart {false};
        while ((proc == m_lbootProcess) && (m_lbootProcess->canReadLine() && !restart)) {
            const QString line {m_lbootProcess->readLine().trimmed()};
            QSharedPointer<QLbootOutRecord> rec{new QLbootOutRecord{line}};
            if (rec->valid()) {
                if (rec->status() == QLbootOutRecord::statusStart()) {
                    m_ui->stageLbl->setText(rec->stageName());
                    if (rec->stage() == QLbootOutRecord::stageRecovery()) {
                        m_ui->bootProgress->setValue(firm_rec_start_perc);
                    }
                } if (rec->status() == QLbootOutRecord::statusDone()) {
                    if (rec->stage() == QLbootOutRecord::stageRecovery()) {
                        m_ui->bootProgress->setValue(firm_rec_finish_perc);
                    }
                } else if (rec->status() == QLbootOutRecord::statusProgress()) {
                    if (rec->stage() == QLbootOutRecord::stageWriteFirmware()) {
                        const int cpl {rec->param(QLbootOutRecord::paramCplSize()).toInt()};
                        const int size {rec->param(QLbootOutRecord::paramFullSize()).toInt()};
                        const double progr {((double)cpl/size)*(firm_write_end_perc-firm_write_start_perc)
                                    + firm_write_start_perc};
                        m_ui->bootProgress->setValue(progr);
                    }
                } else if (rec->status() == QLbootOutRecord::statusError()) {
                    m_err = QLbootError::error(*rec);
                    if (++m_restart_on_err_cur > m_restart_on_err_max) {
                        QMessageBox::critical(this, tr("Error"),
                                                m_err.msg(),
                                                QMessageBox::Ok, QMessageBox::Ok);
                    } else {
                        restart = true;
                        startBoot();
                    }
                }

                if (!restart) {
                    Q_EMIT progressInfoUpdated(rec, m_ui->bootProgress->value());
                }
            }

            m_ui->outputText->appendPlainText(line);
            if (restart) {
                m_ui->outputText->appendPlainText(QString{});
                m_ui->outputText->appendPlainText(QString{"restart lboot (%1)"}.arg(m_restart_on_err_cur));
            }
        }
    }
}

void QLbootDialog::onError(QProcess::ProcessError) {
    QProcess *proc = qobject_cast<QProcess *>(sender());
    if (proc == m_lbootProcess) {
        lbootClearProccess();

        m_ui->stageLbl->setText(QLbootError::lbootExec().msg());
        if (m_err.isSuccess()) {
            m_err = QLbootError::lbootExec();
            QMessageBox::critical(this, tr("Error"),
                                m_err.msg(),
                                QMessageBox::Ok, QMessageBox::Ok);
        }
        m_ui->buttonBox->setEnabled(true);
    }
}

void QLbootDialog::setOutTextVisible(bool visible) {
    m_ui->outputText->setVisible(visible);
    m_ui->btnDetails->setText(visible ? tr("Hide details...") : tr("Show details..."));
    qApp->processEvents();
    resize(width(), visible ? 450 : 20);
}




void QLbootDialog::showDetail() {
    setOutTextVisible(!m_ui->outputText->isVisible());
}

void QLbootDialog::reject() {
    bool cancel {false};
    /* Обработка варианта, когда идет отмена, которое в процессе выполнения */
    if (m_lbootProcess && (m_lbootProcess->state() == QProcess::ProcessState::Running)) {
        /* Если установлено предупреждение, то выдаем его */
        if (!m_terminate_warn_msg.isEmpty()) {
            QMessageBox msgbox{QMessageBox::Icon::Question,
                    tr("Firmware update temination"), m_terminate_warn_msg,
                               QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, this};
            /* автозакрытие предупреждения, на случай, если пока отвечаем, уже процесс
             * завершиться сам */
            connect(m_lbootProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), &msgbox, &QMessageBox::close);
            msgbox.exec();
            if (msgbox.standardButton(msgbox.clickedButton()) != QMessageBox::Yes) {
                cancel = true;
            }
        }
    }

    if (!cancel) {
        /* если процесс все еще выполняется на момент закрытия, то ставим флаг о том, что
         * он был завершен принудительно. Тут делаем повторную проверку, на случай если
         * он уже сам завершился, пока выводили предупреждение */
        if (m_lbootProcess && (m_lbootProcess->state() == QProcess::ProcessState::Running)) {
            m_terminated = true;
            lbootProcTerminate();
        }
        QDialog::reject();
    }

}
