#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 <QScopedPointer>

#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_WRONG   (  "[WRONG]");
QString const Worker::UPDATE_STEP_FAIL   (   " [FAIL]");
QString const Worker::UPDATE_STEP_SUCCESS(" [SUCCESS]");

using UPDATE_STEP = Worker::UPDATE_STEP;
const QMap<UPDATE_STEP, const char*> Worker::smap (
    std::initializer_list<std::pair<UPDATE_STEP, const char*>>{
#define INSERT_ELEMENT(p) std::pair(p, #p)
        INSERT_ELEMENT(UPDATE_STEP::STARTED),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_SANITY),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_SANITY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_SANITY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::REPOSITORY_RECOVERED_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::REPOSITORY_RECOVERED_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CLONE_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::CLONE_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CLONE_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_ISMAS_TRIGGER),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_ISMAS_TRIGGER_WRONG_VALUE),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_ISMAS_TRIGGER_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::INITIAL_CLONE_WITHOUT_ACTIVE_ISMAS_TRIGGER),
        INSERT_ELEMENT(UPDATE_STEP::INITIAL_CLONE_WITH_ACTIVE_ISMAS_TRIGGER),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_BRANCH),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_BRANCH_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECKOUT_BRANCH_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_FOR_REPOSITORY_CHANGES),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_FOR_REPOSITORY_CHANGES_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::CHECK_FOR_REPOSITORY_CHANGES_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::FILES_TO_UPDATE),
        INSERT_ELEMENT(UPDATE_STEP::APPLY_REPOSITORY_CHANGES),
        INSERT_ELEMENT(UPDATE_STEP::FILES_TO_DOWNLOAD),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMANDS),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_1),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_2),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_3),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_4),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_5),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_6),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_7),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_8),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_9),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_LAST),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::EXEC_OPKG_COMMAND_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_CONFIG_FILE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_CONFIG_FILE_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_CONFIG_FILE_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_DEVICE_CONTROLLER),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_DEVICE_CONTROLLER_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::DOWNLOAD_DEVICE_CONTROLLER_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::APPLY_REPOSITORY_CHANGES_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::APPLY_REPOSITORY_CHANGES_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY),
        INSERT_ELEMENT(UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::SAVE_LOGS),
        INSERT_ELEMENT(UPDATE_STEP::SAVE_LOGS_SUCCESS),
        INSERT_ELEMENT(UPDATE_STEP::SAVE_LOGS_FAILURE),
        INSERT_ELEMENT(UPDATE_STEP::SEND_LAST_VERSION),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_SUCCEEDED),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_FAILED),
        INSERT_ELEMENT(UPDATE_STEP::UPDATE_ACTIVATED),
        INSERT_ELEMENT(UPDATE_STEP::FINISHED),
        INSERT_ELEMENT(UPDATE_STEP::DEBUG),
        INSERT_ELEMENT(UPDATE_STEP::ERROR)
#undef INSERT_ELEMENT
});

