#include "worker.h"
#include "update.h"

#include <QCoreApplication>
#include <QApplication>
#include <QDebug>
#include <QTimer>
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QThread>
#include <QRegularExpression>
#include <QDateTime>
#include <QString>
#include <QMessageBox>
#include <QPushButton>
#include <QJsonParseError>
#include <Qt>

#include <thread>

#include "message_handler.h"
#include "plugins/interfaces.h"
#include "ismas/ismas_client.h"
#include "progress_event.h"
#include "mainwindow.h"
#include "utils.h"

QString const Worker::UPDATE_STEP_OK(" [  ok]");
QString const Worker::UPDATE_STEP_DONE(" [done]");
QString const Worker::UPDATE_STEP_FAIL(" [FAIL]");
QString const Worker::UPDATE_STEP_SUCCESS(" [SUCCESS]");

Worker::Worker(int customerNr,
               int machineNr,
               int zoneNr,
               QString branchName,
               QString pluginName,
               QString workingDirectory,
               bool dryRun,
               QObject *parent,
               char const *serialInterface,
               char const *baudrate)
  : m_workerThread("workerThread")
  , m_customerNr(customerNr)
  , m_customerNrStr(QString("customer_") + QString::number(m_customerNr).rightJustified(3, '0'))
  , m_machineNr(machineNr)
  , m_zoneNr(zoneNr)
  , m_pluginName(pluginName)
  , m_workingDirectory(workingDirectory)
  , m_branchName(branchName)
  , m_customerRepositoryPath(QString("https://git.mimbach49.de/GerhardHoffmann/%1.git").arg(m_customerNrStr))
  , m_customerRepository(QDir::cleanPath(m_workingDirectory + QDir::separator() + m_customerNrStr))
  , m_dryRun(dryRun)
  , m_parent(parent)
  , m_serialInterface(serialInterface)
  , m_baudrate(baudrate)
  , m_gc(m_customerNrStr, m_customerRepository, m_workingDirectory, m_branchName, this)
  , m_osVersion(getOsVersion())
  , m_atbqtVersion(getATBQTVersion())
  , m_atbUpdateToolVersion(getATBUpdateToolVersion())
  , m_cpuSerial(getCPUSerial())
  , m_pluginVersionATBDeciceController(getPluginVersion("/opt/app/ATBAPP/plugins/libATBDeviceControllerPlugin.so"))
  , m_pluginVersionIngenicoISelf(getPluginVersion("/opt/app/ATBAPP/plugins/libIngenicoISelf_CCPlugin.so"))
  , m_pluginVersionMobilisisCalc(getPluginVersion("/opt/app/ATBAPP/plugins/libMOBILISIS_CalculatePricePlugin.so"))
  , m_pluginVersionMobilisisCalcConfig(getPluginVersion("/opt/app/ATBAPP/plugins/libMOBILISIS_CalculatePricePlugin_ConfigUi.so"))
  , m_pluginVersionPrmCalc(getPluginVersion("/opt/app/ATBAPP/plugins/libPRM_CalculatePricePlugin.so"))
  , m_pluginVersionPrmCalcConfig(getPluginVersion("/opt/app/ATBAPP/plugins/libPRM_CalculatePricePlugin_ConfigUi.so"))
  , m_pluginVersionTcpZvt(getPluginVersion("/opt/app/ATBAPP/plugins/libTCP_ZVT_CCPlugin.so"))
  , m_ismasUpdateRequests(ISMAS_UPDATE_REQUESTS)
  , m_waitForNewUpdates(this)
  , m_filesToUpdate()
  , m_updateProcessRunning(true)
  , m_returnCode(0)
  , m_mainWindow(nullptr) /* contains plugin */
  , m_progressValue(0)
  //, m_withoutIsmasDirectPort(true) /* useful for testing */ {
  , m_withoutIsmasDirectPort(false) /* useful for testing */ {

    this->setObjectName("worker-object");
    QDir::setCurrent(m_workingDirectory);

    if (std::optional<QString> v = getApismVersion()) {
        m_apismVersion = v.value();
    }

    Utils::printInfoMsg("STARTING PTU-UPDATE");
    qInfo() << "CURRENT TIME ..............." << QDateTime::currentDateTime().toString(Qt::ISODate);
    qInfo() << "OS VERSION ................." << m_osVersion;
    qInfo() << "ATBQT VERSION .............." << m_atbqtVersion;
    qInfo() << "CPU SERIAL ................." << m_cpuSerial;
    qInfo() << "CUSTOMER_NR ................" << m_customerNr;
    qInfo() << "CUSTOMER_NR_STR ............" << m_customerNrStr;
    qInfo() << "CUSTOMER_REPOSITORY_PATH ..." << m_customerRepositoryPath;
    qInfo() << "CUSTOMER_REPOSITORY ........" << m_customerRepository;
    qInfo() << "MACHINE_NR ................." << m_machineNr;
    qInfo() << "ZONE_NR ...................." << m_zoneNr;
    qInfo() << "BRANCH_NAME ................" << m_branchName;
    qInfo() << "PLUGIN_NAME ................" << m_pluginName;
    qInfo() << "WORKING_DIRECTORY .........." << m_workingDirectory;
    qInfo() << "APISM VERSION .............." << m_apismVersion;
    qInfo() << "ATB UPDATE TOOL VERSION ...." << m_atbUpdateToolVersion;

    this->moveToThread(&m_workerThread);
    m_workerThread.start();

    int cnt = 0;
    while (!m_workerThread.isRunning()) {
        if (++cnt > 5) {
            Utils::printCriticalErrorMsg("starting worker thread FAILED");
            return;
        }
        QThread::sleep(1);
    }
}

Worker::~Worker() {
    int cnt = 0;
    m_workerThread.quit();
    while (!m_workerThread.isFinished()) {
        if (!m_workerThread.wait(1000)) {
            if (++cnt > 5) {
                Utils::printCriticalErrorMsg("stopping worker thread FAILED");
                return;
            }
        }
    }
}

void Worker::setProgress(int progress) {
    if (m_mainWindow) {
        m_progressValue = progress;
        QApplication::postEvent(m_mainWindow, new ProgressEvent(this, progress));
    }
}

void Worker::startProgressLoop() {
    QApplication::postEvent(m_mainWindow, new ProgressEvent(this, MainWindow::START_PROGRESS_LOOP));
}

void Worker::stopProgressLoop() {
    QApplication::postEvent(m_mainWindow, new ProgressEvent(this, MainWindow::STOP_PROGRESS_LOOP));
}

static std::once_flag once;
void Worker::update() {
    // user should not start the update process several times
    std::call_once(once, &Worker::privateUpdate, this);
}

