#include "ApismClient.h"

#include <QDebug>
#include <QByteArray>
#include <QHostAddress>
#include <QString>
#include <QList>
#include <QListIterator>
#include <QScopedPointer>
#include <QProcess>
#include <QRegularExpression>


ApismClient::ApismClient(QObject *parent)
    : QObject(parent)
    , lastError(0)
    , lastErrorDescription("")
    , currentRequest(ISMAS::REQUEST::NO_REQUEST)
    , retryCounter(0)
    , flagValidParameter(false)
{   
    this->apismTcpSendClient.reset(new ApismTcpClient("127.0.0.1", "7777", ApismTcpClient::ExpectedResponse::STATE, this));
    this->apismTcpRequestResponseClient.reset(new ApismTcpClient("127.0.0.1", "7778", ApismTcpClient::ExpectedResponse::JSON,  this));

    this->apismTcpRequestResponseClient->setResponseTimeout(30000);

    connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::receivedData,
            this, &ApismClient::onReceivedResponse);
    connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::responseTimeout,
            this, &ApismClient::onRequestResponseClientResponseTimeout);
    connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::connectTimeout,
            this, &ApismClient::onRequestResponseClientConnectTimeout);
    connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::connectionClosedByRemoteHost,
            this, &ApismClient::onRequestResponseClientConnectionClosedByRemoteHost);
    connect(apismTcpRequestResponseClient.get(), &ApismTcpClient::connectionRefusedError,
            this, &ApismClient::restartApism);

    connect(apismTcpSendClient.get(), &ApismTcpClient::responseTimeout,
            this, &ApismClient::onSendClientResponseTimeout);
    connect(apismTcpSendClient.get(), &ApismTcpClient::connectionRefusedError,
            this, &ApismClient::restartApism);


    // not needed as APISM closes the socket after we send data, so readyRead()
    // might not even fire
    // connect(&m_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));

    // defined for Qt >= 5.15, we have 5.12
    // qRegisterMetaType<QAbstractSocket::SocketError>();
    // connect(&m_socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)),
    //    this, SLOT(onSocketError(&QAbstractSocket::SocketError)));

    // switch of DEBUG:
    this->apismTcpSendClient->setDebug(true);
    this->apismTcpRequestResponseClient->setDebug(true);
}

ApismClient::~ApismClient() {
}

void ApismClient::restartApism() {
    QProcess::startDetached("/bin/systemctl", {"restart", "apism"});
}


void ApismClient::sendSelfTest() {
    this->currentRequest = ISMAS::REQUEST::SELF;
    this->apismTcpRequestResponseClient->sendData("#M=APISM#C=REQ_SELF#J={}");
}

void ApismClient::sendRequestParameter() {
    this->currentRequest = ISMAS::REQUEST::PARAMETER;
    this->apismTcpRequestResponseClient->sendData("#M=APISM#C=REQ_ISMASPARAMETER#J={}");
}

void ApismClient::handleISMASResponseError()
{    
    switch (this->currentRequest) {
    case ISMAS::REQUEST::NO_REQUEST:
        qCritical() << "ApismClient::onReceivedResponse() for unknown Request: " << currentRequest;
        break;
    case ISMAS::REQUEST::PING:
        //emit this->sendMininformPingResponse(nsApismInterface::RESULT_STATE::ERROR_BACKEND, QJsonObject());
        break;
    case ISMAS::REQUEST::SELF:
        //emit this->sendReqSelfResponse(nsApismInterface::RESULT_STATE::ERROR_BACKEND, QJsonObject());
        break;
    case ISMAS::REQUEST::PARAMETER:
        //emit this->sendReqParameterResponse(nsApismInterface::RESULT_STATE::ERROR_BACKEND, QJsonObject());
        break;
    }
    this->currentRequest = ISMAS::REQUEST::NO_REQUEST;
}