Worker *Worker::instance = nullptr;

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_mainWindow(nullptr) /* contains plugin */
  //, m_withoutIsmasDirectPort(true) /* useful for testing */ {
  , m_withoutIsmasDirectPort(false) /* useful for testing */ {

    // TODO: turn object into singleton
    instance = this;

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

    m_apismVersion = getAPISMYoctoVersion();

    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::displayProgressInMainWindow(int progress) {
    if (m_mainWindow) {
        QApplication::postEvent(m_mainWindow,
            new ProgressEvent(this, progress));
    }
}

void Worker::setProgress(int progress) {
    m_ismasClient.setProgressInPercent(progress);
    displayProgressInMainWindow(progress);
}


void Worker::startProgressLoop() {
    displayProgressInMainWindow(MainWindow::START_PROGRESS_LOOP);
}

void Worker::stopProgressLoop() {
    displayProgressInMainWindow(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);
}

bool Worker::isRepositoryCorrupted() {
    QDir customerRepository(m_customerRepository);
    if (customerRepository.exists()) {
        QDir customerRepositoryEtc(QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/"));
        QDir customerRepositoryOpt(QDir::cleanPath(m_customerRepository + QDir::separator() + "opt/"));
        QDir customerRepositoryGit(QDir::cleanPath(m_customerRepository + QDir::separator() + ".git/"));
        if (!m_gc.gitFsck()
            // etc-directory inside git-repository does not exist, which means the
            // git-repository is corrupted -> remove it and start from scratch
         || !customerRepositoryEtc.exists()
         || !customerRepositoryGit.exists()
         || !customerRepositoryOpt.exists()) {
            // should never happen
            Utils::printCriticalErrorMsg("CORRUPTED CUSTOMER REPOSITORY");
            return true;
        }
    }
    return false;
}

bool Worker::repairCorruptedRepository() {
    QDir customerRepository(m_customerRepository);
    if (!customerRepository.removeRecursively()) {

        Utils::printCriticalErrorMsg("ERROR REMOVING CORR. CUST-REPOSITORY");

        //m_updateStatus = UpdateStatus(UPDATE_STATUS::REMOVE_GIT_REPOSITORY_FAILED,
        //                    QString("REMOVAL OF GIT-REPOSITORY %1 FAILED").arg(m_customerRepository));
        //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 sanity check", m_updateStatus.m_statusDescription);

        return false;
    }
    return true;
}

#if 0
int Worker::sendCloneAndCheckoutFailure() {
    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(m_updateStatus.m_statusDescription));

    return CLONE_AND_CHECKOUT_FAILURE;
}

int Worker::sendCloneAndCheckoutSuccess() {
    m_updateStatus = UpdateStatus(UPDATE_STATUS::GIT_CLONE_AND_CHECKOUT_SUCCESS,
                          QString("CLONED REPOSITORY %1 AND CHECKED OUT BRANCH %2")
                            .arg(m_customerRepository)
                            .arg(m_gc.branchName()));

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

    return CLONE_AND_CHECKOUT_SUCCESS;
}

int Worker::sendIsmasTriggerFailure() {
    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));
    return ISMAS_TRIGGER_FAILURE;
}

int Worker::sendCustomerEnvironmentConfigurationFailed() {
    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));
    return ENVIRONMENT_CONFIG_FAILURE;
}

int Worker::sendPullFailure() {
    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));
    return GIT_PULL_FAILURE;
}

int Worker::sendFileUpdateFailure() {
    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));
    return UPDATE_FILES_FAILURE;
}

int Worker::sendRsyncFailure() {
    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));

    return RSYNC_FAILURE;
}

int Worker::sendLastVersionFailure() {
    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));
    return SEND_LAST_VERSION_FAILURE;
}

int Worker::sendSaveLogFilesFailure() {
    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));
    return SAVE_LOG_FILES_FAILURE;
}

int Worker::sendFoundFilesToUpdateSuccess() {
    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));
    return 0;
}

int Worker::sendUpdateSucceededAndActivated() {
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::UPDATE_SUCCEEDED));
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::UPDATE_ACTIVATED));
    //IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
    //    QString("#M=APISM#C=CMD_EVENT#J=") +
    //        m_ismasClient.updateOfPSASucceeded(""));

    //m_ismasClient.setProgressInPercent(100);

    // 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());

    return UPDATE_SUCCESS_AND_ACTIVATED;
}
int Worker::sendFinalResult() {
    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());
    }

    return 0;
}