void Worker::privateUpdate() {
    if (!m_mainWindow) {
        Utils::printCriticalErrorMsg("m_mainWindow NOT SET");
        return;
    }

    m_updateProcessRunning = true;
    bool sentIsmasLastVersionNotification = false;

    emit disableExit();

    m_returnCode = -1;
    QDir customerRepository(m_customerRepository);
    if (!customerRepository.exists()) {
        emit appendText("\nInitializing customer environment ...");
        startProgressLoop();
        if (m_gc.gitCloneAndCheckoutBranch()) {
            stopProgressLoop();
            emit replaceLast("Initializing customer environment", UPDATE_STEP_DONE);

            setProgress(5);

            m_updateStatus = UpdateStatus(UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_SUCCESS,
                                  QString("CLONED AND CHECKED OUT: ") + m_customerRepository);

            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.cloneAndCheckoutCustomerRepository(
                            m_updateStatus.m_statusDescription));

            setProgress(10);

            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.updateOfPSASucceeded(""));

            setProgress(100);
            m_ismasClient.setProgressInPercent(100);
            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                QString("#M=APISM#C=CMD_EVENT#J=") + m_ismasClient.updateOfPSAActivated());

            m_returnCode = 0;
        } else {
            stopProgressLoop();
            setProgress(0);

            emit replaceLast("Initializing customer environment", UPDATE_STEP_FAIL);

            m_updateStatus = UpdateStatus(UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_FAILURE,
                                  QString("CLONE OR CHECKOUT FAILED: ") + m_customerRepository);
            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                QString("#M=APISM#C=CMD_EVENT#J=") +
                    m_ismasClient.errorGitClone(100, m_updateStatus.m_statusDescription));

            m_returnCode = -3;
        }
    } else {
        if (updateTriggerSet(5)) {
            if (customerEnvironment(30)) {
                m_ismasClient.setProgressInPercent(50);
                if (filesToUpdate()) {
                    // send message to ISMAS about files which have been
                    // checked in into git repository
                    m_updateStatus = UpdateStatus(UPDATE_STATUS::GIT_CHECK_FILES_TO_UPDATE_SUCCESS,
                                        QString("Files to update: ") + m_filesToUpdate.join(','));
                    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                        QString("#M=APISM#C=CMD_EVENT#J=") +
                            m_ismasClient.updateOfPSAContinues("CHECK-FILES-TO-UPDATE",
                                                               m_updateStatus.m_statusDescription));
                    if (updateFiles(60)) {
                        m_ismasClient.setProgressInPercent(70);
                        if (syncCustomerRepositoryAndFS()) {
                            m_ismasClient.setProgressInPercent(80);
                            if (sendIsmasLastVersionNotification()) {
                                m_ismasClient.setProgressInPercent(90);
                                sentIsmasLastVersionNotification = true;
                                if (saveLogFile()) {
                                    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                        QString("#M=APISM#C=CMD_EVENT#J=") +
                                            m_ismasClient.updateOfPSASucceeded(""));

                                    // mark update as activated -> this resets the WAIT button
                                    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                        QString("#M=APISM#C=CMD_EVENT#J=") +
                                            m_ismasClient.updateOfPSAActivated());

                                    m_returnCode = 0;
                                } else {
                                    m_updateStatus = UpdateStatus(UPDATE_STATUS::SAVE_LOG_FILES_FAILED,
                                                        QString("Saving log files failed"));
                                    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                        QString("#M=APISM#C=CMD_EVENT#J=") +
                                            m_ismasClient.updateOfPSAFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                                            "SAVE-LOG-FILES",
                                                                            m_updateStatus.m_statusDescription));
                                    m_returnCode = -11;
                                }
                            } else {
                                m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_SEND_LAST_VERSION_FAILED,
                                                    QString("Sending ISMAS last version failed"));
                                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                    QString("#M=APISM#C=CMD_EVENT#J=") +
                                        m_ismasClient.updateOfPSAFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                                        "ISMAS-SEND-LAST-VERSION",
                                                                        m_updateStatus.m_statusDescription));
                                m_returnCode = -10;
                            }
                        } else {
                            m_updateStatus = UpdateStatus(UPDATE_STATUS::RSYNC_UPDATES_FAILURE,
                                                QString("Syncing files to update failed"));
                            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                QString("#M=APISM#C=CMD_EVENT#J=") +
                                    m_ismasClient.updateOfPSAFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                                    "RSYNC-UPDATE-FILES",
                                                                    m_updateStatus.m_statusDescription));
                            m_returnCode = -9;
                        }
                    } else {
                        m_updateStatus = UpdateStatus(UPDATE_STATUS::PSA_UPDATE_FILES_FAILED,
                                            QString("Updating files failed"));
                        IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                            QString("#M=APISM#C=CMD_EVENT#J=") +
                                m_ismasClient.updateOfPSAFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                                "UPDATE-FILES",
                                                                m_updateStatus.m_statusDescription));
                        m_returnCode = -8;
                    }
                } else {
                    m_updateStatus = UpdateStatus(UPDATE_STATUS::GIT_FETCH_UPDATES_REQUEST_FAILURE,
                                        QString("No files to update"));
                    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                        QString("#M=APISM#C=CMD_EVENT#J=") +
                            m_ismasClient.updateOfPSAFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                            "FETCH-FILES-TO-UPDATE",
                                                            m_updateStatus.m_statusDescription));
                    m_returnCode = -7;
                }
            } else {
                m_updateStatus = UpdateStatus(UPDATE_STATUS::GIT_CHECKOUT_BRANCH_FAILURE,
                                    QString("Configuring customer environment failed"));
                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.updateOfPSAFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                        "GIT-CHECKOUT-BRANCH",
                                                        m_updateStatus.m_statusDescription));
                m_returnCode = -6;
            }
        } else {
            m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_SET_FAILURE,
                                QString("ISMAS update trigger wrong"));
            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                QString("#M=APISM#C=CMD_EVENT#J=") +
                    m_ismasClient.updateOfPSAFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                    "CHECK-UPDATE-TRIGGER",
                                                    m_updateStatus.m_statusDescription));
            m_returnCode = -5;
        }
    }

    m_ismasClient.setProgressInPercent(100);
    setProgress(100);

    if (m_returnCode != 0) {
        stopProgressLoop();
        emit appendText(QString("UPDATE "), UPDATE_STEP_FAIL);

        // m_updateStatus = UpdateStatus(UPDATE_STATUS::UPDATE_PROCESS_FAILURE,
        //                               QString("Update process failed"));
        // if (std::optional<QString> s = m_ismasClient.finalResult(IsmasClient::RESULT_CODE::INSTALL_ERROR,
        //                                                         m_updateStatus.m_statusDescription)) {
        //    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
        //        QString("#M=APISM#C=CMD_EVENT#J=") + s.value());
        // }

    } else {
        emit appendText(QString("UPDATE "), UPDATE_STEP_SUCCESS);

        m_updateStatus = UpdateStatus(UPDATE_STATUS::UPDATE_PROCESS_SUCCESS,
                                      QString("Update process succeeded. Reset WAIT."));
        if (std::optional<QString> s = m_ismasClient.finalResult(IsmasClient::RESULT_CODE::SUCCESS,
                                                                 m_updateStatus.m_statusDescription)) {
            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                QString("#M=APISM#C=CMD_EVENT#J=") + s.value());
        }
    }

    if (!sentIsmasLastVersionNotification) {
        // try even if the backend is not connected
        sendIsmasLastVersionNotification();
    }

    m_updateProcessRunning = false;
    emit enableExit();
    emit restartExitTimer();
}

