/*
 * SocketIOclient.cpp
 *
 *  Created on: May 12, 2018
 *      Author: links
 */

#include "WebSockets.h"
#include "WebSocketsClient.h"
#include "SocketIOclient.h"

SocketIOclient::SocketIOclient() {
}

SocketIOclient::~SocketIOclient() {
}

void SocketIOclient::begin(const char * host, uint16_t port, const char * url, const char * protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {
    WebSocketsClient::beginSocketIO(host, port, url, protocol);
    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);
    initClient();
}

void SocketIOclient::begin(String host, uint16_t port, String url, String protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {
    WebSocketsClient::beginSocketIO(host, port, url, protocol);
    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);
    initClient();
}
#if defined(HAS_SSL)
void SocketIOclient::beginSSL(const char * host, uint16_t port, const char * url, const char * protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {
    WebSocketsClient::beginSocketIOSSL(host, port, url, protocol);
    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);
    initClient();
}

void SocketIOclient::beginSSL(String host, uint16_t port, String url, String protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {
    WebSocketsClient::beginSocketIOSSL(host, port, url, protocol);
    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);
    initClient();
}
#if defined(SSL_BARESSL)
void SocketIOclient::beginSSLWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {
    WebSocketsClient::beginSocketIOSSLWithCA(host, port, url, CA_cert, protocol);
    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);
    initClient();
}

void SocketIOclient::beginSSLWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {
    WebSocketsClient::beginSocketIOSSLWithCA(host, port, url, CA_cert, protocol);
    WebSocketsClient::enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);
    initClient();
}

void SocketIOclient::setSSLClientCertKey(const char * clientCert, const char * clientPrivateKey) {
    WebSocketsClient::setSSLClientCertKey(clientCert, clientPrivateKey);
}

void SocketIOclient::setSSLClientCertKey(BearSSL::X509List * clientCert, BearSSL::PrivateKey * clientPrivateKey) {
    WebSocketsClient::setSSLClientCertKey(clientCert, clientPrivateKey);
}

#endif
#endif

void SocketIOclient::configureEIOping(bool disableHeartbeat) {
    _disableHeartbeat = disableHeartbeat;
}

void SocketIOclient::initClient(void) {
    if(_client.cUrl.indexOf("EIO=4") != -1) {
        DEBUG_WEBSOCKETS("[wsIOc] found EIO=4 disable EIO ping on client\n");
        configureEIOping(true);
    }
}

/**
 * set callback function
 * @param cbEvent SocketIOclientEvent
 */
void SocketIOclient::onEvent(SocketIOclientEvent cbEvent) {
    _cbEvent = cbEvent;
}

bool SocketIOclient::isConnected(void) {
    return WebSocketsClient::isConnected();
}

void SocketIOclient::setExtraHeaders(const char * extraHeaders) {
    return WebSocketsClient::setExtraHeaders(extraHeaders);
}

void SocketIOclient::setReconnectInterval(unsigned long time) {
    return WebSocketsClient::setReconnectInterval(time);
}

void SocketIOclient::disconnect(void) {
    WebSocketsClient::disconnect();
}

/**
 * send text data to client
 * @param num uint8_t client id
 * @param type socketIOmessageType_t
 * @param payload uint8_t *
 * @param length size_t
 * @param headerToPayload bool (see sendFrame for more details)
 * @return true if ok
 */
bool SocketIOclient::send(socketIOmessageType_t type, uint8_t * payload, size_t length, bool headerToPayload) {
    bool ret = false;
    if(length == 0) {
        length = strlen((const char *)payload);
    }
    if(clientIsConnected(&_client) && _client.status == WSC_CONNECTED) {
        if(!headerToPayload) {
            // webSocket Header
            ret = WebSocketsClient::sendFrameHeader(&_client, WSop_text, length + 2, true);
            // Engine.IO / Socket.IO Header
            if(ret) {
                uint8_t buf[3] = { eIOtype_MESSAGE, type, 0x00 };
                ret            = WebSocketsClient::write(&_client, buf, 2);
            }
            if(ret && payload && length > 0) {
                ret = WebSocketsClient::write(&_client, payload, length);
            }
            return ret;
        } else {
            // TODO implement
        }
    }
    return false;
}

bool SocketIOclient::send(socketIOmessageType_t type, const uint8_t * payload, size_t length) {
    return send(type, (uint8_t *)payload, length);
}

bool SocketIOclient::send(socketIOmessageType_t type, char * payload, size_t length, bool headerToPayload) {
    return send(type, (uint8_t *)payload, length, headerToPayload);
}

bool SocketIOclient::send(socketIOmessageType_t type, const char * payload, size_t length) {
    return send(type, (uint8_t *)payload, length);
}

bool SocketIOclient::send(socketIOmessageType_t type, String & payload) {
    return send(type, (uint8_t *)payload.c_str(), payload.length());
}

