#include "headers/baseconn.h"

BaseConn * pGlobalBaseConn = nullptr;

BaseConn::BaseConn(QObject *parent) : QObject(parent)
{
    pGlobalBaseConn = this;
    socket = new QTcpSocket();
    this->setState("disconnected");

    connect(this->socket, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(gotError(QAbstractSocket::SocketError)));

    this->nextConnectionId = 1;
}

bool BaseConn::connectToHost() {
    qDebug() << "connecting";
    setState("connecting");
    this->connection_progress = 0;
    QEventLoop loop;
    QTimer timer;

    timer.setSingleShot(true);
    // quit the loop when the timer times out
    loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
    //quit the loop when the connection was established
    loop.connect(this->socket, SIGNAL(connected()), &loop, SLOT(quit()));
    // start the timer before starting to connect
    timer.start(3000);
    //connect
    this->socket->connectToHost(this->ip, this->port);

    //wait for the connection to finish (programm gets stuck in here)
    loop.exec();

    //loop finish

    if(timer.remainingTime() == -1){
        //the time has been triggered -> timeout
        this->socket->abort();
        setState("disconnected");
        return(false);
    }

    // stop the timer as the connection has been established
    timer.stop();
    connect(this->socket, &QTcpSocket::readyRead, this, &BaseConn::readyRead);
    this->connection_progress = 100;
    setState("connected");
    return(true);
}

void BaseConn::closeConnection()
{
    qDebug() << "closing connection";
    switch (socket->state())
    {
    case 0:
        socket->disconnectFromHost();
        break;
    case 2:
        socket->abort();
        break;
    default:
        socket->abort();
    }
    setState("disconnected");
//    for(int i = 0; i < this->waitingRequests.length(); i++){
//            this->waitingRequests[i].reply = "ERR_NOT_CONNECTED";
//            this->waitingRequests[i].loop->quit();
//            return;
//    }
}

void BaseConn::gotError(QAbstractSocket::SocketError err)
{
    //qDebug() << "got error";
    QString strError = "unknown";
    switch (err)
    {
        case 0:
            strError = "Connection was refused";
            break;
        case 1:
            strError = "Remote host closed the connection";
            this->closeConnection();
            break;
        case 2:
            strError = "Host address was not found";
            break;
        case 5:
            strError = "Connection timed out";
            break;
        default:
            strError = "Unknown error";
    }

    emit gotError(strError);
    qDebug() << "got socket error: " << strError;
}

QVariantMap BaseConn::sendCommand(int header, QJsonValue data){
    if(this->state != "connected"){
        return {{"status", 910}, {"data", "not connected"}};
    }

    // generate id and witing requests entry
    int thisId = nextConnectionId;
    nextConnectionId ++;

    QEventLoop loop;
    QJsonObject reply;

    this->waitingRequests.append({thisId, &loop, reply});

    QJsonObject requestObj;
    requestObj.insert("id", thisId);
    requestObj.insert("header", header);
    requestObj.insert("data", data);


    QString jsonRequest = QJsonDocument(requestObj).toJson();

    QByteArray arrBlock;
    QDataStream out(&arrBlock, QIODevice::WriteOnly);
    //out.setVersion(QDataStream::Qt_5_10);
    out << quint16(0) << jsonRequest;

    out.device()->seek(0);
    out << quint16(ulong(arrBlock.size()) - sizeof(quint16));

    QTimer timer;

    timer.setSingleShot(true);
    // quit the loop when the timer times out
    loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
    // quit the loop when the connection was established
    // loop.connect(this, &BaseConn::gotReply, &loop, &QEventLoop::quit);
    // start the timer before starting to connect
    timer.start(3000);

    //write data

    socket->write(arrBlock);

    //wait for an answer to finish (programm gets stuck in here)
    loop.exec();

    //loop finished
    if(timer.remainingTime() == -1){
        //the time has been triggered -> timeout

        return {{"status", 911}, {"data", ""}};
    }

    for(int i = 0; i<this->waitingRequests.length(); i++){
        if(this->waitingRequests[i].id == thisId){
            reply = this->waitingRequests[i].reply;
        }
    }

    // stop the timer as the connection has been established
    timer.stop();

    timer.deleteLater();

    //remoteSessions.release(1);
    return {{"status", reply.value("header").toInt()}, {"data", reply.value("data").toVariant()}};

}

void BaseConn::readyRead() {
    QDataStream in(socket);
    //in.setVersion(QDataStream::Qt_5_10);
    qint16 nextBlockSize = 0;
    for (;;)
    {
        if (!nextBlockSize)
        {
            if (ulong(socket->bytesAvailable()) < sizeof(quint16)) { break; }
            in >> nextBlockSize;
        }

        if (socket->bytesAvailable() < nextBlockSize) { break; }

        QString str; in >> str;

        //        if (str == "0")
        //        {
        //            str = "Connection closed";
        //            closeConnection();
        //        }

        nextBlockSize = 0;
        QString reply = str;
        int id = 0;

        QJsonDocument jsonReply = QJsonDocument::fromJson(reply.toUtf8());
        QJsonObject replyObj = jsonReply.object();

        if(!replyObj.isEmpty()){
            id = replyObj.value("id").toInt();

            for(int i = 0; i < this->waitingRequests.length(); i++){
                if(this->waitingRequests[i].id == id){
                    this->waitingRequests[i].reply = replyObj;
                    this->waitingRequests[i].loop->quit();
                    return;
                }
            }
        }

        latestReadReply = str;
        emit gotUnexpectedReply(str);

    }
}

int BaseConn::writeRemoteSetting(QString key, QString value) {
    QJsonArray requestData;
    requestData.append(key);
    requestData.append(value);
    return this->sendCommand(3000, requestData)["status"].toInt();
}

void BaseConn::setIP(const QString &ipAdress){
    this->ip = ipAdress;
}

QString BaseConn::getIP() const
{
    return(this->ip);
}

QString BaseConn::getState() const
{
    return(this->state);
}

void BaseConn::setState(QString newState){
    this->state = newState;
    emit stateChanged();
}

int BaseConn::getProgress() const
{
    return(connection_progress);
}

bool BaseConn::refreshConnections() {
    QVariantMap reply = this->sendCommand(2006);

    if(reply["status"] != 200){
        //handle Error!!
        qDebug() << "+ --- error refreshing connections: " << reply["status"];
        return false;
    }

    QVariantList tmpConnections = reply["data"].toList();

    this->connections = reply["data"].toList();

    return true;

}

QVariant BaseConn::getConnections() {
    return(connections);
}