#endif

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

    QString func(__PRETTY_FUNCTION__);

    GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::STARTED));

    QScopedPointer<UpdateProcessRunning> upr(new UpdateProcessRunning(this));

    QDir customerRepository(m_customerRepository);
    QDir customerRepositoryEtc(QDir::cleanPath(m_customerRepository + QDir::separator() + "etc/"));

    CONSOLE() << UPDATE_STEP::CHECK_SANITY;

    m_clone = false;
    m_repairClone = false;
    m_initialClone = false;
                                    // the customer repository is cloned or
                                    // repaired/re-cloned without checking the
                                    // ISMAS-trigger (WAIT-)button.

    // Case 1: no existing repository:

                                    // if there was a sane repository
                                    // available, then the trigger-button is
                                    // checked:
                                    // 1: trigger == WAIT: then
                                    // have been activated in ISMAS.

    bool continueUpdate = true;     // check if git-clone command has timed-out,
                                    // resulting in a corrupted git-repository, which
                                    // does not contain an ./etc-directory

    if (isRepositoryCorrupted()) {  // a not-existing repository is not meant
                                    // to be corrupted
        CONSOLE() << UPDATE_STEP::CHECK_SANITY_FAILURE;
        if ((continueUpdate = repairCorruptedRepository()) == true) {
            m_repairClone = true;
            CONSOLE() << UPDATE_STEP::REPOSITORY_RECOVERED_SUCCESS;
        } else {
            ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::REPOSITORY_RECOVERED_FAILURE));
            return;
        }
    }

    CONSOLE() << UPDATE_STEP::CHECK_SANITY_SUCCESS;

    if (continueUpdate) {
        if ((continueUpdate = customerRepository.exists()) == false) {
            m_initialClone = (m_repairClone == false);
            GUI() << (CONSOLE() << UPDATE_STEP::CLONE_REPOSITORY);
            for (int i = 0; i < 5; ++i) {   // try to checkout git repository
                setProgress(i);             // and switch to branch
                if (m_gc.gitCloneAndCheckoutBranch()) {
                    if (!isRepositoryCorrupted()) {
                        GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::CLONE_REPOSITORY_SUCCESS));
                        continueUpdate = true;
                        m_clone = true;
                        break;
                    }
                }
                QThread::sleep(1);  // maybe git needs more time
            }

            if (continueUpdate == false) {
                GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::CLONE_REPOSITORY_FAILURE));
                return;
            }

            Q_ASSERT_X(m_clone, (func + QString(":%1").arg(__LINE__)).toStdString().c_str(), "clone failed");

        } else {

            Q_ASSERT_X(!m_clone, (func + QString(":%1").arg(__LINE__)).toStdString().c_str(), "m_clone not false");
            Q_ASSERT_X(!m_initialClone, (func + QString(":%1").arg(__LINE__)).toStdString().c_str(), "m_initialClone not false");
            Q_ASSERT_X(!m_repairClone, (func + QString(":%1").arg(__LINE__)).toStdString().c_str(), "m_repairClone not false");

            CONSOLE() << UPDATE_STEP::CHECK_REPOSITORY;
            if (isRepositoryCorrupted()) {
                ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::CHECK_REPOSITORY_FAILURE));
                return;
            }
        }
    }
    CONSOLE() << UPDATE_STEP::CHECK_REPOSITORY_SUCCESS;


    ////////////////////////////////////////////////////////////////////////////
    //
    //                         CHECK UPDATE TRIGGER
    //
    ////////////////////////////////////////////////////////////////////////////
    m_ismasTriggerActive = false;
    if ((continueUpdate = updateTriggerSet()) == false) {
        if (m_initialClone) {
            GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::INITIAL_CLONE_WITHOUT_ACTIVE_ISMAS_TRIGGER));
        }
        return;
    } else {
        GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::INITIAL_CLONE_WITH_ACTIVE_ISMAS_TRIGGER));
    }
    if (m_ismasTriggerActive == false) {// make it explicit again: only if the
                                        // ismas trigger is active ('WAIT'),
                                        // then proceed
        return;
    }
    GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::CHECK_ISMAS_TRIGGER_SUCCESS));


    ////////////////////////////////////////////////////////////////////////////
    //
    //                           CHECK-OUT BRANCH
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = customerEnvironment()) == false) {
        return;
    }
    CONSOLE() << UPDATE_STEP::CHECKOUT_BRANCH_SUCCESS;


    ////////////////////////////////////////////////////////////////////////////
    //
    //               COMPUTE CHANGED FILES OF CUSTOMER REPOSITORY
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = filesToUpdate()) == false) {
        return;
    }
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::UPDATE_REPOSITORY_SUCCESS));


    ////////////////////////////////////////////////////////////////////////////
    //
    //                  UPDATE THE PSA USING THE CHANGED FILES
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = updateFiles()) == false) {
        return;
    }
    GUI() << (CONSOLE() << UPDATE_STEP::APPLY_REPOSITORY_CHANGES_SUCCESS);


    ////////////////////////////////////////////////////////////////////////////
    //
    //             (R)SYNC THE REPOSITORY WITH THE LOCAL FILEYSTEM
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = syncCustomerRepositoryAndFS()) == false) {
        return;
    }
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_SUCCESS));


    ////////////////////////////////////////////////////////////////////////////
    //
    //                            FUTURE: SAVE LOG FILES
    //
    ////////////////////////////////////////////////////////////////////////////
    if ((continueUpdate = saveLogFile()) == false) {
        return;
    }
    // ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SAVE_LOGS_SUCCESS));


    ////////////////////////////////////////////////////////////////////////////
    //
    //                      FINAL MESSAGES (PART 1)
    //
    ////////////////////////////////////////////////////////////////////////////
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::UPDATE_SUCCEEDED));
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::UPDATE_ACTIVATED));
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::FINISHED));


    ////////////////////////////////////////////////////////////////////////////
    //
    //                 FINAL MESSAGES (PART 2): SEND-LAST-VERSION
    //                 (destructor of struct UpdateProcessRunning)
    //
    ////////////////////////////////////////////////////////////////////////////
}