/**
 * send text data to client
 * @param num uint8_t client id
 * @param payload uint8_t *
 * @param length size_t
 * @param headerToPayload bool  (see sendFrame for more details)
 * @return true if ok
 */
bool SocketIOclient::sendEVENT(uint8_t * payload, size_t length, bool headerToPayload) {
    return send(sIOtype_EVENT, payload, length, headerToPayload);
}

bool SocketIOclient::sendEVENT(const uint8_t * payload, size_t length) {
    return sendEVENT((uint8_t *)payload, length);
}

bool SocketIOclient::sendEVENT(char * payload, size_t length, bool headerToPayload) {
    return sendEVENT((uint8_t *)payload, length, headerToPayload);
}

bool SocketIOclient::sendEVENT(const char * payload, size_t length) {
    return sendEVENT((uint8_t *)payload, length);
}

bool SocketIOclient::sendEVENT(String & payload) {
    return sendEVENT((uint8_t *)payload.c_str(), payload.length());
}

void SocketIOclient::loop(void) {
    WebSocketsClient::loop();
    unsigned long t = millis();
    if(!_disableHeartbeat && (t - _lastHeartbeat) > EIO_HEARTBEAT_INTERVAL) {
        _lastHeartbeat = t;
        DEBUG_WEBSOCKETS("[wsIOc] send ping\n");
        WebSocketsClient::sendTXT(eIOtype_PING);
    }
}

void SocketIOclient::handleCbEvent(WStype_t type, uint8_t * payload, size_t length) {
    switch(type) {
        case WStype_DISCONNECTED:
            if(payload && length > 0) {
                runIOCbEvent(sIOtype_DISCONNECT, payload, length);
                DEBUG_WEBSOCKETS("[wsIOc] Disconnected! reason: %s\n", payload);
            } else {
                runIOCbEvent(sIOtype_DISCONNECT, NULL, 0);
                DEBUG_WEBSOCKETS("[wsIOc] Disconnected!\n");
            }
            break;
        case WStype_CONNECTED: {
            DEBUG_WEBSOCKETS("[wsIOc] Connected to url: %s\n", payload);
            // send message to server when Connected
            // Engine.io upgrade confirmation message (required)
            WebSocketsClient::sendTXT("2probe");
            WebSocketsClient::sendTXT(eIOtype_UPGRADE);
            runIOCbEvent(sIOtype_CONNECT, payload, length);
        } break;
        case WStype_TEXT: {
            if(length < 1) {
                break;
            }

            engineIOmessageType_t eType = (engineIOmessageType_t)payload[0];
            switch(eType) {
                case eIOtype_PING:
                    payload[0] = eIOtype_PONG;
                    DEBUG_WEBSOCKETS("[wsIOc] get ping send pong (%s)\n", payload);
                    WebSocketsClient::sendTXT(payload, length, false);
                    break;
                case eIOtype_PONG:
                    DEBUG_WEBSOCKETS("[wsIOc] get pong\n");
                    break;
                case eIOtype_MESSAGE: {
                    if(length < 2) {
                        break;
                    }
                    socketIOmessageType_t ioType = (socketIOmessageType_t)payload[1];
                    uint8_t * data               = &payload[2];
                    size_t lData                 = length - 2;
                    switch(ioType) {
                        case sIOtype_EVENT:
                            DEBUG_WEBSOCKETS("[wsIOc] get event (%d): %s\n", lData, data);
                            break;
                        case sIOtype_CONNECT:
                            DEBUG_WEBSOCKETS("[wsIOc] connected (%d): %s\n", lData, data);
                            return;
                        case sIOtype_DISCONNECT:
                        case sIOtype_ACK:
                        case sIOtype_ERROR:
                        case sIOtype_BINARY_EVENT:
                        case sIOtype_BINARY_ACK:
                        default:
                            DEBUG_WEBSOCKETS("[wsIOc] Socket.IO Message Type %c (%02X) is not implemented\n", ioType, ioType);
                            DEBUG_WEBSOCKETS("[wsIOc] get text: %s\n", payload);
                            break;
                    }

                    runIOCbEvent(ioType, data, lData);
                } break;
                case eIOtype_OPEN:
                case eIOtype_CLOSE:
                case eIOtype_UPGRADE:
                case eIOtype_NOOP:
                default:
                    DEBUG_WEBSOCKETS("[wsIOc] Engine.IO Message Type %c (%02X) is not implemented\n", eType, eType);
                    DEBUG_WEBSOCKETS("[wsIOc] get text: %s\n", payload);
                    break;
            }
        } break;
        case WStype_ERROR:
        case WStype_BIN:
        case WStype_FRAGMENT_TEXT_START:
        case WStype_FRAGMENT_BIN_START:
        case WStype_FRAGMENT:
        case WStype_FRAGMENT_FIN:
        case WStype_PING:
        case WStype_PONG:
            break;
    }
}