#ifndef BLINKER_PROTOCOL_H
#define BLINKER_PROTOCOL_H

#include "BlinkerConfig.h"
#include "BlinkerDebug.h"
#include "BlinkerStream.h"
#include "BlinkerUtility.h"

enum _blinker_state_t
{
    CONNECTING,
    CONNECTED,
    DISCONNECTED
};

class BlinkerProtocol
{
    public :
        BlinkerProtocol()
            : state(CONNECTING)
            , isInit(false)
            , isFresh(false)
            , isAvail(false)
            , availState(false)
            , canParse(false)
        {}

        void transport(BlinkerStream & bStream) { conn = &bStream; isInit = true; }

    // #if defined(BLINKER_LOWPOWER_AIR202)
    //     void print(const String & data);
    //     void print(const String & key, const String & data);
    //     char * deviceName() { if (isInit) return conn->deviceName(); else return ""; }
    //     char * authKey()    { if (isInit) return conn->authKey(); else return "";  }
    //     int init()          { return isInit ? conn->init() : false; }
    //     void begin(const char* _key, const char* _type, String _imei)
    //     { conn->begin(_key, _type, _imei); }
    //     int deviceRegister(){ return conn->deviceRegister(); }
    // #else
        int connect()       { return isInit ? conn->connect() : false; }
        int connected()
        {
            if (isInit)
            {
                // if (conn->connected())
                // {
                //     state == CONNECTED;
                //     return true;
                // }
                // else
                // {
                //     state = DISCONNECTED;
                //     return false;
                // }
                return conn->connected();
            }
            else
            {
                state = DISCONNECTED;
                return false;
            }
        }
        void disconnect()   { if (isInit) { conn->disconnect(); state = DISCONNECTED; } }
        // bool available()    { if (availState) {availState = false; return true;} else return false; }
        void flush();
        void checkState(bool state = true)      { isCheck = state; }
        void print(const String & data);
        void print(const String & key, const String & data);

        #if defined(BLINKER_MQTT) || defined(BLINKER_PRO) || \
            defined(BLINKER_AT_MQTT) || defined(BLINKER_MQTT_AT) || \
            defined(BLINKER_WIFI_GATEWAY) || defined(BLINKER_NBIOT_SIM7020) || \
            defined(BLINKER_GPRS_AIR202) || defined(BLINKER_PRO_SIM7020) || \
            defined(BLINKER_PRO_AIR202) || defined(BLINKER_MQTT_AUTO) || \
            defined(BLINKER_PRO_ESP) || defined(BLINKER_WIFI_SUBDEVICE) || \
            defined(BLINKER_QRCODE_NBIOT_SIM7020) || defined(BLINKER_NBIOT_SIM7000) || \
            defined(BLINKER_QRCODE_NBIOT_SIM7000) || defined(BLINKE_HTTP)
            int aliPrint(const String & data)   { return isInit ? conn->aliPrint(data) : false; }
            int duerPrint(const String & data, bool report = false)  { return isInit ? conn->duerPrint(data, report) : false; }
            #if !defined(BLINKER_GPRS_AIR202) && !defined(BLINKER_NBIOT_SIM7020) && \
                !defined(BLINKER_PRO_SIM7020) && !defined(BLINKER_PRO_AIR202) && \
                !defined(BLINKER_QRCODE_NBIOT_SIM7020) && !defined(BLINKER_NBIOT_SIM7000) && \
                !defined(BLINKER_QRCODE_NBIOT_SIM7000)
            int miPrint(const String & data)  { return isInit ? conn->miPrint(data) : false; }
            #endif
            // void ping() { if (isInit) conn->ping(); }
            #if !defined(BLINKER_MQTT_AT)
            int bPrint(char * name, const String & data) { return isInit ? conn->bPrint(name, data) : false; }
            int autoPrint(unsigned long id)  { return isInit ? conn->autoPrint(id) : false; }
            void sharers(const String & data) { if (isInit) conn->sharers(data); }
            int needFreshShare() { if (isInit) return conn->needFreshShare(); else return false; }
            #endif
        #endif