std::optional<QString> Worker::getApismVersion() {
    for (int repeat = 0; repeat < 10; ++repeat) {
        qInfo() << "REPEAT" << repeat << "In getApismVersion() -> #M=APISM#C=REQ_SELF#J={}";
        std::optional<QString> result
            = IsmasClient::sendRequestReceiveResponse(
                IsmasClient::APISM::DIRECT_PORT, "#M=APISM#C=REQ_SELF#J={}");
        if (result) {
            QString msg  = result.value();
            qInfo() << "In getApismVersion() -> APISM response" << msg;
            QJsonParseError parseError;
            QJsonDocument document(QJsonDocument::fromJson(msg.toUtf8(), &parseError));
            if (parseError.error != QJsonParseError::NoError) {
                qCritical() << "(1) INVALID JSON MSG: PARSING FAILED (msg=" << msg << "):"
                            << parseError.error << parseError.errorString();
                m_updateStatus = UpdateStatus(UPDATE_STATUS::JSON_PARSE_FAILURE,
                                              QString("(2) INVALID JSON %1 %2 %3")
                                                .arg(msg)
                                                .arg(parseError.error)
                                                .arg(parseError.errorString()));
                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.jsonParseFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                      m_updateStatus.m_statusDescription));
                return std::nullopt;
            }
            if (!document.isObject()) {
                qCritical() << "FILE IS NOT A JSON OBJECT!";
                m_updateStatus = UpdateStatus(UPDATE_STATUS::JSON_PARSE_FAILURE,
                                              QString("NOT A JSON-OBJECT %1").arg(msg));
                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.jsonParseFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                      m_updateStatus.m_statusDescription));
                return std::nullopt;
            }
            QJsonObject obj = document.object();
            QStringList keys = obj.keys().filter("CMD_GET_APISMSTATUS_RESPONSE");
            if (keys.size() != 1) {
                m_updateStatus = UpdateStatus(UPDATE_STATUS::BACKEND_CHECK_FAILURE,
                                    "CMD_GET_APISMSTATUS_RESPONSE KEY NOT AVAILABLE");
                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                        m_updateStatus.m_statusDescription));
                emit showErrorMessage("apism response", m_updateStatus.m_statusDescription);
                return std::nullopt;
            } else {
                QString const key = keys.at(0);
                QJsonValue v = obj.value(key);
                return v.toObject().value("Version").toString();
            }
        } else {
            QThread::sleep(1);
        }
    }
    return std::nullopt;
}

#define CHECK_UPDATE_TRIGGER_SET "Check update trigger ..."