bool Worker::updateTriggerSet() {
    // repository is existent and not corrupted. check now if the ISMAS-trigger
    // (WAIT-button) is activated even in case of initial checkout
    static const QString func = "UPDATE-TRIGGER-SET";

    if (m_withoutIsmasDirectPort) { // useful for testing
        return true;
    }

    GUI() << (ISMAS() << (CONSOLE() << UPDATE_STEP::CHECK_ISMAS_TRIGGER));

    QString triggerValue("");

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

        if (repeat > 1) {
            int const startMs = QTime::currentTime().msecsSinceStartOfDay();
            int const durationMs = QTime::currentTime().msecsSinceStartOfDay() - startMs;
            QString const &s = QString("elapsed: %1.%2s").arg(durationMs / 1000).arg(durationMs % 1000);
            CONSOLE(QStringList(func) << s) << UPDATE_STEP::DEBUG;
        } else {
            CONSOLE(QStringList(func) << QString("-> REPEAT=%1").arg(repeat)) << UPDATE_STEP::DEBUG;
        }

        if ((repeat % 10) == 0) {
            CONSOLE(QStringList(func) << "RESTART APISM") << UPDATE_STEP::DEBUG;
            Command c("systemctl restart apism");
            if (c.execute("/tmp")) {
                QThread::sleep(20); // give APISM some time to reconnect
                CONSOLE(QStringList(func) << "RESTART APISM DONE") << UPDATE_STEP::DEBUG;
            }
        }

        if (std::optional<QString> result
                = IsmasClient::sendRequestReceiveResponse(
                    IsmasClient::APISM::DIRECT_PORT, "#M=APISM#C=REQ_ISMASPARAMETER#J={}")) {

            QString const &msg = QString("APISM RESPONSE(%1)=(").arg(repeat) + result.value() + ")";
            CONSOLE(QStringList(func) << msg) << UPDATE_STEP::DEBUG;

            QJsonParseError parseError;
            QJsonDocument document(QJsonDocument::fromJson(result.value().toUtf8(), &parseError));
            if (parseError.error != QJsonParseError::NoError) {
                QStringList lst(QString("INVALID JSON MSG: PARSING FAILED (json=<START>%1<END> error=[%2] str=[%3] offset=[%4])")
                                .arg(msg)
                                .arg(parseError.error)
                                .arg(parseError.errorString())
                                .arg(parseError.offset));
                GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                break;
            }
            if (!document.isObject()) {
                QStringList lst(QString("not a json-object %1").arg(result.value()));
                GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                break;
            }

            QJsonObject obj = document.object();

            // always look for an 'error' first
            if (obj.contains("error")) {
                QStringList lst(obj.value("error").toString());
                CONSOLE(QStringList(lst)) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE;
                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")) {
                        int const customerNr = obj.value("Custom_ID").toInt(-1);
                        int const machineNr = obj.value("Device_ID").toInt(-1);
                        if (customerNr != m_customerNr) {
                            QStringList lst(QString("CUSTOMER-NR (%1) != LOCAL CUSTOMER-NR (%2)")
                                            .arg(customerNr).arg(m_customerNr));
                            GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                            break;
                        }
                        if (machineNr != m_machineNr) {
                            QStringList lst(QString("MACHINE-NR (%1) != LOCAL MACHINE-NR (%2)")
                                                .arg(machineNr).arg(m_machineNr));
                            GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                            break;
                        }
                    } else {
                        QStringList lst("Dev_ID DOES NOT CONTAIN Custom_ID AND/OR Device_ID");
                        GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                        break;
                    }
                } else {
                    QStringList lst("Dev_ID KEY NOT A JSON-OBJECT");
                    GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                    break;
                }
            } else {
                QStringList lst("Dev_ID KEY NOT AVAILABLE");
                GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                break;
            }

            if (obj.contains("Fileupload")) {
                QJsonValue v = obj.value("Fileupload");
                if (v.isObject()) {
                    obj = v.toObject();
                    if (obj.contains("TRG")) {
                        if ((triggerValue = obj.value("TRG").toString()) == "WAIT") {
                            m_ismasTriggerActive = true;
                            return m_ismasTriggerActive;
                        } else
                        if (QRegExp("\\s*").exactMatch(triggerValue)) { // check for whitespace
                            QStringList lst("empty update trigger");
                            if (m_clone) {
                                GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_WRONG_VALUE);
                                                    // if the customer repository has just been cloned
                                break;              // it is OK the ISMAS trigger might not be 'WAIT'
                            } else {
                                GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                            }
                            QThread::sleep(6);
                            continue;
                        } else {
                            // if the download-button once has the wrong value, it will never recover
                            if (m_clone) {
                                GUI() << (CONSOLE() << UPDATE_STEP::CHECK_ISMAS_TRIGGER_WRONG_VALUE);
                            } else {
                                QStringList lst(QString("TRIGGER-VALUE=<%1> NOT 'WAIT'").arg(triggerValue));
                                GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                            }
                            break;
                        }
                    } else {
                        QStringList lst("TRG key not available");
                        GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                        break;
                    }
                } else {
                    QStringList lst("Fileupload not a json-object");
                    GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                    break;
                }
            } else {
                QStringList lst(QString("Fileupload not available"));
                GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
                break;
            }
        } else {
            QStringList lst = QStringList(QString("no ISMAS response"));
            GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE);
            QThread::sleep(6);
        }
    }

    if (m_initialClone == false) {
        if (!triggerValue.contains("WAIT", Qt::CaseInsensitive)) {
            QStringList lst(QString("ISMAS_UPDATE-TRIGGER-NOT-SET-OR-WRONG: VALUE=(") + triggerValue + ")");
            ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECK_ISMAS_TRIGGER_FAILURE));
        }
    }

    return false;
}

