#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "worker.h"
#include "utils.h"
#include "progress_event.h"
#include "update_dc_event.h"
#include "plugins/interfaces.h"

#include <QDateTime>
#include <QMessageBox>
#include <QDebug>
#include <QScrollBar>



#if EMERGENCY_LEAVE_BL==1
static int step = 0;

void MainWindow::emergencyLeaveBL() {
    //
    qCritical() << __func__ << step;
    switch(step) {
    case 0:
        if (m_hw->dc_openSerial(5, "115200", "ttymxc2", 1)) {
            qCritical() << __func__ << "open ok";
            step++;
            QThread::msleep(2000);
            m_hw->dc_autoRequest(false);
            emit leaveBL();
        }
        break;
    case 1:
        m_hw->bl_rebootDC();
        QThread::msleep(1000);
        qCritical() << __func__ << "reboot ok" << QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
        step++;
        emit leaveBL();
        break;
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
        m_hw->bl_startBL();
        QThread::msleep(1000);
        qCritical() << __func__ << "start" << QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
        step++;
        emit leaveBL();
        break;
    case 7:
    case 9:
    case 11:
    case 13:
    case 15:
        m_hw->bl_checkBL();
        qCritical() << __func__ << "check" << QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
        QThread::msleep(1500);
        ++step;
        emit leaveBL();
        break;
    case 8:
    case 10:
    case 12:
    case 14:
    case 16:
        qCritical() << __func__ << "is Up..." << QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
        if (m_hw->bl_isUp()) {
            qCritical() << __func__ << "is Up...OK" << step << QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
            QThread::msleep(5000);
            step = 16;
        } else {
            qCritical() << __func__ << "is Up...NO" << step << QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
        }
        ++step;
        emit leaveBL();
        break;
    case 17:
    case 18:
    case 19:
        qCritical() << __func__ << "stop" << QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
        m_hw->bl_stopBL();
        QThread::msleep(1000);
        //m_hw->dc_closeSerial();
        ++step;
        emit leaveBL();
        break;
    }
}
#endif

