#include "apism_tcp_client.h"

#include <QHostAddress>
#include <QTimer>
#include <QCoreApplication>

ApismTcpClient::ApismTcpClient(const QString & hostname,
                               const QString & port,
                               QObject *parent)
    : QObject(parent)
    , hostname(hostname)
    , port(port)
    , responseTimerTimeoutCounter(0)
{  
    this->responseTimeoutTimer = new QTimer(this);
    this->responseTimeoutTimer->setInterval(10000);
    this->responseTimeoutTimer->setSingleShot(true);

    connect(this->responseTimeoutTimer, SIGNAL(timeout()), this, SLOT(onResponseTimeoutTimerTimeout()));

    socket = new QTcpSocket(this);
    connect(socket, SIGNAL(connected()),    this, SLOT(onSocketConnected()));
    connect(socket, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
    connect(socket, SIGNAL(readyRead()),    this, SLOT(onSocketReadyRead()));
    connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(onSocketBytesWritten(qint64)));
}



void ApismTcpClient::connectToHost() {
    qCritical() << "ApismTcpClient::connectToHost(this->" << hostname << ", " << port << ")";
    int portNumber = this->port.toInt();
    this->socket->connectToHost(QHostAddress(this->hostname), portNumber);
    if (!socket->waitForConnected(10000)) {
        qCritical() << "ERROR IN WAIT FOR CONNECTED" << socket->errorString();
    } else {
        qDebug() << "connected to" << hostname << ", " << port << ")";
    }
}

void ApismTcpClient::connectToHost(const QString & hostname, const QString & port)
{
    qCritical() << "ApismTcpClient::connectToHost(" << hostname << ", " << port << ")";

    int portNumber = port.toInt();
    socket->connectToHost(hostname, portNumber);
}

void ApismTcpClient::closeConnection()
{
    socket->close();
}

bool ApismTcpClient::isConnected()
{
    bool result = false;
    QAbstractSocket::SocketState socketState = socket->state();

    switch (socketState) {
        case QAbstractSocket::UnconnectedState:
            /* FALLTHRU */
        case QAbstractSocket::HostLookupState:
            /* FALLTHRU */
        case QAbstractSocket::ConnectingState:
            result = false;
            break;
        case QAbstractSocket::ConnectedState:
            /* FALLTHRU */
        case QAbstractSocket::BoundState:
            result = true;
            break;
        case QAbstractSocket::ClosingState:
            /* FALLTHRU */
        case QAbstractSocket::ListeningState:
            result = false;
            break;
    }

    return result;
}




void ApismTcpClient::sendData(const QByteArray & message) {
    qDebug() << "ApismTcpClient::send: " << message;

    this->sendQueue.enqueue(message);

    if (this->isConnected()) {
        qCritical() << "ApismTcpClient::send: connected, send" << message;
        this->private_sendData();
    } else {
        qCritical() << "ApismTcpClient::send: not connected, connect";
        this->connectToHost();
    }
}

/**
 * @brief ApismTcpClient::private_sendData
 *
 * Precondition is that queue is not empty.
 */
void ApismTcpClient::private_sendData()
{
    // take message from queue
    QByteArray ba = this->sendQueue.dequeue();

    qDebug() << "ApismTcpClient::send: " << QString(ba);

    socket->write(ba);
    socket->flush();

    // start timeoutTimer
    this->responseTimeoutTimer->start();
}

void ApismTcpClient::onSocketConnected()
{
    qInfo() << "ApismTcpClient: Connected!";

    if (this->sendQueue.size() > 0) {
        this->private_sendData();
    }
}

void ApismTcpClient::onSocketDisconnected()
{
    qDebug() << "ApismTcpClient: Disconnected!";
    qDebug() << "        -> SocketErrorString: " << socket->errorString();

    if (this->sendQueue.size() > 0) {
        this->connectToHost();
    }
}

void ApismTcpClient::onSocketBytesWritten(qint64 bytes)
{
    Q_UNUSED(bytes)
}

void ApismTcpClient::onSocketReadyRead()
{
    QByteArray readData;

    // stop timeoutTimer
    this->responseTimeoutTimer->stop();

    readData = socket->readAll();

    qDebug() << "ISMAS received: " << QString(readData);

    emit this->receivedData(readData);
    //QCoreApplication::processEvents();

    this->socket->close();
}


void ApismTcpClient::onResponseTimeoutTimerTimeout()
{
    if (this->sendQueue.size() == 0) {
        return;
    }
    emit this->responseTimeout();

    qCritical() << "ApismTcpClient::onResponseTimeoutTimerTimeout() --> skip this message, send next command, if available.";

    // Try next command
    this->sendQueue.removeFirst();
    if (this->sendQueue.size() > 0) this->private_sendData();
}
