#ifndef SCSTWCLIENT_H
#define SCSTWCLIENT_H

#include <QObject>
#include <QTcpSocket>
#include <QDataStream>
#include <QDateTime>
#include <QTimer>
#include <QEventLoop>
#include <QSemaphore>
#include <QThread>
#include <QFuture>
#include <QtConcurrent/QtConcurrent>
#include <string.h>
#include <QByteArray>

#include "ScStw.hpp"

/*!
 * This class is used to connect and talk to the ScStw basestation.
 *
 * \code{.cpp}
 *  ScStwClient * client = new ScStwClient();
 *  client->setIp("192.168.4.1");
 *  client->connectToHost();
 * \endcode
 *
 * \brief The ScStwClient class
 * \author Dorian Zedler
 */
class ScStwClient : public QObject
{
    Q_OBJECT
    Q_PROPERTY(State state READ getState NOTIFY stateChanged)
    Q_PROPERTY(QVariantList extensions READ getExtensions NOTIFY extensionsChanged)
    Q_PROPERTY(QString ipAddress READ getIP WRITE setIP)

public:
    /*!
     * The constructor
     *
     * \brief ScStwClient
     */
    explicit ScStwClient(QObject *parent = nullptr);

    enum State {DISCONNECTED, CONNECTING, INITIALISING, CONNECTED};
    Q_ENUM(State);

    const QString API_VERSION = "1.0.0";

private:
    // values for the socket connection
    QString ip;
    ushort port = 3563;
    int errors;
    const static int ERRORS_UNTIL_DISCONNECT = 4;

    QVariantList extensions;

    //---general status values---//

    // some meta data of the base station
    QString firmwareVersion;
    QString apiVersion;
    double timeOffset;

    // the current state
    ScStwClient::State state;

    QDateTime *date;
    //to get the current time

    QTcpSocket *socket;
    //socket for communication with the extention

    QTimer *timeoutTimer;

    QString readBuffer;

    int nextConnectionId;

    struct waitingRequest {
        int id;
        QEventLoop * loop;
        QJsonObject reply;
    };

    QList<waitingRequest> waitingRequests;

public slots:

    /*!
     * \brief Function to connect to the base station
     */
    void connectToHost();

    /*!
     * \brief Function to disconnect from the basestation
     */
    void closeConnection();


    /*! socket communication handling */

    /*!
     * \brief Funtion to send a command to the server (for internal use)
     * \param header             the command to send
     * \param data               the data to send
     * \param timeout            the timeout
     * \param useTerminationKeys wether to use the termination keys defined in
     *
     * \return a variant map containing the Keys "status" and "data"
     */
    QVariantMap sendCommand(int header, QJsonValue data = "", int timeout = 3000);

    /*! updater functions */

    /*!
     * \brief Function to check wether the firmware of the base station is up-to-date
     * \see getFirmwareVersion()
     * \see updateFirmware()
     * \return true or false
     */
    bool isFirmwareUpToDate();

    /*!
     * \brief Function to write a setting on the base station
     * \param key the key to write to
     * \param value the value to write to
     * \return the status code returned by the command
     */
    ScStw::StatusCode writeRemoteSetting(ScStw::BaseStationSetting key, QString value);

    /*!
     * \brief Function to read a setting on the base station
     * \param key the key to read from
     * \return the value of the key or "false" if the key is not found or an error occured
     */
    QString readRemoteSetting(ScStw::BaseStationSetting key);

    /*! Getter fuctions */

    /*!
     * \brief Function to get the ip the client will try to connect to.
     * \see setIP()
     * \return the ip
     */
    QString getIP();

    /*!
     * \brief Function to get the current state of the client
     * \return the current state
     */
    ScStwClient::State getState();

    /*!
     * \brief Function to get the extensions and their state from the base station
     * \return a list with all configured extensions and their state
     */
    QVariantList getExtensions();

    /*!
     * \brief Function to get the time offset of the base station relative to the clients time
     * \see updateTime()
     * \return the time offset in milliseconds
     */
    int getTimeOffset();

