#include "ismas/ismas_client.h"
#include "utils.h"

#include <cstring>
#include <cstdio>

#include <errno.h>
#include <arpa/inet.h> // inet_addr()
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <string.h>
#include <strings.h> // bzero()
#include <sys/socket.h>
#include <unistd.h> // read(), write(), close()
#include <fcntl.h>


#include <QThread>
#include <QJsonDocument>
#include <QJsonObject>

#if 0
########################
# Spec vom 27.10.2023:
# U0010 -> %-Werte
# U0001 -> 100%
# U0003 -> "FAIL"
# U0002 -> "" (OK -> WAIT state reset)
# ISMAS -> "WAIT"
########################
#
# $1: EVENT: U0001 update finished: 100%
#            U0002 reset TRG
#            U0003 error
#            U0010 for update process
# $2: PERCENT : "only for ISMAS: 0-100%",
# $3: RESULTCODE : "only for ISMAS",
#   0: Success
#   1: no Update nessesary
#   2: Backup failed
#   3: Package error/ Wrong package
#   4: Install Error
# $4: STEP : "running step (only for us): update_psa...",
# $5: STEP_RESULT : "error and result text",
# $6: VERSION : "opkg and conf info; what will be updated"
#
#endif

#include <QDateTime>
#include <QDebug>

void IsmasClient::printDebugMessage(int port,
                                    QString const &clientIP,
                                    int clientPort,
                                    QString const &message) {
#if 0
    Q_UNUSED(port);
    Q_UNUSED(clientIP);
    Q_UNUSED(clientPort);
    Q_UNUSED(message);
#else
    qDebug().noquote()
        << "\n"
        << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n"
        << "hostname ........" << "127.0.0.1"  << "\n"
        << "port ............" << port << "\n"
        << "local address ..." << clientIP << "\n"
        << "local port ......" << clientPort << "\n"
        << message;
#endif
}

void IsmasClient::printInfoMessage(int port,
                                   QString const &clientIP,
                                   int clientPort,
                                   QString const &message) {
#if 0
    Q_UNUSED(port);
    Q_UNUSED(clientIP);
    Q_UNUSED(clientPort);
    Q_UNUSED(message);
#else
    qInfo().noquote()
        << "\n"
        << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n"
        << "hostname ........" << "127.0.0.1"  << "\n"
        << "port ............" << port << "\n"
        << "local address ..." << clientIP << "\n"
        << "local port ......" << clientPort << "\n"
        << message;
#endif
}

void IsmasClient::printErrorMessage(int port,
                                    QString const &clientIP,
                                    int clientPort,
                                    QString const &message) {
    qCritical().noquote()
        << "\n"
        << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n"
        << "hostname ........" << "127.0.0.1"  << "\n"
        << "port ............" << port << "\n"
        << "local address ..." << clientIP << "\n"
        << "local port ......" << clientPort << "\n"
        << message;
}