        #if defined(BLINKER_MQTT) || defined(BLINKER_PRO) || \
            defined(BLINKER_AT_MQTT) || defined(BLINKER_WIFI_GATEWAY) || \
            defined(BLINKER_GPRS_AIR202) || defined(BLINKER_NBIOT_SIM7020) || \
            defined(BLINKER_PRO_SIM7020) || defined(BLINKER_PRO_AIR202) || \
            defined(BLINKER_MQTT_AUTO) || defined(BLINKER_PRO_ESP) || \
            defined(BLINKER_WIFI_SUBDEVICE) || defined(BLINKER_QRCODE_NBIOT_SIM7020) || \
            defined(BLINKER_NBIOT_SIM7000) || defined(BLINKER_QRCODE_NBIOT_SIM7000) || \
            defined(BLINKE_HTTP)
            int toServer(char * data) { return isInit ? conn->toServer(data) : false; }
            char * deviceName() { if (isInit) return conn->deviceName(); else return ""; }
            char * authKey()    { if (isInit) return conn->authKey(); else return "";  }
            char * token()    { if (isInit) return conn->token(); else return "";  }
            int init()          { return isInit ? conn->init() : false; }
            int mConnected()    { return isInit ? conn->mConnected() : false; }
            void freshAlive() { if (isInit) conn->freshAlive(); }
        #endif

        #if defined(BLINKER_LOWPOWER_AIR202)
            char * deviceName() { if (isInit) return conn->deviceName(); else return ""; }
            char * authKey()    { if (isInit) return conn->authKey(); else return "";  }
            char * token()    { if (isInit) return conn->token(); else return "";  }
            int init()          { return isInit ? conn->init() : false; }
            void begin(const char* _key, const char* _type, String _imei) { conn->begin(_key, _type, _imei); }
            int deviceRegister(){ return conn->deviceRegister(); }
        #endif

        #if defined(BLINKER_PRO) || defined(BLINKER_MQTT_AUTO) || \
            defined(BLINKER_PRO_ESP) || defined(BLINKER_WIFI_GATEWAY) || \
            defined(BLINKER_WIFI_SUBDEVICE)
            int deviceRegister(){ return conn->deviceRegister(); }
            int authCheck()     { return conn->authCheck(); }
            #if defined(BLINKER_PRO)
            void begin(const char* _deviceType) { conn->begin(_deviceType); }
            #elif defined(BLINKER_MQTT_AUTO) || defined(BLINKER_PRO_ESP) || \
                defined(BLINKER_WIFI_GATEWAY) || defined(BLINKER_WIFI_SUBDEVICE)
            void begin(const char* _key, const char* _type) { conn->begin(_key, _type); }
            #endif
        #elif defined(BLINKER_GPRS_AIR202) || defined(BLINKER_NBIOT_SIM7020) || \
            defined(BLINKER_PRO_SIM7020) || defined(BLINKER_PRO_AIR202) || \
            defined(BLINKER_QRCODE_NBIOT_SIM7020) || defined(BLINKER_NBIOT_SIM7000) || \
            defined(BLINKER_QRCODE_NBIOT_SIM7000)
            int deviceRegister(){ return conn->deviceRegister(); }

            #if defined(BLINKER_QRCODE_NBIOT_SIM7020) || defined(BLINKER_QRCODE_NBIOT_SIM7000)
                void begin(const char* _authKey, const char* _deviceType, String _imei)
                { conn->begin(_authKey, _deviceType, _imei); }
            #else
                void begin(const char* _deviceType, String _imei)
                { conn->begin(_deviceType, _imei); }
            #endif

            #if defined(BLINKER_PRO_SIM7020) || defined(BLINKER_PRO_AIR202)
                int authCheck()     { return conn->authCheck(); }
            #endif
        #endif

        // #if defined(BLINKER_WIFI_SUBDEVICE)
        //     void attachSubAvailable(blinker_callback_return_int_t func)
        //     { if (isInit) conn->attachAvailable(func); }
            
        //     void attachSubRead(blinker_callback_return_string_t func)
        //     { if (isInit) conn->attachRead(func); }

        //     void attachSubPrint(blinker_callback_with_string_arg_t func)
        //     { if (isInit) conn->attachPrint(func); }