bool Worker::updateTriggerSet(int progress) {
    if (m_withoutIsmasDirectPort) { // useful for testing
        return true;
    }

    emit appendText("\n" CHECK_UPDATE_TRIGGER_SET);
    QString triggerValue("");
    int const startMs = QTime::currentTime().msecsSinceStartOfDay();

    for (int repeat = 1; repeat <= 100; ++repeat) {

        qInfo() << "UPDATE TRIGGER SET -> REPEAT" << repeat;

        if (repeat > 1) {
            int const durationMs = QTime::currentTime().msecsSinceStartOfDay() - startMs;
            QString const &msg = QString("elapsed: %1.%2s").arg(durationMs / 1000).arg(durationMs % 1000);
            qInfo() << "REPEAT" << msg;
            emit showErrorMessage("check update trigger", msg);
        } else {
            emit showErrorMessage("check update trigger", "");
        }

        if ((repeat % 10) == 0) {
            qInfo() << "CHECK UPDATE TRIGGER. RESTART APISM ...";
            Command c("systemctl restart apism");
            if (c.execute("/tmp")) {
                QThread::sleep(20); // give APISM some time to reconnect
                qInfo() << "CHECK UPDATE TRIGGER. RESTARTING APISM DONE";
            }
        }

        startProgressLoop();
        if (std::optional<QString> result
                = IsmasClient::sendRequestReceiveResponse(
                    IsmasClient::APISM::DIRECT_PORT, "#M=APISM#C=REQ_ISMASPARAMETER#J={}")) {
            stopProgressLoop();
            setProgress(m_mainWindow->progressValue()/10 + 11);

            QString msg = result.value();

            qInfo() << "REPEAT" << repeat << "APISM RESPONSE (" << msg << ")";

            QJsonParseError parseError;
            QJsonDocument document(QJsonDocument::fromJson(msg.toUtf8(), &parseError));
            if (parseError.error != QJsonParseError::NoError) {
                qCritical() << "(2) INVALID JSON MSG: PARSING FAILED (msg=" << msg << "):"
                            << parseError.error << parseError.errorString();
                setProgress(100);
                m_updateStatus = UpdateStatus(UPDATE_STATUS::JSON_PARSE_FAILURE,
                                              QString("(2) INVALID JSON %1 %2 %3")
                                                .arg(msg)
                                                .arg(parseError.error)
                                                .arg(parseError.errorString()));
                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.jsonParseFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                      m_updateStatus.m_statusDescription));
                emit showErrorMessage("check update trigger",
                                      QString("invalid json ") + msg.mid(0, 20));
                emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                return false;
            }
            if (!document.isObject()) {
                qCritical() << "FILE IS NOT A JSON OBJECT!";
                setProgress(100);
                m_updateStatus = UpdateStatus(UPDATE_STATUS::JSON_PARSE_FAILURE,
                                              QString("NOT A JSON-OBJECT %1").arg(msg));
                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.jsonParseFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                      m_updateStatus.m_statusDescription));
                emit showErrorMessage("check update trigger", QString("not a json object") + msg);
                emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                return false;
            }
            setProgress(m_mainWindow->progressValue()/10 + 11);

            QJsonObject obj = document.object();

            // always look for an 'error' first
            if (obj.contains("error")) {
                setProgress(m_mainWindow->progressValue()/10 + 11);
                QString value = obj.value("error").toString();
                emit showErrorMessage("check update trigger", QString("REPEAT %1 error=<").arg(repeat) + value + ">");
                qInfo() << "REPEAT" << repeat << "In updateTriggerSet() error=<"
                        << value << ">";
                QThread::sleep(6);
                continue;
            }
            // sanity check: cust_nr and machine_nr of PSA correct ?
            // note: this check has to be done here, as the cust_nr and the machine_nr
            // of the PSA are sent by ISMAS.
            if (obj.contains("Dev_ID")) {
                QJsonValue v = obj.value("Dev_ID");
                if (v.isObject()) {
                    QJsonObject obj = v.toObject();
                    if (obj.contains("Custom_ID") && obj.contains("Device_ID")) {
                        QJsonValue const c = obj.value("Custom_ID");
                        QJsonValue const m = obj.value("Device_ID");
                        int customerNr = c.toInt(-1);
                        int machineNr = m.toInt(-1);
                        if (customerNr != m_customerNr) {
                            setProgress(100);
                            m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
                                                          QString("CUSTOMER-NR (%1) != LOCAL CUSTOMER-NR (%2)")
                                                            .arg(customerNr).arg(m_customerNr));
                            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                QString("#M=APISM#C=CMD_EVENT#J=") +
                                    m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                                    m_updateStatus.m_statusDescription));
                            emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
                            emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                            return false;
                        }
                        if (machineNr != m_machineNr) {
                            setProgress(100);
                            m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
                                                          QString("MACHINE-NR (%1) != LOCAL MACHINE-NR (%2)")
                                                            .arg(machineNr).arg(m_machineNr));
                            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                QString("#M=APISM#C=CMD_EVENT#J=") +
                                    m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                                    m_updateStatus.m_statusDescription));
                            emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
                            emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                            return false;
                        }

                        qInfo() << "MACHINE-AND-CUSTOMER-CHECK" << m_updateStatus.m_statusDescription;

                    } else {
                        setProgress(100);
                        m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
                                                      "Dev_ID DOES NOT CONTAIN Custom_ID AND/OR Device_ID");
                        IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                            QString("#M=APISM#C=CMD_EVENT#J=") +
                                m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                                m_updateStatus.m_statusDescription));
                        emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
                        emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                        return false;
                    }
                } else {
                    setProgress(100);
                    m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
                                                  "Dev_ID KEY NOT A JSON-OBJECT");
                    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                        QString("#M=APISM#C=CMD_EVENT#J=") +
                            m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                            m_updateStatus.m_statusDescription));
                    emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
                    emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                    return false;
                }
            } else {
                setProgress(100);
                m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
                                              "Dev_ID KEY NOT AVAILABLE");
                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                        m_updateStatus.m_statusDescription));
                emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
                emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                return false;
            }
            setProgress(m_mainWindow->progressValue()/10 + 11);

            if (obj.contains("Fileupload")) {
                QJsonValue v = obj.value("Fileupload");
                if (v.isObject()) {
                    obj = v.toObject();
                    if (obj.contains("TRG")) {
                        triggerValue = obj.value("TRG").toString();

                        qInfo() << "REPEAT" << repeat
                                << "In updateTriggerSet() TRG value=<"
                                << triggerValue << ">";

                        if (triggerValue == "WAIT") {
                            setProgress(100);
                            m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_SANITY_CHECK_OK,
                                                          QString("MACHINE-NR (%1) AND CUST-NR (%2) OK")
                                                            .arg(m_machineNr).arg(m_customerNr));

                            m_ismasClient.setProgressInPercent(progress);

                            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                QString("#M=APISM#C=CMD_EVENT#J=") +
                                    m_ismasClient.updateOfPSAContinues("MACHINE-AND-CUSTOMER-CHECK",
                                                                   m_updateStatus.m_statusDescription));

                            progress += 5;
                            m_ismasClient.setProgressInPercent(progress);

                            m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_SET,
                                QString("UPDATE TRIGGER SET. CONTINUE. "));
                            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                QString("#M=APISM#C=CMD_EVENT#J=") +
                                    m_ismasClient.updateTriggerSet(m_updateStatus.m_statusDescription, ""));

                            emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_DONE);
                            return true;
                        } else
                        if (QRegExp("\\s*").exactMatch(triggerValue)) { // check for whitespace
                            stopProgressLoop();
                            setProgress(m_mainWindow->progressValue()/10 + 11);
                            emit showErrorMessage("check update trigger", "empty update-trigger");
                            QThread::sleep(6);
                            continue;
                        } else {
                            // if the download-button once has the wrong value, it will never recover
                            setProgress(100);
                            m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
                                             QString("TRIGGER-VALUE=<") + triggerValue + "> NOT 'WAIT'");
                            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                            QString("#M=APISM#C=CMD_EVENT#J=") +
                                                m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                                                m_updateStatus.m_statusDescription));
                            emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
                            emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                            return false;
                        }
                    } else {
                        setProgress(100);
                        m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
                                              "TRG KEY NOT AVAILABLE");
                        IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                            QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                        m_updateStatus.m_statusDescription));
                        emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                        emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
                        return false;
                    }
                } else {
                    setProgress(100);
                    m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
                                          "Fileupload NOT A JSON-OBJECT");
                    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                        QString("#M=APISM#C=CMD_EVENT#J=") +
                    m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                    m_updateStatus.m_statusDescription));
                    emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                    emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
                    return false;
                }
            } else {
                setProgress(100);
                m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
                                              "Fileupload KEY NOT AVAILABLE");
                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                    QString("#M=APISM#C=CMD_EVENT#J=") +
                        m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
                                                        m_updateStatus.m_statusDescription));
                emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
                emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
                return false;
            }
        } else {
            stopProgressLoop();
            setProgress(m_mainWindow->progressValue()/10 + 11);
            emit showErrorMessage("check update trigger", "no ISMAS response");
            QThread::sleep(6);
        }
    }

    setProgress(100);
    m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_NOT_SET_OR_WRONG,
                                  QString("ISMAS_UPDATE-TRIGGER-NOT-SET-OR-WRONG: VALUE=(") +
                                  triggerValue + ")");
    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
        QString("#M=APISM#C=CMD_EVENT#J=") +
            m_ismasClient.errorUpdateTrigger(m_updateStatus.m_statusDescription, ""));

    emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
    return false;
}

bool Worker::customerEnvironment(int progress) {
    emit appendText("\nPrepare customer environment ...");
    if (QDir(m_customerRepository).exists()) {
        startProgressLoop();
        setProgress(m_mainWindow->progressValue()/10 + 11);
        if (m_gc.gitCheckoutBranch()) {
            stopProgressLoop();
            m_ismasClient.setProgressInPercent(progress);

            m_updateStatus = UpdateStatus(UPDATE_STATUS::GIT_CHECKOUT_BRANCH,
                                    QString("CHECKED-OUT BRANCH ") + m_gc.branchName());

            IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                    QString("#M=APISM#C=CMD_EVENT#J=") +
                                        m_ismasClient.checkoutBranch(m_updateStatus.m_statusDescription, ""));

            setProgress(100);
            emit replaceLast("Prepare customer environment ...", UPDATE_STEP_DONE);
            qInfo() << "PREPARE CUSTOMER ENVIRONMENT DONE";
            return true;
        } else {
            stopProgressLoop();
            m_ismasClient.setProgressInPercent(0);

            emit showErrorMessage("cust-env",
                                  QString("Checkout ") + m_customerRepository + " failed");
            Utils::printCriticalErrorMsg(QString("CHECKOUT OF " + m_customerRepository + "FAILED"));
        }
    } else {
        emit showErrorMessage("cust-env", m_customerRepository + " does not exist");
        Utils::printCriticalErrorMsg(m_customerRepository + " DOES NOT EXIST");
    }

    setProgress(100);
    emit replaceLast("Prepare customer environment ...", UPDATE_STEP_FAIL);
    return false;
}