std::optional<QString>
IsmasClient::sendRequestReceiveResponse(int port, QString const &request) {

    qInfo() << "REQUEST" << request;

    int sockfd;
    int r;
    errno = 0;
    // socket create and verification
    if ((sockfd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        qCritical().noquote()
            << "\n"
            << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n"
            << "SOCKET CREATION FAILED (" << strerror(errno) << ")";
        return std::nullopt;
    }

    struct sockaddr_in servAddr;
    bzero(&servAddr, sizeof(servAddr));
    // assign IP, PORT
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    servAddr.sin_port = htons(port);
    // connect the client socket to server socket
    if ((r = ::connect(sockfd, (struct sockaddr *)(&servAddr), sizeof(servAddr))) != 0) {
        qCritical().noquote()
            << "\n"
            << "SEND-REQUEST-RECEIVE-RESPONSE ..." << "\n"
            << "CONNECTION WITH SERVER FAILED (" << strerror(r) << ")";
        ::close(sockfd);
        return std::nullopt;
    }

    struct sockaddr_in clientAddr;
    bzero(&clientAddr, sizeof(clientAddr));
    socklen_t sockLen = sizeof(clientAddr);

    char clientIP[16];
    bzero(&clientIP, sizeof(clientIP));
    getsockname(sockfd, (struct sockaddr *)(&clientAddr), &sockLen);
    inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP));
    unsigned int clientPort = ntohs(clientAddr.sin_port);

    printDebugMessage(port, clientIP, clientPort, QString("CONNECTED TO SERVER"));

    struct timeval tv;
    tv.tv_sec = 10;  /* 10 secs timeout for read and write */

    struct linger so_linger;
    so_linger.l_onoff = 1;
    so_linger.l_linger = 0;

    int maxfdp1;
    fd_set rset;
    fd_set wset;

    setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
    // no reliable, but does not harm, as we use select() as well
    setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    int flag = 1;
    setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));

    static char buf[1024*8];
    bzero(buf, sizeof(buf));
    int const bytesToWrite = strlen(request.toStdString().c_str());
    strncpy(buf, request.toStdString().c_str(), sizeof(buf)-1);

    int loop = 0;
    int bytesWritten = 0;
    while (bytesWritten < bytesToWrite) {
        errno = 0;
        FD_ZERO(&wset);
        FD_SET(sockfd, &wset);
        maxfdp1 = sockfd + 1;
        tv.tv_sec = 60;  /* 60 secs timeout for read and write -> APISM cuts the connection after 30s */
        tv.tv_usec = 0;

        int const w = select(maxfdp1, NULL, &wset, NULL, &tv);
        if (w < 0) { // error
            if (errno == EINTR) {
                printErrorMessage(port, clientIP, clientPort,
                    QString("INTERRUPTED BY SIGNAL (1) (") + strerror(errno) + ")");
                continue;
            } else {
                printErrorMessage(port, clientIP, clientPort,
                        QString("SELECT-ERROR (WRITE) %1(").arg(loop) + strerror(errno) + ")");
                ::close(sockfd);
                return std::nullopt;
            }
        } else
        if (w == 0) { // timeout
            printErrorMessage(port, clientIP, clientPort,
                    QString("SELECT-TIMEOUT (WRITE) %1(").arg(loop) + strerror(errno) + ")");
            if (++loop < 10) {
                QThread::msleep(500);
                continue;
            }
            ::close(sockfd);
            return std::nullopt;
        } else
        if (w > 0) {
            int n = ::sendto(sockfd, buf+bytesWritten, bytesToWrite-bytesWritten, 0, NULL, 0);
            if (n >= 0) {
                bytesWritten += n;
            } else {
                if (errno == EWOULDBLOCK) {
                    if (++loop < 10) {
                        QThread::msleep(500);
                        continue;
                    }
                    printErrorMessage(port, clientIP, clientPort,
                        QString("WRITE TIMEOUT %1(").arg(loop) + strerror(errno) + ")");
                    ::close(sockfd);
                    return std::nullopt;
                } else
                if (errno == EINTR) {
                    printErrorMessage(port, clientIP, clientPort,
                        QString("WRITE INTERRUPTED BY SIGNAL (1) (") + strerror(errno) + ")");
                    continue;
                }
            }
        }
    }

    // DO NOT USE SHUTDOWN! APISM CAN NOT COPE WITH IT
    // errno = 0;
    // if (shutdown(sockfd, SHUT_WR) < 0) {
    //     printErrorMessage(port, clientIP, clientPort,
    //        QString("CANNOT CLOSE WRITING END (") + strerror(errno) + ")");
    // }

    printInfoMessage(port, clientIP, clientPort, QString("MESSAGE SENT <<<") + buf + ">>>");

    loop = 0;
    bzero(buf, sizeof(buf));
    int bytesToRead = sizeof(buf)-1;
    int bytesRead = 0;
    while (bytesRead < bytesToRead) {
        errno = 0;
        FD_ZERO(&rset);
        FD_SET(sockfd, &rset);
        maxfdp1 = sockfd + 1;
        tv.tv_sec = 60;  /* 60 secs timeout for read and write */
        tv.tv_usec = 0;

        QString const selectStart = QDateTime::currentDateTime().toString(Qt::ISODateWithMs);

        int const r = select(maxfdp1, &rset, NULL, NULL, &tv);
        if (r < 0) { // error
            if (errno == EINTR) {
                printErrorMessage(port, clientIP, clientPort,
                    QString("INTERRUPTED BY SIGNAL (2) (") + strerror(errno) + ")");
                continue;
            } else {
                printErrorMessage(port, clientIP, clientPort,
                        QString("SELECT-ERROR (READ) %1(").arg(loop) + strerror(errno) + ")");
                ::close(sockfd);
                return std::nullopt;
            }
        } else
        if (r == 0) { // timeout
            printErrorMessage(port, clientIP, clientPort,
                    QString("SELECT-TIMEOUT (READ) %1(").arg(loop) + strerror(errno) + ")");
            if (++loop < 10) {
                QThread::msleep(500);
                continue;
            }
            ::close(sockfd);
            return std::nullopt;
        } else
        if (r > 0) {
            if (FD_ISSET(sockfd, &rset)) {
                int n = ::recvfrom(sockfd, buf+bytesRead, bytesToRead-bytesRead,
                                   0, NULL, NULL);
                if (n > 0) { //
                    bytesRead += n;
                } else
                if (n == 0) {
                    // The return value will be 0 when the peer has performed an orderly shutdown.
                    printErrorMessage(port, clientIP, clientPort,
                        QString("PEER CLOSED CONNECTION (") + strerror(errno) + ") START AT" +
                                selectStart + " NOW " + QDateTime::currentDateTime().toString(Qt::ISODateWithMs));
                    ::close(sockfd);
                    return std::nullopt;
                } else
                if (n < 0) {
                    if (errno == EWOULDBLOCK) { // check just in case
                        if (++loop < 10) {
                            QThread::msleep(500);
                            continue;
                        }
                        printErrorMessage(port, clientIP, clientPort,
                                QString("READ TIMEOUT %1(").arg(loop) + strerror(errno) + ")");
                        ::close(sockfd);
                        return std::nullopt;
                    }
                    if (errno == EINTR) {
                        printErrorMessage(port, clientIP, clientPort,
                            QString("INTERRUPTED BY SIGNAL (2) (") + strerror(errno) + ")");
                        continue;
                    }
                }
            }
        }

        // printInfoMessage(port, clientIP, clientPort, QString("MESSAGE RECEIVED ") + buf);
        QString response(buf);

        if (int idx = response.indexOf("{\"error\":\"ISMAS is offline\"}")) {
            response = response.mid(0, idx);
        } else
        if (response.contains("RECORD")) { // RECORD SAVED or RECORD WRITE ABORTED
            printInfoMessage(port, clientIP, clientPort, QString("IGNORED '") + response + "' RESPONSE");
            ::close(sockfd);
            return std::nullopt;
        }

        QJsonParseError parseError;
        QJsonDocument document(QJsonDocument::fromJson(response.toUtf8(), &parseError));
        if (parseError.error == QJsonParseError::NoError) {
            if (document.isObject()) { // done: received valid APISM response
                printInfoMessage(port, clientIP, clientPort,
                   QString("VALID APISM RESPONSE .. \n") + response);
                ::close(sockfd);
                return response;
            } else {
                printInfoMessage(port, clientIP, clientPort,
                   QString("CORRUPTED RESPONSE ") + response);
                ::close(sockfd);
                return std::nullopt;
            }
        } else {
            printDebugMessage(port, clientIP, clientPort,
                QString("PARSE ERROR ") + response + " " + parseError.errorString());
            ::close(sockfd);
            return std::nullopt;
        }
    }
    return std::nullopt;
}