        //     void attachSubBegin(blinker_callback_t func)
        //     { if (isInit) conn->attachBegin(func); }

        //     void attachSubConnect(blinker_callback_return_int_t func)
        //     { if (isInit) conn->attachConnect(func); }

        //     void attachSubConnected(blinker_callback_return_int_t func)
        //     { if (isInit) conn->attachConnected(func); }

        //     void attachSubDisconnect(blinker_callback_t func)
        //     { if (isInit) conn->attachDisconnect(func); }
        // #endif

        #if defined(BLINKER_WIFI_GATEWAY) || defined(BLINKER_WIFI_SUBDEVICE)
            void meshCheck() { conn->meshCheck(); }
            #if !defined(BLINKER_WIFI_SUBDEVICE)
                void setTimezone(float tz) { conn->setTimezone(tz); }
            #endif
            #if defined(BLINKER_WIFI_SUBDEVICE)
                int subPrint(const String & data) { return conn->subPrint(data); }
                int meshAvail() { return conn->meshAvail(); }
                char * meshLastRead() { return conn->meshLastRead(); }
                void meshFlush() { conn->meshFlush(); }
            #endif
        #endif

        #if defined(BLINKER_PRO_ESP) || defined(BLINKER_WIFI_GATEWAY)
            void smartConfigType() { conn->setSmartConfig(); }
            void apConfigType() { conn->setApConfig(); }
            bool checkIsSmartConfig() { return conn->checkSmartConfig(); }
        #endif

        #if defined(BLINKER_HTTP)
            void subscribe() { conn->subscribe(); }
        #endif
    // #endif
    private :

    protected :
        BlinkerStream       *conn;
        _blinker_state_t    state;
        bool                isInit;
        bool                isFresh;
        bool                isAvail;
        bool                availState;
        bool                canParse;
        bool                autoFormat = false;
        bool                isCheck = true;
        uint32_t            autoFormatFreshTime;
        char*               _sendBuf;
        blinker_callback_with_string_arg_t  _availableFunc = NULL;

    // #if defined(BLINKER_LOWPOWER_AIR202)
    //     void checkFormat();
    //     void autoFormatData(const String & key, const String & jsonValue);
    // #else
        int checkAvail();
        #if defined(BLINKER_MQTT) || defined(BLINKER_PRO) || \
            defined(BLINKER_AT_MQTT) || defined(BLINKER_WIFI_GATEWAY) || \
            defined(BLINKER_PRO_SIM7020) || defined(BLINKER_PRO_AIR202) || \
            defined(BLINKER_MQTT_AUTO) || defined(BLINKER_PRO_ESP) || \
            defined(BLINKER_WIFI_SUBDEVICE) || defined(BLINKE_HTTP)
            bool checkAliAvail()    { return conn->aligenieAvail(); }
            bool checkDuerAvail()   { return conn->duerAvail(); }
            #if !defined(BLINKER_GPRS_AIR202) && !defined(BLINKER_NBIOT_SIM7020) && \
                !defined(BLINKER_PRO_SIM7020) && !defined(BLINKER_PRO_AIR202) && \
                !defined(BLINKER_QRCODE_NBIOT_SIM7020) && !defined(BLINKER_NBIOT_SIM7000) && \
                !defined(BLINKER_QRCODE_NBIOT_SIM7000)
            bool checkMIOTAvail()   { return conn->miAvail(); }
            #endif
        #endif
        