void ApismClient::handleISMASOfflineResponseError()
{
    nsApismInterface::RESULT_STATE resultState;

    if (this->retryCounter < 50) {
        resultState = nsApismInterface::RESULT_STATE::ERROR_RETRY;
        this->retryCounter++;
    }
    else {
        resultState = nsApismInterface::RESULT_STATE::ERROR_BACKEND;
        this->retryCounter = 0;
    }

    qCritical() << "ApismClient::handleISMASOfflineResponseError(): currentRequest is " << this->currentRequest;


    switch (this->currentRequest) {
    case ISMAS::REQUEST::NO_REQUEST:
        break;
    case ISMAS::REQUEST::PING:
        //emit this->sendMininformPingResponse(resultState, QJsonObject());
        //break;
    case ISMAS::REQUEST::SELF:
        emit this->sendReqSelfResponse(resultState, QJsonObject());
        break;
    case ISMAS::REQUEST::PARAMETER:
        emit this->sendReqParameterResponse(resultState, QJsonObject());
        break;
    }
    this->currentRequest = ISMAS::REQUEST::NO_REQUEST;

    // note: this does not work here, because a new request within this error handling method
    // must use a new socket connection!
    // This does not work with an allready open socket connection.
    /* Communication to APISM must be in the following way:
     *     1) establish socket connection
     *     2) send your request to this socket
     *     3) wait for a possible answer
     *     4) receive the answer
     *     5) close socket connection
     *
     * => a request can only be sent to apsim if the socket is closed!
     * => apism (socket) is blocked systemwide until socket is closed!
     * =>
     */

    //this->sendSelfTest();
}



void ApismClient::private_handleErrorResponse(QString errorString)
{
    qCritical() << "------------------------------------------------------";
    qCritical() << "ApismClient::private_handleErrorResponse(" << errorString << ")";
    qCritical() << "        this->retryCounter = " << this->retryCounter;
    qCritical() << "------------------------------------------------------";

    this->handleISMASOfflineResponseError();
}


/*
{\r\n    \"REQ_START#53365_Response\": {\r\n        \"Aknoledge\": \"OK\"\r\n    },
 \r\n    \"Response\": {\r\n        \"Result\": \"ERR:Ung�ltiger Datums-String: \"\r\n    }\r\n}
*/

/*
: ISMAS received:  "{\r\n    \"REQ_PING#30844_Response\": {\r\n        \"Aknoledge\": \"OK\"\r\n    },
     "PING": { "IsAviable": "TRUE" }
  }"
*/

//void ApismClient::onSendClientResponseTimeout()
//{
//
//}

void ApismClient::private_handleReqSelfResponse(QJsonObject response)
{
    bool ismasConnected  = response["ISMAS"].toBool();
    QString brokerConnectedString = response["Broker"].toString();

    qCritical() << "ApismClient::handleReqSelfResponse() " << endl
                << "                  ismasConnected = " << ismasConnected << endl
                << "           brokerConnectedString = " << brokerConnectedString;

    /* possible values:    "Restart connection"
     *                     "Connected"
     *                     "NOT CONNECTED"
     */
    if (brokerConnectedString == "NOT CONNECTED") {
        qCritical() << "ApismClient: \"NOT CONNECTED\": restart APISM";
        this->restartApism();
    }

    emit this->sendReqSelfResponse(nsApismInterface::RESULT_STATE::SUCCESS, response);
}

/**
 * sample parameter response:
        {
            "REQ_ISMASPARAMETER#4210959_Response": {
                "Aknoledge": "OK"
            },
            "Dev_ID": {
                "Device_Type": "2020",
                "Custom_ID": 332,
                "Device_ID": 99
            },
            "Fileupload": {
                "TRG": ""
            },
            "Parameter": {
                "Location": "An Der Bahn 11",
                "Group": "Group1",
                "Zone": "Zone1",
                "Name": "PSA",
                "SecNumber": "0",
                "LastAcc": "0",
                "GPSLat": "49.6062",
                "GPSLon": "12.1244",
                "TarifID": "none",
                "UserTextID": "1"
                "TERMINALID": "",
                "TERMINALKEY": ""
            },
            "Lock": {
                "Locked": "0"
            }
        }
 */