MainWindow::MainWindow(hwinf *hw, Worker *worker, Update *update, QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , m_hw(hw)
    , m_worker(worker)
    , m_width(70)
    , m_progressRunning(false)
    , m_progressValue(0)
    , m_update(update)
    , m_updateStep(UpdateDcEvent::UpdateStep::NONE) {

#if EMERGENCY_LEAVE_BL==1
    QTimer *t = new QTimer(this);
    connect(t, SIGNAL(timeout()), this, SLOT(emergencyLeaveBL()));
    connect(this, SIGNAL(leaveBL()), this, SLOT(emergencyLeaveBL()), Qt::QueuedConnection);
    t->setSingleShot(true);
    t->start(1000);
    return;
#endif

    this->setStatusBar(new QStatusBar(this));
    QFont f;
    f.setStyleHint(QFont::Monospace);
    f.setWeight(QFont::Bold);
    f.setFamily("Misc Fixed");
    f.setPixelSize(12);
    this->statusBar()->setFont(f);

    ui->setupUi(this);

    ui->updateProgress->setRange(0, 100);
    ui->updateProgress->reset();

    QStringList lst;
    QString start = QDateTime::currentDateTime().toString(Qt::ISODate);
    lst << QString("Start: ") + start.leftJustified(m_width-10);
    lst << QString("").leftJustified(m_width-3, '=');
    lst << QString("Machine number  : %1 ").arg(m_worker->machineNr()).leftJustified(m_width-3);
    lst << QString("Customer number : %1 ").arg(m_worker->customerNr()).leftJustified(m_width-3);
    lst << QString("Zone number     : %1 (%2)").arg(m_worker->zoneNr()).arg(Utils::zoneName(m_worker->zoneNr())).leftJustified(m_width-3);
    lst << QString("APISM version   : %1").arg(m_worker->apismVersion()).leftJustified(m_width-3);
    lst << QString("").leftJustified(m_width-3, '=');

    ui->updateStatus->setText(lst.join('\n'));
    ui->updateStatus->setEnabled(true);
    // ui->updateStatus->installEventFilter(this);

    m_startTimer = new QTimer(this);
    connect(m_startTimer, SIGNAL(timeout()), m_worker, SLOT(update()));
    m_startTimer->setSingleShot(true);
    m_startTimer->start(1000);

    m_exitTimer = new QTimer(this);
    connect(m_exitTimer, SIGNAL(timeout()), ui->exit, SLOT(click()));
    m_exitTimer->setSingleShot(true);
    m_exitTimer->start(1800 * 1000);

    connect(ui->exit, SIGNAL(clicked()),this,SLOT(onQuit()));
    connect(m_worker, SIGNAL(disableExit()),this,SLOT(onDisableExit()));
    connect(m_worker, SIGNAL(enableExit()),this,SLOT(onEnableExit()));
    connect(m_worker, SIGNAL(stopStartTimer()),this,SLOT(onStopStartTimer()));
    connect(m_worker, SIGNAL(restartExitTimer()),this,SLOT(onRestartExitTimer()));
    connect(m_worker, SIGNAL(appendText(QString,QString)),this,SLOT(onAppendText(QString,QString)));
    connect(m_worker, SIGNAL(showErrorMessage(QString,QString)),this, SLOT(onShowErrorMessage(QString,QString)));
    connect(m_worker, SIGNAL(showStatusMessage(QString,QString)),this, SLOT(onShowStatusMessage(QString,QString)));
    connect(m_worker, SIGNAL(replaceLast(QString,QString)),this,SLOT(onReplaceLast(QString,QString)));
    connect(m_worker, SIGNAL(replaceLast(QStringList,QString)),this, SLOT(onReplaceLast(QStringList,QString)));
}

MainWindow::~MainWindow() {
    delete m_startTimer;
    delete m_exitTimer;
    delete ui;
}

void MainWindow::customEvent(QEvent *event) {
    if (event->type() == ProgressEvent::type()) {
        ProgressEvent *pevent = (ProgressEvent *)event;
        int const progress = pevent->progressPercent();
        QObject const *sender = pevent->sender();
        if (sender == this) {
            switch(progress) {
            case 0: {
                ui->updateProgress->reset();
            } break;
            case START_PROGRESS_LOOP: {
                m_progressRunning = true;
                ui->updateProgress->reset();
                m_progressValue = 10;
                QApplication::postEvent(this, new ProgressEvent(this, m_progressValue));
            } break;
            case STOP_PROGRESS_LOOP: {
                m_progressRunning = false;
                m_progressValue -= 10;
                m_worker->setProgress(m_progressValue/10);
            } break;
            default: {
                    if (m_progressRunning) {
                        m_progressValue = progress;
                        ui->updateProgress->setValue(progress/10);
                        QApplication::postEvent(this, new ProgressEvent(this, progress+10));
                        QThread::msleep(500);
                    }
                }
            }
        } else
        if (sender == m_worker) {
            switch(progress) {
            case 0: {
                ui->updateProgress->reset();
            } break;
            case START_PROGRESS_LOOP: {
                QApplication::postEvent(this, new ProgressEvent(this, START_PROGRESS_LOOP));
            } break;
            case STOP_PROGRESS_LOOP: {
                QApplication::postEvent(this, new ProgressEvent(this, STOP_PROGRESS_LOOP));
            } break;
            default:{
                ui->updateProgress->setValue(progress);
            }}
        } else {
            qCritical() << "!!! UNKNOWN SENDER !!!";
        }
    } else
    if (event->type() == UpdateDcEvent::type()) {
        UpdateDcEvent *pevent = (UpdateDcEvent *)event;
        UpdateDcEvent::UpdateStep const updateStep = pevent->updateStep();
        QObject const *sender = pevent->sender();
        if (sender == m_worker) {
            QDateTime const &recv = QDateTime::currentDateTime();
            QDateTime const &send = pevent->sendDateTime();
            qint64 const delay = recv.toMSecsSinceEpoch() - send.toMSecsSinceEpoch();
            switch(updateStep) {
            case UpdateDcEvent::UpdateStep::NONE:
                break;
            case UpdateDcEvent::UpdateStep::DC_REBOOT: {
                m_hw->bl_rebootDC();
                QString msg = QDateTime::currentDateTime().toString(Qt::ISODateWithMs)
                          + QString(": reset device controller (delay=%1ms").arg(delay);
                emit m_worker->showStatusMessage("dc update", msg);
                Utils::printInfoMsg(msg.toUpper());
                m_updateStep = UpdateDcEvent::UpdateStep::DC_REBOOT;
            } break;
            case UpdateDcEvent::UpdateStep::BL_START: {
                QString const &msg = recv.toString(Qt::ISODateWithMs)
                    + QString(": start bootloader (%1, delay=%2ms)").arg(pevent->count()).arg(delay);
                emit m_worker->showStatusMessage("dc update", msg);
                Utils::printInfoMsg(msg.toUpper());
                m_hw->bl_startBL();
                if (pevent->count() == BL_START_COUNT) {
                    m_updateStep = UpdateDcEvent::UpdateStep::BL_START;
                }
            } break;
            case UpdateDcEvent::UpdateStep::BL_CHECK: {
                if (m_updateStep != UpdateDcEvent::UpdateStep::BL_IS_UP) {
                    QString const &msg = recv.toString(Qt::ISODateWithMs)
                        + QString(": request bootloader version (%1, delay=%2ms)").arg(pevent->count()).arg(delay);
                    emit m_worker->showStatusMessage("dc update", msg);
                    Utils::printInfoMsg(msg.toUpper());
                    m_hw->bl_checkBL();
                    //m_updateStep = UpdateDcEvent::UpdateStep::BL_CHECK;
                }
            } break;
            case UpdateDcEvent::UpdateStep::BL_IS_UP: {
                QString msg = recv.toString(Qt::ISODateWithMs)
                    + QString(": check running bootloader (%1, delay=%2ms)").arg(pevent->count()).arg(delay);
                emit m_worker->showStatusMessage("dc update", msg);
                Utils::printInfoMsg(msg.toUpper());
                if (m_updateStep != UpdateDcEvent::UpdateStep::BL_IS_UP) {
                    if (m_hw->bl_isUp()) {
                        msg = recv.toString(Qt::ISODateWithMs)
                            + QString(": bootloader running (%1, delay=%2ms)").arg(pevent->count()).arg(delay);
                        emit m_worker->showStatusMessage("dc update", msg);
                        Utils::printInfoMsg(msg.toUpper());
                        m_updateStep = UpdateDcEvent::UpdateStep::BL_IS_UP;
                    } else {
                        msg = recv.toString(Qt::ISODateWithMs)
                            + QString(": bootloader stop requested (%1, delay=%2ms)").arg(pevent->count()).arg(delay);
                        emit m_worker->showStatusMessage("dc update", msg);
                        Utils::printInfoMsg(msg.toUpper());
                        if (m_updateStep == UpdateDcEvent::UpdateStep::BL_STOP) {
                            msg = QDateTime::currentDateTime().toString(Qt::ISODateWithMs)
                                    + QString(": bootloader down (%1, delay=%2ms)").arg(pevent->count()).arg(delay);
                            emit m_worker->showStatusMessage("dc update", msg);
                            Utils::printInfoMsg(msg.toUpper());
                            m_updateStep = UpdateDcEvent::UpdateStep::BL_IS_DOWN;
                        }
                    }
                }
            } break;
            case UpdateDcEvent::UpdateStep::BL_STOP: {
                QString const &msg = QDateTime::currentDateTime().toString(Qt::ISODateWithMs)
                    + QString(": stop bootloader (%1, delay=%2ms)").arg(pevent->count()).arg(delay);
                emit m_worker->showStatusMessage("dc update", msg);
                Utils::printInfoMsg(msg.toUpper());
                //if (m_bootLoaderIsUp) {
                m_hw->bl_stopBL();
                m_updateStep = UpdateDcEvent::UpdateStep::BL_STOP;
                //}
            } break;
            default: ;
            }
        }
    }

    QThread::yieldCurrentThread();
}

void MainWindow::onStopStartTimer() {
    m_startTimer->stop();
}

void MainWindow::onDisableExit() {
   ui->exit->setEnabled(false);
}

void MainWindow::onEnableExit() {
   ui->exit->setEnabled(true);
}

//bool MainWindow::eventFilter(QObject *obj, QEvent *ev) {
//    if (obj == ui->updateStatus) {
//        qCritical() << "REc. event for text edit" << ev->type();
//    }
//    return QMainWindow::eventFilter(obj, ev);
//}

void MainWindow::onRestartExitTimer() {
    m_exitTimer->stop();
    m_exitTimer->start(60 * 1000);

    // ui->updateStatus->blockSignals(true);
}

void MainWindow::onQuit() {
    m_exitTimer->stop();
    qCritical() << QString("ON QUIT: EXIT CODE %1").arg(m_worker->returnCode());
    qApp->exit(m_worker->returnCode());
}

void MainWindow::scrollDownTextEdit() {
    Utils::printInfoMsg(QString("SCROLL-DOWN-TEXT_EDIT CALLED AT ")
        + QDateTime::currentDateTime().toString(Qt::ISODateWithMs));

    ui->updateStatus->setEnabled(true);

    QTextCursor tmpCursor = ui->updateStatus->textCursor();
    tmpCursor.movePosition(QTextCursor::End);
    ui->updateStatus->setTextCursor(tmpCursor);
    ui->updateStatus->ensureCursorVisible();
}

void MainWindow::onAppendText(QString text, QString suffix) {
    Utils::printInfoMsg(QString("ON APPEND CALLED AT ")
        + QDateTime::currentDateTime().toString(Qt::ISODateWithMs));

    QString editText = ui->updateStatus->toPlainText();
    if (!suffix.isNull() && suffix.size() > 0) {
        //qInfo() << "TEXT" << text << "SUFFIX" << suffix;
        if (suffix == Worker::UPDATE_STEP_SUCCESS || suffix == Worker::UPDATE_STEP_FAIL) {
            ui->updateStatus->insertPlainText(QString("\n").leftJustified(m_width-3, '=') + " ");
            // editText += QString("\n").leftJustified(m_width-3, '=');
            // editText += " ";
        }
        QString const &add = (QString("\n") + text).leftJustified(m_width - (2 + suffix.size())) + suffix;
        ui->updateStatus->insertPlainText(add);
        // editText += add;
    } else {
        QString const &add = text.leftJustified(m_width-9);
        ui->updateStatus->insertPlainText(add);
        //editText += add;
    }

    // debug
    // QString editText = ui->updateStatus->toPlainText();
    // Utils::printLineEditInfo(editText.split('\n', QString::SplitBehavior::SkipEmptyParts));
    // ui->updateStatus->setText(editText.trimmed());

    scrollDownTextEdit();
}

void MainWindow::onReplaceLast(QStringList newTextLines, QString suffix) {
    Utils::printInfoMsg(QString("ON REPLACE LAST (LIST) CALLED AT ")
        + QDateTime::currentDateTime().toString(Qt::ISODateWithMs));

    int const s = newTextLines.size();
    if (s > 0) {
        QString editText = ui->updateStatus->toPlainText();
        QStringList lines = editText.split('\n', QString::SplitBehavior::SkipEmptyParts);
        QString newText;
        if (lines.size() >= s) {
            for (int i = 0; i < s; ++i) {
                lines.removeLast();
            }
            if (lines.size() > 0) {
                newText = lines.join('\n');
                newText += '\n';
            }
            QStringList newLines;
            for (int i = 0; i < s; ++i) {
                if (i == 0 && !suffix.isNull() && suffix.size() > 0 && suffix != "\n") {
                    newLines += Utils::rstrip(newTextLines.at(i).leftJustified(m_width-10) + suffix);
                } else {
                    newLines += Utils::rstrip(newTextLines.at(i).leftJustified(m_width-10));
                }
            }
            lines += newLines;
            newText += newLines.join(' ');
        }

        ui->updateStatus->setText(newText);
        Utils::printLineEditInfo(lines);
        scrollDownTextEdit();
    }
}

void MainWindow::onReplaceLast(QString text, QString suffix) {
    Utils::printInfoMsg(QString("ON REPLACE LAST (TEXT) CALLED AT ")
        + QDateTime::currentDateTime().toString(Qt::ISODateWithMs));

    QString editText = ui->updateStatus->toPlainText();
    QStringList lines = editText.split('\n', QString::SplitBehavior::SkipEmptyParts);
    if (lines.size() > 0) {
        lines.removeLast();
        if (!suffix.isNull() && suffix.size() > 0 && suffix != "\n") {
            QString const add = text.leftJustified(m_width-10) + suffix;
            if (!add.isEmpty()) {
                lines += text.leftJustified(m_width-10) + suffix;
            }
        } else {
            QString const add = text.leftJustified(m_width-10);
            if (!add.isEmpty()) {
                lines += text.leftJustified(m_width-10);
            }
        }
    }

    Utils::printLineEditInfo(lines);
    ui->updateStatus->setText(lines.join('\n').trimmed());
    scrollDownTextEdit();
}

void MainWindow::onShowMessage(QString title, QString text) {
    this->statusBar()->clearMessage();
    this->statusBar()->showMessage( // timeout: 10000
        QString(title + " " + text).leftJustified(80, ' '), 10000);
}

void MainWindow::onShowErrorMessage(QString title, QString text) {
    onShowMessage(title, text);
}

void MainWindow::onShowStatusMessage(QString title, QString text) {
    onShowMessage(title, text);
}