        #if defined(BLINKER_AT_MQTT)
            void begin(const char* auth) { return conn->begin(auth); }
            int serialAvailable()   { return conn->serialAvailable(); }
            int serialPrint(const String & s1, const String & s2, bool needCheck = true)
            { return conn->serialPrint(s1, s2, needCheck); }
            int serialPrint(const String & s, bool needCheck = true)
            { return conn->serialPrint(s, needCheck); }
            int mqttPrint(const String & data)
            { return conn->mqttPrint(data); }
            char * serialLastRead() { return conn->serialLastRead(); }
            void aligenieType(blinker_at_aligenie_t _type)
            { conn->aligenieType(_type); }
            void duerType(blinker_at_dueros_t _type)
            { conn->duerType(_type); }
            char * deviceId()   { return conn->deviceId(); }
            char * uuid()       { return conn->uuid(); }
            void softAPinit()   { conn->softAPinit(); }
            void smartconfig()  { conn->smartconfig(); }
            int autoInit()      { return conn->autoInit(); }
            void connectWiFi(String _ssid, String _pswd) { return conn->connectWiFi(_ssid, _pswd); }
            void connectWiFi(const char* _ssid, const char* _pswd) { return conn->connectWiFi(_ssid, _pswd); }
        #endif
        void checkFormat();
        void checkAutoFormat();
        char* dataParse()       { if (canParse) return conn->lastRead(); else return ""; }
        char* lastRead()        { return conn->lastRead(); }
        void isParsed()         { BLINKER_LOG_ALL(BLINKER_F("isParsed")); flush(); }
        int parseState()        { return canParse; }
        int printNow();
        void _timerPrint(const String & n);
        int _print(char * n, bool needCheckLength = true);

        void autoFormatData(const String & key, const String & jsonValue);
    // #endif
};

// #if !defined(BLINKER_LOWPOWER_AIR202)
void BlinkerProtocol::flush()
{
    if (isInit && isAvail) conn->flush();
    isAvail = false;
    isFresh = false;
    availState = false; 
    canParse = false;
}

int BlinkerProtocol::checkAvail()
{
    if (!isInit) return false;

    flush();

    if (connected())
    {
        isAvail = conn->available();

        if (isAvail)
        { 
            BLINKER_LOG_ALL(BLINKER_F("checkAvail: "), isAvail);

            isFresh = true;
            canParse = true;
            availState = true;
        }
    }

    return isAvail;
}

void BlinkerProtocol::checkAutoFormat()
{
    if (autoFormat)
    {
        if ((millis() - autoFormatFreshTime) >= BLINKER_MSG_AUTOFORMAT_TIMEOUT)
        {
            if (strlen(_sendBuf))
            {
                // #if !defined(BLINKER_LOWPOWER_AIR202)
                #if defined(BLINKER_ARDUINOJSON)
                    _print(_sendBuf);
                #else
                    strcat(_sendBuf, "}");
                    _print(_sendBuf);
                #endif
                // #endif
            }
            free(_sendBuf);
            autoFormat = false;
            BLINKER_LOG_FreeHeap_ALL();
        }
    }
}

int BlinkerProtocol::printNow()
{
    if (strlen(_sendBuf) && autoFormat)
    {
        int8_t print_state = BLINKER_ERROR;
        #if defined(BLINKER_ARDUINOJSON)
            if (_print(_sendBuf)) print_state = BLINKER_SUCCESS;
        #else
            strcat(_sendBuf, "}");
            if (_print(_sendBuf)) print_state = BLINKER_SUCCESS;
        #endif

        free(_sendBuf);
        autoFormat = false;
        BLINKER_LOG_FreeHeap_ALL();

        return print_state;
    }

    return BLINKER_ERROR;
}

void BlinkerProtocol::_timerPrint(const String & n)
{
    BLINKER_LOG_ALL(BLINKER_F("print: "), n);
    
    if (n.length() <= BLINKER_MAX_SEND_SIZE)
    {
        checkFormat();
        checkState(false);
        strcpy(_sendBuf, n.c_str());
    }
    else
    {
        BLINKER_ERR_LOG(BLINKER_F("SEND DATA BYTES MAX THAN LIMIT!"));
    }
}

int BlinkerProtocol::_print(char * n, bool needCheckLength)
{
    BLINKER_LOG_ALL(BLINKER_F("print: "), n);
    
    if (strlen(n) <= BLINKER_MAX_SEND_SIZE || \
        !needCheckLength)
    {
        // BLINKER_LOG_FreeHeap_ALL();
        BLINKER_LOG_ALL(BLINKER_F("Proto print..."));
        BLINKER_LOG_FreeHeap_ALL();
        conn->print(n, isCheck);
        if (!isCheck) isCheck = true;

        return true;
    }
    else {
        BLINKER_ERR_LOG(BLINKER_F("SEND DATA BYTES MAX THAN LIMIT!"));

        return false;
    }
}