bool Worker::filesToUpdate() {
    emit appendText("\nFetch changes files ...");
    startProgressLoop();

    // always execute contents of opkg_commands-file
    m_filesToUpdate << "etc/psa_update/opkg_commands";

    if (std::optional<QString> changes = m_gc.gitPull()) {
        stopProgressLoop();
        int progress = (m_mainWindow->progressValue()/10) + 10;
        setProgress(progress);

        m_updateStatus = UpdateStatus(UPDATE_STATUS::GIT_FETCH_UPDATES,
                                        QString("FETCHING OF ") + m_customerRepositoryPath +
                                        QString(" INTO ") + m_customerRepository);

        setProgress(progress + 10);
        if (std::optional<QStringList> changedFileNames = m_gc.gitDiff(changes.value())) {
            setProgress(progress + 20);
            if (m_gc.gitPull()) {
                emit replaceLast(QString("Fetch changes files ..."), UPDATE_STEP_DONE);
                m_filesToUpdate << changedFileNames.value();
            } else {
                emit showErrorMessage("files to update", "pulling files failed");
                Utils::printCriticalErrorMsg("PULLING FILES FAILED");

                emit replaceLast(QString("Fetch changes files ..."), UPDATE_STEP_FAIL);

                stopProgressLoop();
                setProgress(100);

                return false;
            }
        }

        Utils::printInfoMsg("FILES-TO-UPDATE " + m_filesToUpdate.join(','));

        m_filesToUpdate.removeDuplicates();
        int const size = m_filesToUpdate.size();
        if (size > 1) {
            emit appendText(QString("Found %1 files to update :").arg(size), UPDATE_STEP_DONE);
            for (int i = 0; i < size; ++i) {
                emit appendText(QString("\n ") + m_filesToUpdate.at(i));
            }
        } else {
            emit appendText("Found 1 file to update :", UPDATE_STEP_DONE);
            emit appendText(QString("\n ") + m_filesToUpdate.at(0));
        }

        setProgress(progress + 30);
    }

    return true;
}

bool Worker::updateFiles(quint8 percent) {
    QStringList filesToDownload;
    m_displayIndex = 0;
    startProgressLoop();
    for (int i = 0; i < m_filesToUpdate.size(); ++i) {
        QString const fName = m_filesToUpdate.at(i);
        Utils::printInfoMsg(QString("FNAME ") + fName);

        if (fName.contains("opkg_commands", Qt::CaseInsensitive)) {
            emit appendText("\n( ) Update opkg pakets ...");
            // execute opkg commands
            if (QDir::setCurrent(m_customerRepository)) {
                QFile f(fName);
                if (f.exists()) {
                    if (f.open(QIODevice::ReadOnly)) {
                        QTextStream in(&f);
                        QStringList opkgCommands;
                        bool executeOpkgCommandFailed = false;
                        while (!in.atEnd()) {
                            QString line = in.readLine();
                            static const QRegularExpression comment("^\\s*#.*$");
                            if (line.indexOf(comment, 0) == -1) {
                                // found opkg command
                                QString opkgCommand = line.trimmed();
                                if (!executeOpkgCommand(opkgCommand)) {
                                    executeOpkgCommandFailed = true;
                                } else {
                                    QString cmd = "\n  " + opkgCommand;
                                    emit appendText(cmd);
                                    opkgCommands << cmd;

                                    m_ismasClient.setProgressInPercent(++percent);
                                    m_updateStatus = UpdateStatus(UPDATE_STATUS::EXEC_OPKG_COMMAND,
                                                        QString("EXEC OPKG-COMMAND ") + opkgCommand);
                                    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                            QString("#M=APISM#C=CMD_EVENT#J=") +
                                                m_ismasClient.execOpkgCommand(m_updateStatus.m_statusDescription, ""));
                                }
                            }
                        }
                        f.close();
                        if (!executeOpkgCommandFailed) {
                            if (opkgCommands.size() > 0) {
                                m_displayIndex = 1;
                                QString prepend = QString("(") + QString("%1").arg(m_displayIndex).rightJustified(3, ' ') + QString(")")
                                                    + QString(" Update opkg pakets ... ");
                                opkgCommands.prepend(prepend);
                                emit replaceLast(opkgCommands, UPDATE_STEP_DONE);
                            }
                        } else {
                            m_displayIndex = 1;
                            emit replaceLast(QString("(") + QString("%1").arg(m_displayIndex).rightJustified(3, ' ') + QString(")")
                                          + QString(" Update opkg pakets ... "), UPDATE_STEP_FAIL);

                            stopProgressLoop();
                            setProgress(100);
                            return false;
                        }
                    }
                }
            }
        } else
        if (fName.contains("DC2C_print", Qt::CaseInsensitive) ||
            fName.contains("DC2C_device", Qt::CaseInsensitive) ||
            fName.contains("DC2C_conf", Qt::CaseInsensitive) ||
            fName.contains("DC2C_cash", Qt::CaseInsensitive)) {
            filesToDownload << fName;  // download printer-config-files
        } else {
            static const QRegularExpression version("^.*dc2c[.][0-9]{1,2}[.][0-9]{1,2}[.]bin.*$");
            if (fName.contains(version)) {
                filesToDownload << fName; // download device controller
            }
        }
    }

    stopProgressLoop();
    setProgress(100);

    if (filesToDownload.size() > 0) {
        Utils::printInfoMsg(QString("FILES_TO_DOWNLOAD_TO_PSA_HW ") + filesToDownload.join(','));

        Update *update = m_mainWindow->getUpdate();
        if (update) {
            return update->doUpdate(m_displayIndex, filesToDownload);
        } else {
            Utils::printCriticalErrorMsg("UPDATE NOT SET");
        }
    } else {
        Utils::printCriticalErrorMsg("NO FILES_TO_DOWNLOAD_TO_PSA_HW");
    }

    return true;
}