bool Worker::customerEnvironment() {
    // configure customer environment -> checkout branch in case someone has
    // changed the zone_nr

    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::CHECKOUT_BRANCH));

    if (QDir(m_customerRepository).exists()) {
        if (m_gc.gitCheckoutBranch()) {
            return true;
        } else {
            QStringList lst(QString("CHECKOUT OF " + m_customerRepository + "FAILED"));
            ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECKOUT_BRANCH_FAILURE));
        }
    } else {// cannot happen
        QStringList lst(QString(m_customerRepository + " DOES NOT EXIST"));
        ISMAS(lst) << (GUI(lst) << (CONSOLE(lst) << UPDATE_STEP::CHECKOUT_BRANCH_FAILURE));
    }

    return false;
}

bool Worker::filesToUpdate() {
    // determine which files has to be updated: either sent to the hardware or
    // rsynced with the filesystem in case of tariff-files

    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::UPDATE_REPOSITORY));

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

    if (std::optional<QString> changes = m_gc.gitPull()) {
        if (!changes.value().contains("Already up to date")) {
            if (std::optional<QStringList> changedFileNames = m_gc.gitDiff(changes.value())) {
                m_filesToUpdate << changedFileNames.value();
                m_filesToUpdate.removeDuplicates();
            }
        }
        GUI() << (CONSOLE(m_filesToUpdate) << UPDATE_STEP::FILES_TO_UPDATE);
    } else {
        ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::UPDATE_REPOSITORY_FAILURE));
    }

    return true;
}