void BlinkerProtocol::print(const String & data)
{
    #if !defined(BLINKER_LOWPOWER_AIR202)
    checkFormat();
    strcpy(_sendBuf, data.c_str());
    _print(_sendBuf);
    free(_sendBuf);
    autoFormat = false;
    BLINKER_LOG_FreeHeap_ALL();
    #endif
}

void BlinkerProtocol::print(const String & key, const String & data)
{
    checkFormat();
    autoFormatData(key, data);
    if ((millis() - autoFormatFreshTime) >= BLINKER_MSG_AUTOFORMAT_TIMEOUT)
    {
        autoFormatFreshTime = millis();
    }
}

void BlinkerProtocol::checkFormat()
{
    if (!autoFormat)
    {
        autoFormat = true;
        _sendBuf = (char*)malloc(BLINKER_MAX_SEND_SIZE*sizeof(char));
        memset(_sendBuf, '\0', BLINKER_MAX_SEND_SIZE);
    }
}

void BlinkerProtocol::autoFormatData(const String & key, const String & jsonValue)
{
    #if defined(BLINKER_ARDUINOJSON)
        BLINKER_LOG_ALL(BLINKER_F("autoFormatData key: "), key, \
                        BLINKER_F(", json: "), jsonValue);
        
        String _data;

        if (STRING_contains_string(STRING_format(_sendBuf), key))
        {

            // DynamicJsonBuffer jsonSendBuffer;
            DynamicJsonDocument jsonBuffer(1024);

            if (strlen(_sendBuf)) {
                BLINKER_LOG_ALL(BLINKER_F("add"));

                // JsonObject& root = jsonSendBuffer.parseObject(STRING_format(_sendBuf));
                deserializeJson(jsonBuffer, STRING_format(_sendBuf));
                JsonObject root = jsonBuffer.as<JsonObject>();

                if (root.containsKey(key)) {
                    root.remove(key);
                }
                // root.printTo(_data);
                serializeJson(root, _data);

                _data = _data.substring(0, _data.length() - 1);

                if (_data.length() > 4 ) _data += BLINKER_F(",");
                _data += jsonValue;
                _data += BLINKER_F("}");
            }
            else {
                BLINKER_LOG_ALL(BLINKER_F("new"));
                
                _data = BLINKER_F("{");
                _data += jsonValue;
                _data += BLINKER_F("}");
            }
        }
        else {
            _data = STRING_format(_sendBuf);

            if (_data.length())
            {
                BLINKER_LOG_ALL(BLINKER_F("add."));

                _data = _data.substring(0, _data.length() - 1);

                _data += BLINKER_F(",");
                _data += jsonValue;
                _data += BLINKER_F("}");
            }
            else {
                BLINKER_LOG_ALL(BLINKER_F("new."));
                
                _data = BLINKER_F("{");
                _data += jsonValue;
                _data += BLINKER_F("}");
            }
        }

        if (_data.length() > BLINKER_MAX_SEND_BUFFER_SIZE)
        {
            BLINKER_ERR_LOG(BLINKER_F("FORMAT DATA SIZE IS MAX THAN LIMIT: "), BLINKER_MAX_SEND_BUFFER_SIZE);
            return;
        }

        strcpy(_sendBuf, _data.c_str());
    #else
        String data;

        BLINKER_LOG_ALL(BLINKER_F("autoFormatData data: "), jsonValue);
        BLINKER_LOG_ALL(BLINKER_F("strlen(_sendBuf): "), strlen(_sendBuf));
        BLINKER_LOG_ALL(BLINKER_F("data.length(): "), jsonValue.length());

        if ((strlen(_sendBuf) + jsonValue.length()) >= BLINKER_MAX_SEND_BUFFER_SIZE)
        {
            BLINKER_ERR_LOG(BLINKER_F("FORMAT DATA SIZE IS MAX THAN LIMIT"));
            return;
        }

        if (strlen(_sendBuf) > 0) {
            data = "," + jsonValue;
            strcat(_sendBuf, data.c_str());
        }
        else {
            data = "{" + jsonValue;
            strcpy(_sendBuf, data.c_str());
        }
    #endif
}

// #elif defined(BLINKER_LOWPOWER_AIR202)