bool Worker::syncCustomerRepositoryAndFS() {
    // this step is currently needed only for updating tariff-files
    setProgress(0);
    emit appendText("\nSync customer environment with filesystem ...");
    if (QDir(m_customerRepository).exists()) {
        if (QDir::setCurrent(m_customerRepository)) {
            Command md("bash");
            if (!md.execute(m_customerRepository,
                            QStringList() << "-c" << "mkdir -p /etc/psa_config /etc/dc /etc/psa_tariff")) {
                qCritical() << "COULD NOT EXECUTE '" << md.command() << "' exitCode=(" << md.exitCode() << ")";
            }
            int progress = 10;
            setProgress(progress);
            QString const params("-vvv                    "
                                 "--recursive             "
                                 "--progress              "
                                 "--checksum              "
                                 "--exclude=.*            "
                                 "--include=*.bin         "
                                 "--include=*.json        "
                                 "--include=*.ini");
            QStringList cmds;

            if (QDir(QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/")).exists()) {
                cmds << QString("rsync ") + params.simplified() + " etc/ /etc";
                Utils::printInfoMsg(QString("CONFIGURED SYNCING TO /ETC"));
            }
            if (QDir(QDir::cleanPath(m_customerRepository + QDir::separator() + "opt/")).exists()) {
                cmds << QString("rsync ") + params.simplified() + " opt/ /opt";
                Utils::printInfoMsg(QString("CONFIGURED SYNCING TO /OPT"));
            }

            QString cmd;
            bool error = false;
            foreach (cmd, cmds) {
                progress += 5;
                setProgress(progress);
                if (!error) {
                    Command c("bash");
                    qInfo() << "EXECUTING CMD..." << cmd;
                    Utils::printInfoMsg(QString("EXECUTING CMD %1...").arg(cmd));
                    if (c.execute(m_customerRepository, QStringList() << "-c" << cmd)) {
                        QStringList result = c.getCommandResult().split('\n');
                        QString const &p1 = "send_files mapped ";
                        QString const &p2 = "of size";
                        for (int i = 0; i < result.size(); ++i) {
                            QString line = result.at(i);
                            qInfo() << line;

                            // "send_files mapped etc/psa_tariff/tariff01.json of size 19339"
                            int sendFilesAtPos = line.indexOf(p1);
                            int ofSizeAtPos = line.indexOf(p2);
                            if (sendFilesAtPos != -1 && ofSizeAtPos != -1) {
                                sendFilesAtPos += p1.length();
                                QString const &s = line.mid(sendFilesAtPos, ofSizeAtPos - sendFilesAtPos).trimmed();
                                m_updateStatus = UpdateStatus(UPDATE_STATUS::RSYNC_FILE_SUCCESS,
                                    QString("RSYNC FILE ") + s.split("/").last() +
                                            " LAST-COMMIT: " + m_gc.gitLastCommit(s));
                                IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
                                        QString("#M=APISM#C=CMD_EVENT#J=") +
                                            m_ismasClient.rsyncFile(m_updateStatus.m_statusDescription, ""));
                            }
                        }
                    } else {
                        Utils::printCriticalErrorMsg(QString("CMD ") + cmd + " FAILED: "
                                                   + c.getCommandResult() + QString(" EXIT_CODE=(%1)").arg(c.exitCode()));
                        error = true;
                    }
                }
            }
            progress += 5;
            setProgress(progress);
            if (!error) {
                // now check tariff-files in etc and /etc/psa_tariff
                QDir dir1(QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/psa_tariff"));
                QDir dir2("/etc/psa_tariff");
                if (Utils::sameFilesInDirs(dir1, dir2)) {
                    setProgress(100);
                    emit replaceLast(QString("Sync customer environment with filesystem ..."), UPDATE_STEP_DONE);
                    return true;
                } else {
                    // TODO: send message to ISMAS
                }
            }
        }
    }
    setProgress(100);
    emit replaceLast(QString("Sync customer environment with filesystem ..."), UPDATE_STEP_FAIL);
    return false;
}

bool Worker::sendIsmasLastVersionNotification() {
    IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
            QString("#M=APISM#C=CMD_SENDVERSION#J=") +
                m_ismasClient.updateOfPSASendVersion(getPSAInstalled()));
    emit appendText(QString("Send last version info "), UPDATE_STEP_DONE);
    return true;
}