    /*!
     * \brief Function to get the current firmware version of the base station
     * \see updateFirmware()
     * \see isFirmwareUpToDate()
     * \return Firmware version as string encoded as <major>.<minor>.<patch>
     */
    QString getFirmwareVersion();

    /*!
     * \brief Function to get the current API version of the base station
     * \return
     */
    QString getApiVersion();

    /*! setter functions */

    /*!
     * \brief Function to set the ip to connect to
     * \see getIP()
     * \param ipAdress
     */
    void setIP(QString ipAdress);

private slots:
    /*!
     * \brief called when timeout timer times out
     */
    void connectionTimeout();

    /*!
     * \brief Function that is connected to the QAbstractSocket::error slot and used to handle upcoming errors
     * \param err the error that occurred
     */
    void handleError(QAbstractSocket::SocketError err);

    /*!
     * \brief Function to init a session at the base station
     * \return true or false
     */
    bool init();

    /*!
     * \brief Function to end a session on the base station
     */
    void deInit();

    /*!
     * \brief Funtion to send a command to the server (for internal use)
     * \param header             the command to send
     * \param data               the data to send
     * \param timeout            the timeout
     * \param useTerminationKeys wether to use the termination keys defined in
     *                           ScStw::SOCKET_MESSAGE_START_KEY and ScStw::SOCKET_MESSAGE_END_KEY
     * \return a variant map containing the Keys "status" and "data"
     */
    QVariantMap sendCommand(int header, QJsonValue data, int timeout, bool useTerminationKeys);

    /*!
     * \brief Function connected to the QAbstractSocket::readyRead signal
     */
    void handleReadyRead();

    /*!
     * \brief Function to process an incoming string and parse the messages contained in it.
     * \param message the message sting to parse
     */
    void processSocketMessage(QString message);

    /*!
     * \brief Function that handles a parsed message
     * \details This fuction looks up the id of the incoming message and tries to find the according waiting request.
     *          If it is unable to find an accordin request, the signal ScStwClient::gotUnexpectedMessage(QString message) is called.
     *          If the recieved message is a signal, the ScStwClient::handleSignal() function is called.
     * \see gotUnexpectedMessage()
     * \see handleSignal()
     * \param reply the massage that needs to be handles
     */
    void handleSocketMessage(QString reply);

    /*!
     * \brief Function to handle a change of the state of the tcp socket
     * \details it is connected to the QAbstractSocket::stateChanged signal
     * \see stateChanged()
     * \param socketState
     */
    void handleSocketStateChange(QAbstractSocket::SocketState socketState);

    /*!
     * \brief Function to handle a signal from the base station.
     * \details Called by the ScStwClient::handleSocketMessage function
     * \see handleSocketMessage()
     * \param data
     */
    void handleSignal(QVariantMap data);

    /*! Helper Functions */

    /*!
     * \brief Function used to set the local cache of the baseStation connections.
     * \details emits ScStwClient::gotSignal() with a ScStw::ExtensionsChanged signal.
     * \see gotSignal()
     * \param connections the list to set the chache to
     */
    void setExtensions(QVariantList extensions);

    /*!
     * \brief Function to set the local state.
     * \details emits ScStwClient::stateChanged() when the new state does not match the old one.
     * \see stateChanged()
     * \param newState the state to change to
     */
    void setState(ScStwClient::State newState);

signals:
    /*!
     * \brief Is emitted, when the connection state changes
     */
    void stateChanged();

    /*!
     * \brief Is emitted, whenever a reply is recieved which does not match any requests
     *
     * \param reply contains the reply
     */
    void gotUnexpectedMessage(QString message);

    /*!
     * \brief Is emitted, when an update signal from the basestation is recieved
     *
     * \param data
     */
    void gotSignal(ScStw::SignalKey key, QVariant data);

    /*!
     * \brief Is emitted, when there is any network error
     * \param error
     */
    void gotError(QAbstractSocket::SocketError error);

    /*!
     * \brief Is emitted, when the extensions of the base station changed
     */
    void extensionsChanged();
};

extern ScStwClient * pGlobalScStwClient;

#endif // SCSTWCLIENT_H