void ApismClient::private_handleParameterResponse(QJsonObject response)
{
    // extract location info and store location info in persistent data:
    QJsonValue jsonSubVal_Location;
    jsonSubVal_Location = response["Location"];

    QString locationString = jsonSubVal_Location.toString("no location");

    //this->persistentData->setParameter("Location", locationString);

    // feature UserText
    QJsonValue jsonSubVal_UserText;
    jsonSubVal_UserText = response["UserTextID"];
    QString userTextIdString = jsonSubVal_UserText.toString("0");
    //this->persistentData->setParameter("UserTextID", userTextIdString);

    // TERMINALID
    //QJsonValue jsonSubVal_TERMINALID;
    //jsonSubVal_TERMINALID = response["TERMINALID"];
    //QString terminalIdString = jsonSubVal_TERMINALID.toString("");
    //QString terminalIdFile = this->m_config->getSystemDataPath() + "TERMINALID";
    //QString terminalIdStringOrigin = ATBSystem::readPSAConfigString(terminalIdFile);
    //if ( (terminalIdString.size() > 1) &&
    //     (terminalIdString != terminalIdStringOrigin))
    //{
    //    qCritical() << "ApismClient::handleParameterResponse(): new TERMINALID: " << terminalIdString;
    //    ATBSystem::setPSAConfigString(terminalIdFile, terminalIdString);
    //}

    // TERMINALKEY
    //QJsonValue jsonSubVal_TERMINALKEY;
    //jsonSubVal_TERMINALKEY = response["TERMINALKEY"];
    //QString terminalKeyString = jsonSubVal_TERMINALKEY.toString("");
    //QString terminalJeyFile = this->m_config->getSystemDataPath() + "TERMINALKEY";
    //QString terminalKeyStringOrigin = ATBSystem::readPSAConfigString(terminalJeyFile);
    //if ( (terminalKeyString.size() > 1) &&
    //     (terminalKeyString != terminalKeyStringOrigin))
    //{
    //    qCritical() << "ApismClient::handleParameterResponse(): new TERMINALKEY: " << terminalKeyString;
    //

    //    ATBSystem::setPSAConfigString(terminalJeyFile, terminalKeyString);
    //}


    //qCritical() << "ApismClient::handleParameterResponse() " << endl
    //            << "                  Location = " << locationString   << endl
    //            << "                UserTextID = " << userTextIdString << endl
    //            << "                TERMINALID = " << terminalIdString << endl
    //            << "               TERMINALKEY = " << terminalKeyString;


    if (locationString != "no location") {
        this->flagValidParameter = true;
        emit this->sendReqParameterResponse(nsApismInterface::RESULT_STATE::SUCCESS, response);
    }
}

void ApismClient::private_handleFileUploadResponse(QJsonObject response) {
    QJsonValue jsonSubVal_TRG{response["TRG"]};
    if (jsonSubVal_TRG.isUndefined()) {
        return;
    }

    bool updateRequested = (jsonSubVal_TRG.toString("") == "WAIT");

}

/************************************************************************************************
 * operators
 */
QDebug operator<< (QDebug debug, ISMAS::REQUEST request)
{
    switch(request) {
    case ISMAS::REQUEST::NO_REQUEST:
        debug << QString("ISMAS::REQUEST::NO_REQUEST");
        break;
    case ISMAS::REQUEST::PING:
        debug << QString("ISMAS::REQUEST::PING");
        break;
    case ISMAS::REQUEST::SELF:
        debug << QString("ISMAS::REQUEST::SELF");
        break;
    case ISMAS::REQUEST::PARAMETER:
        debug << QString("ISMAS::REQUEST::PARAMETER");
        break;
    }
    return debug;
}

QString& operator<< (QString& str, ISMAS::REQUEST request)
{
    switch(request) {
    case ISMAS::REQUEST::NO_REQUEST:
        str = QString("ISMAS::REQUEST::NO_REQUEST");
        break;
    case ISMAS::REQUEST::PING:
        str = QString("ISMAS::REQUEST::PING");
        break;
    case ISMAS::REQUEST::SELF:
        str = QString("ISMAS::REQUEST::SELF");
        break;
    case ISMAS::REQUEST::PARAMETER:
        str = QString("ISMAS::REQUEST::PARAMETER");
        break;
    }
    return str;
}