bool Worker::updateFiles() {
    m_filesToDownload.clear();
    m_displayIndex = 0;

    GUI(m_filesToUpdate) << (CONSOLE(m_filesToUpdate) << UPDATE_STEP::APPLY_REPOSITORY_CHANGES);

    for (int i = 0; i < m_filesToUpdate.size(); ++i) {
        QString const fName = m_filesToUpdate.at(i);
        if (fName.contains("opkg_commands", Qt::CaseInsensitive)) {
            GUI() << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMANDS);
            // execute opkg commands
            if (QDir::setCurrent(m_customerRepository)) {
                QFile f(fName);
                if (f.exists()) {
                    if (f.open(QIODevice::ReadOnly)) {
                        QTextStream in(&f);
                        m_opkgCommands.clear();
                        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);
                                    m_opkgCommands << cmd;

                                    QStringList const opkgLst(opkgCommand);
                                    QStringList const cmdLst(cmd);

                                    switch(m_opkgCommands.size()) {
                                    case 1:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_1));
                                    break;
                                    case 2:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_2));
                                    break;
                                    case 3:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_3));
                                    break;
                                    case 4:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_4));
                                    break;
                                    case 5:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_5));
                                    break;
                                    case 6:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_6));
                                    break;
                                    case 7:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_7));
                                    break;
                                    case 8:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_8));
                                    break;
                                    case 9:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_9));
                                    break;
                                    default:
                                        ISMAS(opkgLst) << (GUI(cmdLst) << (CONSOLE() << UPDATE_STEP::EXEC_OPKG_COMMAND_LAST));
                                    }
                                }
                            }
                        }
                        f.close();
                        if (!executeOpkgCommandFailed) {
                            if (m_opkgCommands.size() > 0) {
                                m_displayIndex = 1;
                                GUI() << UPDATE_STEP::EXEC_OPKG_COMMAND_SUCCESS;
                            }
                        } else {
                            m_displayIndex = 1;
                            GUI() << UPDATE_STEP::EXEC_OPKG_COMMAND_FAILURE;
                            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)) {
            m_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)) {
                m_filesToDownload << fName; // download device controller
            }
        }
    }

    if (m_filesToDownload.size() > 0) {
        CONSOLE(m_filesToDownload) << UPDATE_STEP::FILES_TO_DOWNLOAD;

        Update *update = m_mainWindow->getUpdate();
        if (update) {
            return update->doUpdate(m_displayIndex, m_filesToDownload);
        } else {
            CONSOLE(QStringList("UPDATE NOT SET")) << UPDATE_STEP::ERROR;
        }
    } else {
        CONSOLE(QStringList("NO FILES TO DOENLOAD TO PSA-HW")) << UPDATE_STEP::ERROR;
    }

    return true;
}

bool Worker::syncCustomerRepositoryAndFS() {
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY));

    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() << ")";
            }
            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) {
                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;
                    }
                }
            }
            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)) {
                    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_SUCCESS));
                    return true;
                }
            }
        }
    }
    ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SYNC_CUSTOMER_REPOSITORY_FAILURE));
    return false;
}

#if 0
bool Worker::sendIsmasLastVersionNotification(int progress) {
    m_ismasClient.setProgressInPercent(progress);
    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;
}
#endif

bool Worker::saveLogFile() {
    // ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SAVE_LOGS));
    // ISMAS() << (GUI() << (CONSOLE() << UPDATE_STEP::SAVE_LOGS_FAILURE));
    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::getAPISMYoctoVersion() {
    if (QFile::exists("/var/lib/opkg/status")) {
        QString const cmd = QString("echo -n $(cat /var/lib/opkg/status | grep -A1 apism | tail -n 1 | cut -d':' -f2 | cut -d' ' -f2)");
        Command c("bash");
        if (c.execute("/tmp", QStringList() << "-c" << cmd)) {
            return c.getCommandResult(); // 1.4.1.0-r4
        }
    }
    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::getAPISMYoctoInstallationStatus()  {
    if (QFile::exists("/var/lib/opkg/status")) {
        QString const cmd = QString("echo -n $(cat /var/lib/opkg/status | grep -A3 apism | 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;

    psaInstalled.sw.apismVersion = getAPISMYoctoVersion();
    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;
}