QString IsmasClient::updateNewsToIsmas(char const *event,
                                       int percent,
                                       int resultCode,
                                       char const *step,
                                       char const *step_result,
                                       char const *version) {
    char buf[1024];
    memset(buf, 0, sizeof(buf));

    QString const ts = QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
    snprintf(buf, sizeof(buf)-1,
        "{"
            "\"REASON\":\"SW_UP\","
            "\"TIMESTAMP\":\"%s\","
            "\"EVENT_ID\":\"0\","
            "\"EVENT\":\"%s\","
            "\"EVENTSTATE\":1,"
            "\"PARAMETER\": {"
                "\"PERCENT\" : %d,"
                "\"RESULTCODE\" : %d,"
                "\"STEP\" : \"%s\","
                "\"STEP_RESULT\" : \"%s\","
                "\"VERSION\" : \"%s\""
            "}"
        "}", ts.toStdString().c_str(), event, percent, resultCode,
             step, step_result, version);
    return buf;
}

QString IsmasClient::errorBackendNotConnected(QString const &info,
                                              QString const &version) {
    return updateNewsToIsmas("U0003",
                             m_progressInPercent,
                             RESULT_CODE::ISMAS_NO_CONNECTION_ERROR,
                             "CHECK BACKEND CONNECTIVITY",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::errorGitClone(QString const &info,
                                   QString const &version) {
    return updateNewsToIsmas("U0003",
                             m_progressInPercent,
                             RESULT_CODE::GIT_CLONE_ERROR,
                             "CLONE CUSTOMER REPOSITORY FAILED",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::backendConnected(QString const &info,
                                      QString const &version) {
    return updateNewsToIsmas("U0010",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "CHECK BACKEND CONNECTIVITY",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::execOpkgCommand(QString const &info,
                                     QString const &version) {
    return updateNewsToIsmas("U0010",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "EXECUTE OPKG COMMAND",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::rsyncFile(QString const &info, QString const &version) {
    return updateNewsToIsmas("U0010",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "RSYNC FILE",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::updateTriggerSet(QString const &info, QString const &version) {
    return updateNewsToIsmas("U0010",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "CHECK UPDATE TRIGGER",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());

}

QString IsmasClient::errorUpdateTrigger(QString const &info, QString const &version) {
    return updateNewsToIsmas("U0003",
                             m_progressInPercent,
                             RESULT_CODE::ISMAS_TRIGGER_ERROR,
                             "CHECK UPDATE TRIGGER",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());

}

QString IsmasClient::updateOfPSASendVersion(PSAInstalled const &psa) {
    //static int const constexpr SIZE = 4096*8;
    static char buf[4096*2];
    memset(buf, 0, sizeof(buf));

    // local data="#M=APISM#C=CMD_SENDVERSION#J=
    snprintf(buf, sizeof(buf)-1,
        "{"
            "\"VERSION_INFO\" : {"
                "\"REASON\":\"%s\","
                "\"CREATED\":\"%s\","
                "\"HASH\":\"%s\""
            "},"
            "\"TARIFF\" : {"
                "\"VERSION\" : \"%s\","
                "\"PROJECT\" : \"%s\","
                "\"ZONE\" : %d,"
                "\"INFO\" : \"%s\","
                "\"BLOB\" : \"%s\","
                "\"LAST-COMMIT\" : \"%s\","
                "\"SIZE\" : %d,"
                "\"LOADED\" : \"%s\""
            "},"
            "\"OPKG_COMMANDS\" : {"
                "\"BLOB\" : \"%s\","
                "\"LAST-COMMIT\" : \"%s\","
                "\"SIZE\" : %d,"
                "\"LOADED\" : \"%s\""
            "},"
            "\"JSON\" : {"
                "\"DC2C_CASH\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_CONF\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_DEVICE\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_01\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_02\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_03\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_04\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_05\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_06\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_07\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_08\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_09\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_10\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_11\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_12\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_13\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_14\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_15\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_16\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_17\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_18\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_19\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_20\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_21\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_22\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_23\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_24\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_25\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_26\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_27\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_28\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_29\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_30\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_31\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "},"
                "\"DC2C_PRINT_32\" : {"
                    "\"BLOB\" : \"%s\","
                    "\"SIZE\" : %d"
                "}"
            "},"
            "\"HARDWARE\" : {"
                "\"DEVICES\" : [\"PTU5\", \"DC\", \"PRINTER\", \"BNA\"]"
            "},"
            "\"OS\" : {"
                "\"Linux\": \"%s\""
            "},"
            "\"CONFIG\" : {"
                "\"PTU5\" : {"
                    "\"CPU_SERIAL\" : \"%s\""
                "},"
                "\"DC\" : {"
                    "\"HW-VERSION\" : \"%s\","
                    "\"SW-VERSION\" : \"%s\","
                    "\"SIZE\" : %d,"
                    "\"GITBLOB\" : \"%s\","
                    "\"GITLASTCOMMIT\" : \"%s\""
                "},"
                "\"PRINTER\" : {"
                "},"
                "\"BNA\" : {"
                "}"
            "},"
            "\"SOFTWARE\": {"
                "\"APISM\" : {"
                    "\"VERSION\" : \"%s\""
                "},"
                "\"ATBQT\" : {"
                    "\"VERSION\" : \"%s\""
                "},"
                "\"ATB-UPDATE-TOOL\" : {"
                    "\"VERSION\" : \"%s\""
                "}"
            "},"
            "\"PLUGINS\" : {"
                "\"libATBDeviceControllerPlugin.so\" : {"
                    "\"VERSION\" : \"%s\""
                "},"
                "\"libIngenicoISelf_CCPlugin.so\" : {"
                    "\"VERSION\" : \"%s\""
                "},"
                "\"libMOBILISIS_CalculatePricePlugin.so\" : {"
                    "\"VERSION\" : \"%s\""
                "},"
                "\"libMOBILISIS_CalculatePricePlugin_ConfigUi.so\" : {"
                    "\"VERSION\" : \"%s\""
                "},"
                "\"libPRM_CalculatePricePlugin.so\" : {"
                    "\"VERSION\" : \"%s\""
                "},"
                "\"libPRM_CalculatePricePlugin_ConfigUi.so\" : {"
                    "\"VERSION\" : \"%s\""
                "},"
                "\"libTCP_ZVT_CCPlugin.so\" : {"
                    "\"VERSION\" : \"%s\""
                "}"
            "}"
        "}",
        psa.versionInfo.reason.toStdString().c_str(),
        psa.versionInfo.created.toStdString().c_str(),
        psa.versionInfo.lastCommit.toStdString().c_str(),

        psa.tariff.version.toStdString().c_str(),
        psa.tariff.project.toStdString().c_str(),
        psa.tariff.zone,
        psa.tariff.info.toStdString().c_str(),
        psa.tariff.blob.toStdString().c_str(),
        psa.tariff.lastCommit.toStdString().c_str(),
        psa.tariff.size,
        psa.tariff.loadTime.toStdString().c_str(),

        psa.opkg.blob.toStdString().c_str(),
        psa.opkg.lastCommit.toStdString().c_str(),
        psa.opkg.size,
        psa.opkg.loadTime.toStdString().c_str(),

        psa.cash.blob.toStdString().c_str(),
        psa.cash.size,
        psa.conf.blob.toStdString().c_str(),
        psa.conf.size,
        psa.device.blob.toStdString().c_str(),
        psa.device.size,

        psa.print[0].blob.toStdString().c_str(),
        psa.print[0].size,
        psa.print[1].blob.toStdString().c_str(),
        psa.print[1].size,
        psa.print[2].blob.toStdString().c_str(),
        psa.print[2].size,
        psa.print[3].blob.toStdString().c_str(),
        psa.print[3].size,
        psa.print[4].blob.toStdString().c_str(),
        psa.print[4].size,
        psa.print[5].blob.toStdString().c_str(),
        psa.print[5].size,
        psa.print[6].blob.toStdString().c_str(),
        psa.print[6].size,
        psa.print[7].blob.toStdString().c_str(),
        psa.print[7].size,
        psa.print[8].blob.toStdString().c_str(),
        psa.print[8].size,
        psa.print[9].blob.toStdString().c_str(),
        psa.print[9].size,
        psa.print[10].blob.toStdString().c_str(),
        psa.print[10].size,
        psa.print[11].blob.toStdString().c_str(),
        psa.print[11].size,
        psa.print[12].blob.toStdString().c_str(),
        psa.print[12].size,
        psa.print[13].blob.toStdString().c_str(),
        psa.print[13].size,
        psa.print[14].blob.toStdString().c_str(),
        psa.print[14].size,
        psa.print[15].blob.toStdString().c_str(),
        psa.print[15].size,
        psa.print[16].blob.toStdString().c_str(),
        psa.print[16].size,
        psa.print[17].blob.toStdString().c_str(),
        psa.print[17].size,
        psa.print[18].blob.toStdString().c_str(),
        psa.print[18].size,
        psa.print[19].blob.toStdString().c_str(),
        psa.print[19].size,
        psa.print[20].blob.toStdString().c_str(),
        psa.print[20].size,
        psa.print[21].blob.toStdString().c_str(),
        psa.print[21].size,
        psa.print[22].blob.toStdString().c_str(),
        psa.print[22].size,
        psa.print[23].blob.toStdString().c_str(),
        psa.print[23].size,
        psa.print[24].blob.toStdString().c_str(),
        psa.print[24].size,
        psa.print[25].blob.toStdString().c_str(),
        psa.print[25].size,
        psa.print[26].blob.toStdString().c_str(),
        psa.print[26].size,
        psa.print[27].blob.toStdString().c_str(),
        psa.print[27].size,
        psa.print[28].blob.toStdString().c_str(),
        psa.print[28].size,
        psa.print[29].blob.toStdString().c_str(),
        psa.print[29].size,
        psa.print[30].blob.toStdString().c_str(),
        psa.print[30].size,
        psa.print[31].blob.toStdString().c_str(),
        psa.print[31].size,

        psa.hw.linuxVersion.toStdString().c_str(),
        psa.hw.cpuSerial.toStdString().c_str(),

        psa.dc.versionHW.toStdString().c_str(),
        psa.dc.versionSW.toStdString().c_str(),
        psa.dc.size,
        psa.dc.gitBlob.toStdString().c_str(),
        psa.dc.gitLastCommit.toStdString().c_str(),

        psa.sw.apismVersion.toStdString().c_str(),
        psa.sw.atbQTVersion.toStdString().c_str(),
        psa.sw.atbUpdateToolVersion.toStdString().c_str(),

        psa.pluginVersion.deviceController.toStdString().c_str(),
        psa.pluginVersion.ingenicoISelfCC.toStdString().c_str(),
        psa.pluginVersion.mobilisisCalculatePrice.toStdString().c_str(),
        psa.pluginVersion.mobilisisCalculatePriceConfigUi.toStdString().c_str(),
        psa.pluginVersion.prmCalculatePrice.toStdString().c_str(),
        psa.pluginVersion.prmCalculatePriceConfigUi.toStdString().c_str(),
        psa.pluginVersion.tcpZVT.toStdString().c_str());

    qInfo() << buf;

    return buf;
}

#if 0
// prepare
QString IsmasClient::sendLastVersion(UPDATE_COMPONENT updateComponent,
                                     PSAInstalled const &psa) {
    static char buf[4096*2];
    memset(buf, 0, sizeof(buf));

    switch (updateComponent) {
    case UPDATE_COMPONENT::TARIFF:
    snprintf(buf, sizeof(buf)-1,
        "{"
            "\"VERSION_INFO\" : {"
                "\"UPDATE_REASON\":\"%s\","
                "\"CREATED\":\"%s\","
                "\"GIT_COMMIT\":\"%s\""
            "},"
            "\"TARIFF\" : {"
                "\"VERSION\" : \"%s\","
                "\"PROJECT\" : \"%s\","
                "\"ZONE\" : %d,"
                "\"INFO\" : \"%s\","
                "\"SIZE\" : %d\""
            "},"
        "}",
        psa.tariff.versionInfo.reason.toStdString().c_str(),
        psa.tariff.versionInfo.created.toStdString().c_str(),
        psa.tariff.versionInfo.lastCommit.toStdString().c_str(),

        psa.tariff.version.toStdString().c_str(),
        psa.tariff.project.toStdString().c_str(),
        psa.tariff.zone,
        psa.tariff.info.toStdString().c_str(),
        psa.tariff.size);
    break;
    case UPDATE_COMPONENT::SOFTWARE_ATBQT:
    break;
    case UPDATE_COMPONENT::SOFTWARE_APISM:
    break;
    case UPDATE_COMPONENT::SOFTWARE_ATB_UPDATE_TOOL:
    break;
    case UPDATE_COMPONENT::CONFIG_PTU5_CPU_SERIAL:
    break;
    case UPDATE_COMPONENT::CONFIG_DEVICE_CONTROLLER:
    break;
    case UPDATE_COMPONENT::CONFIG_PRINTER:
    break;
    case UPDATE_COMPONENT::CONFIG_BNA:
    break;
    case UPDATE_COMPONENT::PLUGIN_ATB_DEVICE_CONTROLLER:
    break;
    case UPDATE_COMPONENT::PLUGIN_INGENICO_CC:
    break;
    case UPDATE_COMPONENT::PLUGIN_MOBILISIS_CALC_PRICE:
    break;
    case UPDATE_COMPONENT::PLUGIN_MOBILISIS_CALC_PRICE_UI:
    break;
    case UPDATE_COMPONENT::PLUGIN_PRM_CALC_PRICE:
    break;
    case UPDATE_COMPONENT::PLUGIN_PRM_CALC_PRICE_UI:
    break;
    case UPDATE_COMPONENT::PLUGIN_TCP_ZVT_CC:
    break;
    case UPDATE_COMPONENT::OPKG_COMMANDS:
    break;
    case UPDATE_COMPONENT::HARDWARE_DEVICES:
    break;
    case UPDATE_COMPONENT::OS:
    break;
    case UPDATE_COMPONENT::DC2C_CASH_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_CONF_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_DEVICE_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT01_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT02_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT03_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT04_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT05_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT06_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT07_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT08_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT09_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT10_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT11_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT12_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT13_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT14_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT15_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT16_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT17_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT18_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT19_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT20_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT21_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT22_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT23_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT24_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT25_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT26_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT27_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT28_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT29_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT30_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT31_JSON:
    break;
    case UPDATE_COMPONENT::DC2C_PRINT32_JSON:
    break;
    }

    qInfo() << buf;

    return buf;
}
#endif

QString IsmasClient::updateOfPSAContinues(QString currentStage,
                                          QString currentStageInfo,
                                          QString const &version) {
    return updateNewsToIsmas("U0010",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             currentStage.toStdString().c_str(),
                             currentStageInfo.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::updateOfPSAStarted(QString const &version) {
    return updateNewsToIsmas("U0010",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "START",
                             "detected WAIT state: start update process",
                             version.toStdString().c_str());
}

QString IsmasClient::checkoutBranch(QString const &info, QString const &version) {
    return updateNewsToIsmas("U0010",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "BRANCH-CHECKOUT",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::cloneAndCheckoutCustomerRepository(QString const &info, QString const &version) { // clone and checkout customer repository
    return updateNewsToIsmas("U0010",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "CLONE-CHECKOUT",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::gitFetch(QString const &info, QString const &version) {
    return updateNewsToIsmas("U0010",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "GIT-FETCH",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::errorGitFetch(int resultCode, QString const &info, QString const &version) {
    return updateNewsToIsmas("U0003",
                             m_progressInPercent,
                             resultCode,
                             "GIT-FETCH-FAILED",
                             info.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::updateOfPSAActivated(QString const &version) {   // sent even after success
    m_progressInPercent = 0;
    return updateNewsToIsmas("U0002",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "UPDATE ACTIVATED",
                             "reset WAIT state",
                             version.toStdString().c_str());
}

QString IsmasClient::updateOfPSASucceeded(QString const &version) {
    m_progressInPercent = 100;
    return updateNewsToIsmas("U0001",
                             m_progressInPercent,
                             RESULT_CODE::SUCCESS,
                             "UPDATE SUCCESS",
                             "update process succeeded",
                             version.toStdString().c_str());
}

QString IsmasClient::sanityCheckFailed(int resultCode, QString reason, QString const &version) {
    return updateNewsToIsmas("U0003",
                             m_progressInPercent,
                             resultCode,
                             "SANITY-CHECK-FAILED",
                             reason.toStdString().c_str(),
                             version.toStdString().c_str());
}

QString IsmasClient::jsonParseFailed(int resultCode, QString reason, QString const &version) {
    return updateNewsToIsmas("U0003",
                             m_progressInPercent,
                             resultCode,
                             "JSON-PARSE-ERROR",
                             reason.toStdString().c_str(),
                             version.toStdString().c_str());
}

std::optional<QString> IsmasClient::finalResult(int resultCode,
                                                QString reason,
                                                QString const &version) {
    Q_UNUSED(resultCode);
    Q_UNUSED(reason);
    Q_UNUSED(version);

    /*
    m_progressInPercent = 100;
    if (resultCode == RESULT_CODE::SUCCESS) {
        return updateNewsToIsmas("U0002",
                                 m_progressInPercent,
                                 resultCode,
                                 "FINAL-UPDATE-RESULT",
                                 reason.toStdString().c_str(),
                                 version.toStdString().c_str());
    }
    if (resultCode == RESULT_CODE::INSTALL_ERROR) {
        return updateNewsToIsmas("U0003",
                                 m_progressInPercent,
                                 resultCode,
                                 "FINAL-UPDATE-RESULT",
                                 reason.toStdString().c_str(),
                                 version.toStdString().c_str());
    }
    */
    return std::nullopt;
}

QString IsmasClient::updateOfPSAFailed(int resultCode, QString step,
                                       QString reason, QString const &version) {
    return updateNewsToIsmas("U0003",
                             m_progressInPercent,
                             resultCode,
                             step.toStdString().c_str(),
                             reason.toStdString().c_str(),
                             version.toStdString().c_str());
}

char const *IsmasClient::reason[REASON::ENTRIES] = {
    "TIME-TRIGGERED", "SERVICE", "DEV-TEST"
};

QString IsmasClient::getReasonForLastSendVersion() {
    QString const &parentName = Utils::getParentName();
    if (parentName == "ATBQT") {
        return reason[REASON::SERVICE];
    }
    if (parentName == "systemd") {
        return reason[REASON::TIME_TRIGGERED];
    }
    return reason[REASON::DEV_TEST];
}