// void BlinkerProtocol::print(const String & data)
// {
//     #if !defined(BLINKER_LOWPOWER_AIR202)
//     checkFormat();
//     strcpy(_sendBuf, data.c_str());
//     _print(_sendBuf);
//     free(_sendBuf);
//     autoFormat = false;
//     BLINKER_LOG_FreeHeap_ALL();
//     #endif
// }

// void BlinkerProtocol::print(const String & key, const String & data)
// {
//     checkFormat();
//     autoFormatData(key, data);
//     if ((millis() - autoFormatFreshTime) >= BLINKER_MSG_AUTOFORMAT_TIMEOUT)
//     {
//         autoFormatFreshTime = millis();
//     }
// }

// void BlinkerProtocol::checkFormat()
// {
//     if (!autoFormat)
//     {
//         autoFormat = true;
//         _sendBuf = (char*)malloc(BLINKER_MAX_SEND_SIZE*sizeof(char));
//         memset(_sendBuf, '\0', BLINKER_MAX_SEND_SIZE);
//     }
// }

// void BlinkerProtocol::autoFormatData(const String & key, const String & jsonValue)
// {
//     #if defined(BLINKER_ARDUINOJSON)
//         BLINKER_LOG_ALL(BLINKER_F("autoFormatData key: "), key, 
//                         BLINKER_F(", json: "), jsonValue);
        
//         String _data;

//         if (STRING_contains_string(STRING_format(_sendBuf), key))
//         {

//             DynamicJsonBuffer jsonSendBuffer;                

//             if (strlen(_sendBuf)) {
//                 BLINKER_LOG_ALL(BLINKER_F("add"));

//                 JsonObject& root = jsonSendBuffer.parseObject(STRING_format(_sendBuf));

//                 if (root.containsKey(key)) {
//                     root.remove(key);
//                 }
//                 root.printTo(_data);

//                 _data = _data.substring(0, _data.length() - 1);

//                 if (_data.length() > 4 ) _data += BLINKER_F(",");
//                 _data += jsonValue;
//                 _data += BLINKER_F("}");
//             }
//             else {
//                 BLINKER_LOG_ALL(BLINKER_F("new"));
                
//                 _data = BLINKER_F("{");
//                 _data += jsonValue;
//                 _data += BLINKER_F("}");
//             }
//         }
//         else {
//             _data = STRING_format(_sendBuf);

//             if (_data.length())
//             {
//                 BLINKER_LOG_ALL(BLINKER_F("add."));

//                 _data = _data.substring(0, _data.length() - 1);

//                 _data += BLINKER_F(",");
//                 _data += jsonValue;
//                 _data += BLINKER_F("}");
//             }
//             else {
//                 BLINKER_LOG_ALL(BLINKER_F("new."));
                
//                 _data = BLINKER_F("{");
//                 _data += jsonValue;
//                 _data += BLINKER_F("}");
//             }
//         }

//         if (_data.length() > BLINKER_MAX_SEND_BUFFER_SIZE)
//         {
//             BLINKER_ERR_LOG(BLINKER_F("FORMAT DATA SIZE IS MAX THAN LIMIT: "), BLINKER_MAX_SEND_BUFFER_SIZE);
//             return;
//         }

//         strcpy(_sendBuf, _data.c_str());
//     #else
//         String data;

//         BLINKER_LOG_ALL(BLINKER_F("autoFormatData data: "), jsonValue);
//         BLINKER_LOG_ALL(BLINKER_F("strlen(_sendBuf): "), strlen(_sendBuf));
//         BLINKER_LOG_ALL(BLINKER_F("data.length(): "), jsonValue.length());

//         if ((strlen(_sendBuf) + jsonValue.length()) >= BLINKER_MAX_SEND_BUFFER_SIZE)
//         {
//             BLINKER_ERR_LOG(BLINKER_F("FORMAT DATA SIZE IS MAX THAN LIMIT"));
//             return;
//         }

//         if (strlen(_sendBuf) > 0) {
//             data = "," + jsonValue;
//             strcat(_sendBuf, data.c_str());
//         }
//         else {
//             data = "{" + jsonValue;
//             strcpy(_sendBuf, data.c_str());
//         }
//     #endif
// }
// #endif

#endif