bool Worker::saveLogFile() {
    return true;
}
QString Worker::getOsVersion() const {
    QString const cmd = QString("echo -n $(cat /etc/os-release | head -n 1 | cut -d'\"' -f2 | tr -d '\"')");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getATBUpdateToolYoctoVersion() {
    if (QFile::exists("/var/lib/opkg/status")) {
        QString const cmd = QString("echo -n $(cat /var/lib/opkg/status | grep -A1 atbupdatetool | tail -n 1 | cut -d':' -f2 | cut -d' ' -f2)");
        Command c("bash");
        if (c.execute("/tmp", QStringList() << "-c" << cmd)) {
            return c.getCommandResult(); // 1.3.9+git0+226553a8ab-r0
        }
    }
    return "N/A";
}

QString Worker::getATBUpdateToolYoctoInstallationStatus()  {
    if (QFile::exists("/var/lib/opkg/status")) {
        QString const cmd = QString("echo -n $(cat /var/lib/opkg/status | grep -A3 atbupdatetool | tail -n 1 | cut -d':' -f2 | cut -d' ' -f2,3,4)");
        Command c("bash");
        if (c.execute("/tmp", QStringList() << "-c" << cmd)) {
            return c.getCommandResult(); // 1.3.9+git0+226553a8ab-r0
        }
    }
    return "N/A";
}



QString Worker::getATBQTVersion() const {
    QString const cmd = QString("echo -n $(/opt/app/ATBAPP/ATBQT -v | head -n 2 | cut -d':' -f2)");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getATBUpdateToolVersion() const {
    return APP_EXTENDED_VERSION;
}

QString Worker::getCPUSerial() const {
    QString const cmd = QString("echo -n $(cat /proc/cpuinfo | grep -i Serial | cut -d':' -f2)");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getRaucVersion() const {
    QString const cmd = QString("echo -n $(rauc --version)");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getOpkgVersion() const {
    QString const cmd = QString("echo -n $(opkg --version)");
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QString Worker::getPluginVersion(QString const &pluginFileName) const {
    QString const cmd = QString("echo -n $(strings %1 | grep \\\"Version\\\" | cut -d':' -f2 | tr -d '\"' | tr -d ',')").arg(pluginFileName);
    Command c("bash");
    if (c.execute(m_workingDirectory, QStringList() << "-c" << cmd)) {
        return c.getCommandResult();
    }
    return "N/A";
}

QStringList Worker::getDCVersion() const {
    QStringList lst = (QStringList() << "N/A" << "N/A");
    hwinf *hwi = m_mainWindow->getPlugin();
    if (hwi) {
        hwi->dc_autoRequest(true); // turn auto-request setting on

        QByteArray const cmp(8, char(0));
        QByteArray hw(""), sw("");
        for (int i=0; i<5; ++i) {
            hw = hwi->dc_getHWversion().toUtf8();
            sw = hwi->dc_getSWversion().toUtf8();
            if (!hw.startsWith(cmp)) {
                lst.clear();
                qInfo() << hw << sw;
                lst << hw << sw;
                break;
            }
            QThread::sleep(1);
        }
    }
    return lst;
}

qint64 Worker::getFileSize(QString const &fileName) const {
    // fileName has to be an absolute path
    QFileInfo fInfo(fileName);
    return fInfo.exists() ? fInfo.size() : -1;
}

bool Worker::executeOpkgCommand(QString opkgCommand) {
    Command c(opkgCommand);
    if (c.execute(m_workingDirectory)) {
        QString const r = c.getCommandResult();
        Utils::printInfoMsg(QString("EXECUTE OPKG COMMAND %1 OK: %2")
                                    .arg(opkgCommand)
                                    .arg(c.getCommandResult()));
        return true;
    } else {
        Utils::printCriticalErrorMsg(QString("EXECUTE OPKG COMMAND %1 FAILED")
                                    .arg(opkgCommand));
    }
    return false;
}

PSAInstalled Worker::getPSAInstalled() {
    QStringList const dcVersion = getDCVersion();
    QString const deviceControllerVersionHW = dcVersion.first();
    QString const deviceControllerVersionSW = dcVersion.last();

    qInfo() << "CURRENT DC-HW-VERSION: " << deviceControllerVersionHW;
    qInfo() << "CURRENT DC-SW-VERSION: " << deviceControllerVersionSW;

    QString const deviceControllerGitBlob = "N/A";
    QString const deviceControllerGitLastCommit = "N/A";

    PSAInstalled psaInstalled;
    QString printSysDir("/etc/psa_config");
    QString tariffSysDir("/etc/psa_tariff");
    QString tariffRepoDir("etc/psa_tariff");
    QString opkgRepoDir("etc/psa_update");
    QString const &absPathNameRepositoryOpkg = QDir::cleanPath(opkgRepoDir + QDir::separator() + "opkg_commands");
    QString absPathName;
    QString absPathNameRepository;

    psaInstalled.versionInfo.lastCommit = "";
    psaInstalled.versionInfo.reason = "";
    psaInstalled.versionInfo.created = "";

    QStringList versionInfo = m_gc.gitShowReason(m_branchName);
    if (versionInfo.size() == 3) {
        psaInstalled.versionInfo.lastCommit = versionInfo.at(0);
        psaInstalled.versionInfo.reason = versionInfo.at(1);
        psaInstalled.versionInfo.created = versionInfo.at(2);
    }

    if (m_zoneNr != 0) {
        QString const &n = QString("%1").arg(m_zoneNr).rightJustified(2, '0');
        psaInstalled.tariff.name = QString("tariff%1.json").arg(n);
        absPathName = QDir::cleanPath(tariffSysDir + QDir::separator() + psaInstalled.tariff.name);
        psaInstalled.tariff.blob = m_gc.gitBlob(absPathName);
        absPathNameRepository = QDir::cleanPath(tariffRepoDir + QDir::separator() + psaInstalled.tariff.name);
        psaInstalled.tariff.lastCommit = m_gc.gitLastCommit(absPathNameRepository);
        psaInstalled.tariff.size = getFileSize(absPathName);
        psaInstalled.tariff.zone = m_zoneNr;
        psaInstalled.tariff.loadTime = Utils::getTariffLoadTime(absPathName);
    }
    psaInstalled.tariff.project = "Szeged";
    psaInstalled.tariff.info = "N/A";
    psaInstalled.tariff.version = "N/A";

    psaInstalled.hw.linuxVersion = getOsVersion();
    psaInstalled.hw.cpuSerial = m_cpuSerial;

    psaInstalled.opkg.blob = m_gc.gitBlob(absPathNameRepositoryOpkg);
    psaInstalled.opkg.size = getFileSize(absPathNameRepositoryOpkg);
    psaInstalled.opkg.loadTime = Utils::getTariffLoadTime(absPathNameRepositoryOpkg);
    psaInstalled.opkg.lastCommit = m_gc.gitLastCommit(absPathNameRepositoryOpkg);

    psaInstalled.dc.versionHW = deviceControllerVersionHW;
    psaInstalled.dc.versionSW = deviceControllerVersionSW;
    psaInstalled.dc.gitBlob = "N/A";
    psaInstalled.dc.gitLastCommit = "N/A";
    psaInstalled.dc.size = -1;

    if (std::optional<QString> v = getApismVersion()) {
        psaInstalled.sw.apismVersion = v.value();
    }
    psaInstalled.sw.atbQTVersion = getATBQTVersion();
    psaInstalled.sw.atbUpdateToolVersion = m_atbUpdateToolVersion;

    psaInstalled.pluginVersion.deviceController = m_pluginVersionATBDeciceController;
    psaInstalled.pluginVersion.ingenicoISelfCC = m_pluginVersionIngenicoISelf;
    psaInstalled.pluginVersion.mobilisisCalculatePrice = m_pluginVersionMobilisisCalc;
    psaInstalled.pluginVersion.mobilisisCalculatePriceConfigUi = m_pluginVersionMobilisisCalcConfig;
    psaInstalled.pluginVersion.prmCalculatePrice = m_pluginVersionPrmCalc;
    psaInstalled.pluginVersion.prmCalculatePriceConfigUi = m_pluginVersionPrmCalcConfig;
    psaInstalled.pluginVersion.tcpZVT = m_pluginVersionTcpZvt;

    psaInstalled.cash.name = "DC2C_cash.json";
    absPathName = QDir::cleanPath(printSysDir + QDir::separator() + psaInstalled.cash.name);
    psaInstalled.cash.blob = m_gc.gitBlob(absPathName);
    psaInstalled.cash.size = getFileSize(absPathName);

    psaInstalled.conf.name = "DC2C_conf.json";
    absPathName = QDir::cleanPath(printSysDir + QDir::separator() + psaInstalled.conf.name);
    psaInstalled.conf.blob = m_gc.gitBlob(absPathName);
    psaInstalled.conf.size = getFileSize(absPathName);

    psaInstalled.device.name = "DC2C_device.json";
    absPathName = QDir::cleanPath(printSysDir + QDir::separator() + psaInstalled.device.name);
    psaInstalled.device.blob = m_gc.gitBlob(absPathName);
    psaInstalled.device.size = getFileSize(absPathName);

    for (int i=0; i < 32; ++i) {
        QString const &n = QString("%1").arg(i+1).rightJustified(2, '0');
        psaInstalled.print[i].name = QString("DC2C_print%1.json").arg(n);
        absPathName = QDir::cleanPath(printSysDir + QDir::separator() + psaInstalled.print[i].name);
        psaInstalled.print[i].blob = m_gc.gitBlob(absPathName);
        psaInstalled.print[i].size = getFileSize(absPathName);
    }

    return psaInstalled;
}

hwinf *Worker::getPlugin() {
    return m_mainWindow ? m_mainWindow->getPlugin() : nullptr;
}

hwinf const *Worker::getPlugin() const {
    return m_mainWindow ? m_mainWindow->getPlugin() : nullptr;
}

/************************************************************************************************
 * operators
 */
QDebug operator<< (QDebug debug, UpdateStatus status) {
    switch(status.m_updateStatus) {
    case UPDATE_STATUS::ISMAS_SEND_LAST_VERSION_FAILED:
        debug << QString("UPDATE_STATUS::ISMAS_SEND_LAST_VERSION_FAILED: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::SAVE_LOG_FILES_FAILED:
        debug << QString("UPDATE_STATUS::SAVE_LOG_FILES_FAILED: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CHECK_FILES_TO_UPDATE_SUCCESS:
        debug << QString("UPDATE_STATUS::GIT_CHECK_FILES_TO_UPDATE_SUCCESS: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::PSA_UPDATE_FILES_FAILED:
        debug << QString("UPDATE_STATUS::PSA_UPDATE_FILES_FAILED: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::RSYNC_UPDATES_SUCCESS:
        debug << QString("UPDATE_STATUS::RSYNC_UPDATES_SUCCESS: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::RSYNC_UPDATES_FAILURE:
        debug << QString("UPDATE_STATUS::RSYNC_UPDATES_FAILURE: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::EXEC_OPKG_COMMAND:
        debug << QString("UPDATE_STATUS::EXEC_OPKG_COMMAND: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_SUCCESS:
        debug << QString("UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_SUCCESS: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_FAILURE:
        debug << QString("UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_FAILURE: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::NOT_DEFINED:
        debug << QString("UPDATE_STATUS::NOT_DEFINED: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::UPDATE_PROCESS_FAILURE:
        debug << QString("UPDATE_STATUS::UPDATE_PROCESS_FAILURE: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_SET_FAILURE:
        debug << QString("UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_FAILURE: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CHECKOUT_BRANCH_FAILURE:
        debug << QString("UPDATE_STATUS::GIT_CHECKOUT_BRANCH_FAILURE: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CHECKOUT_BRANCH:
        debug << QString("UPDATE_STATUS::GIT_CHECKOUT_BRANCH: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_NOT_SET_OR_WRONG:
        debug << QString("UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_NOT_SET_OR_WRONG: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_SET:
        debug << QString("UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_SET: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_SANITY_CHECK_OK:
        debug << QString("UPDATE_STATUS::ISMAS_SANITY_CHECK_OK: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::JSON_PARSE_FAILURE:
        debug << QString("UPDATE_STATUS::JSON_PARSE_FAILURE: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::BACKEND_CHECK:
        debug << QString("UPDATE_STATUS::BACKEND_CHECK: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::BACKEND_CHECK_FAILURE:
        debug << QString("UPDATE_STATUS::BACKEND_CHECK_FAILURE: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::BACKEND_NOT_CONNECTED:
        debug << QString("UPDATE_STATUS::BACKEND_NOT_CONNECTED: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::UPDATE_PROCESS_SUCCESS:
        debug << QString("UPDATE_STATUS::UPDATE_PROCESS_SUCCESS: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_PENDING:
        debug << QString("UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_PENDING: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE:
        debug << QString("UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE: ")
              << status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_FETCH_UPDATES:
        debug << QString("UPDATE_STATUS::GIT_FETCH_UPDATES: ")
              << status.m_statusDescription;
    break;
    case UPDATE_STATUS::GIT_FETCH_UPDATES_REQUEST_FAILURE:
        debug << QString("UPDATE_STATUS::GIT_FETCH_UPDATES_REQUEST_FAILURE: ")
              << status.m_statusDescription;
    break;
    case UPDATE_STATUS::ISMAS_RESPONSE_RECEIVED:
        debug << QString("UPDATE_STATUS::ISMAS_RESPONSE_RECEIVED: ")
              << status.m_statusDescription;
    break;
    case UPDATE_STATUS::RSYNC_FILE_SUCCESS:
        debug << QString("UPDATE_STATUS::RSYNC_FILE_SUCCESS: ")
              << status.m_statusDescription;
    break;
    case UPDATE_STATUS::EXEC_OPKG_COMMANDS:
        debug << QString("UPDATE_STATUS::EXEC_OPKG_COMMANDS: ")
              << status.m_statusDescription;
    break;
    // default:;
    }
    return debug;
}

QString& operator<< (QString& str, UpdateStatus status) {
    switch(status.m_updateStatus) {
    case UPDATE_STATUS::ISMAS_SEND_LAST_VERSION_FAILED:
        str = QString("UPDATE_STATUS::ISMAS_SEND_LAST_VERSION_FAILED: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::SAVE_LOG_FILES_FAILED:
        str = QString("UPDATE_STATUS::SAVE_LOG_FILES_FAILED: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CHECK_FILES_TO_UPDATE_SUCCESS:
        str = QString("UPDATE_STATUS::GIT_CHECK_FILES_TO_UPDATE_SUCCESS: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::PSA_UPDATE_FILES_FAILED:
        str = QString("UPDATE_STATUS::PSA_UPDATE_FILES_FAILED: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::RSYNC_UPDATES_SUCCESS:
        str = QString("UPDATE_STATUS::RSYNC_UPDATES_SUCCESS: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::RSYNC_UPDATES_FAILURE:
        str = QString("UPDATE_STATUS::RSYNC_UPDATES_FAILURE: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::EXEC_OPKG_COMMAND:
        str = QString("UPDATE_STATUS::EXEC_OPKG_COMMAND: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_SUCCESS:
        str = QString("UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_SUCCESS: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_FAILURE:
        str = QString("UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_FAILURE: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::NOT_DEFINED:
        str = QString("UPDATE_STATUS::NOT_DEFINED: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::UPDATE_PROCESS_FAILURE:
        str = QString("UPDATE_STATUS::UPDATE_PROCESS_FAILURE: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_SET_FAILURE:
        str = QString("UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_FAILURE: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CHECKOUT_BRANCH_FAILURE:
        str = QString("UPDATE_STATUS::GIT_CHECKOUT_BRANCH_FAILURE: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::GIT_CHECKOUT_BRANCH:
        str = QString("UPDATE_STATUS::GIT_CHECKOUT_BRANCH: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_NOT_SET_OR_WRONG:
        str = QString("UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_NOT_SET_OR_WRONG: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE:
        str = QString("UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_SET:
        str = QString("UPDATE_STATUS::ISMAS_UPDATE_TRIGGER_SET: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_SANITY_CHECK_OK:
        str = QString("UPDATE_STATUS::ISMAS_SANITY_CHECK_OK: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::JSON_PARSE_FAILURE:
        str = QString("UPDATE_STATUS::JSON_PARSE_FAILURE: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::BACKEND_CHECK:
        str = QString("UPDATE_STATUS::BACKEND_CHECK: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::BACKEND_CHECK_FAILURE:
        str = QString("UPDATE_STATUS::BACKEND_CHECK_FAILURE: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::BACKEND_NOT_CONNECTED:
        str = QString("UPDATE_STATUS::BACKEND_NOT_CONNECTED: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::UPDATE_PROCESS_SUCCESS:
        str = QString("UPDATE_STATUS::UPDATE_PROCESS_SUCCESS: ");
        str += status.m_statusDescription;
        break;
    case UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_PENDING:
        str = QString("UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_PENDING: ");
        str += status.m_statusDescription;
    break;
    case UPDATE_STATUS::GIT_FETCH_UPDATES:
        str = QString("UPDATE_STATUS::GIT_FETCH_UPDATES: ");
        str += status.m_statusDescription;
    break;
    case UPDATE_STATUS::GIT_FETCH_UPDATES_REQUEST_FAILURE:
        str = QString("UPDATE_STATUS::GIT_FETCH_UPDATES_REQUEST_FAILURE: ");
        str += status.m_statusDescription;
    break;
    case UPDATE_STATUS::ISMAS_RESPONSE_RECEIVED:
        str = QString("UPDATE_STATUS::ISMAS_RESPONSE_RECEIVED: ");
        str += status.m_statusDescription;
    break;
    case UPDATE_STATUS::EXEC_OPKG_COMMANDS:
        str = QString("UPDATE_STATUS::EXEC_OPKG_COMMANDS: ");
        str += status.m_statusDescription;
    break;
    case UPDATE_STATUS::RSYNC_FILE_SUCCESS:
        str = QString("UPDATE_STATUS::RSYNC_FILE_SUCCESS: ");
        str += status.m_statusDescription;
    break;
    //default:;
    }
    return str;
}