#ifndef BLINKER_SIM7020_H
#define BLINKER_SIM7020_H

#if defined(ARDUINO)
    #if ARDUINO >= 100
        #include <Arduino.h>
    #else
        #include <WProgram.h>
    #endif
#endif

#include "../Blinker/BlinkerATMaster.h"
#include "../Blinker/BlinkerConfig.h"
#include "../Blinker/BlinkerDebug.h"
#include "../Blinker/BlinkerStream.h"
#include "../Blinker/BlinkerUtility.h"

#include <time.h>

#define BLINKER_SIM7020_DATA_BUFFER_SIZE 1024

enum sim7020_status_t
{
    sim7020_cpin_REQ,
    sim7020_cpin_success,
    sim7020_csq_REQ,
    sim7020_csq_success,
    sim7020_cgreg_REQ,
    sim7020_cgreg_success,
    sim7020_cgact_REQ,
    sim7020_cgact_success,
    sim7020_cops_REQ,
    sim7020_cops_success,
    sim7020_cgcontrdp_REQ,
    sim7020_cgcontrdp_success,
};

class BlinkerSIM7020
{
    public :
        BlinkerSIM7020() :
            isHWS(false)
        {}

        ~BlinkerSIM7020() { flush(); }

        time_t  _ntpTime = 0;

        void setStream(Stream& s, bool isHardware, blinker_callback_t _func)
        { stream = &s; isHWS = isHardware; listenFunc = _func; }

        // int16_t year()
        // {
        //     if (_ntpTime)
        //     {
        //         struct tm timeinfo;
        //         #if defined(ESP8266) || defined(__AVR__)
        //             gmtime_r(&_ntpTime, &timeinfo);
        //         #elif defined(ESP32)
        //             localtime_r(&_ntpTime, &timeinfo);
        //         #endif
                
        //         return timeinfo.tm_year + 1900;
        //     }
        //     return -1;
        // }

        // int8_t  month()
        // {
        //     if (_ntpTime)
        //     {
        //         struct tm timeinfo;
        //         #if defined(ESP8266) || defined(__AVR__)
        //             gmtime_r(&_ntpTime, &timeinfo);
        //         #elif defined(ESP32)
        //             localtime_r(&_ntpTime, &timeinfo);
        //         #endif
                
        //         return timeinfo.tm_mon + 1;
        //     }
        //     return -1;
        // }

        // int8_t  mday()
        // {
        //     if (_ntpTime)
        //     {
        //         struct tm timeinfo;
        //         #if defined(ESP8266) || defined(__AVR__)
        //             gmtime_r(&_ntpTime, &timeinfo);
        //         #elif defined(ESP32)
        //             localtime_r(&_ntpTime, &timeinfo);
        //         #endif
                
        //         return timeinfo.tm_mday;
        //     }
        //     return -1;
        // }

        // int8_t  wday()
        // {
        //     if (_ntpTime)
        //     {
        //         struct tm timeinfo;
        //         #if defined(ESP8266) || defined(__AVR__)
        //             gmtime_r(&_ntpTime, &timeinfo);
        //         #elif defined(ESP32)
        //             localtime_r(&_ntpTime, &timeinfo);
        //         #endif
                
        //         return timeinfo.tm_wday;
        //     }
        //     return -1;
        // }

        // int8_t  hour()
        // {
        //     if (_ntpTime)
        //     {
        //         struct tm timeinfo;
        //         #if defined(ESP8266) || defined(__AVR__)
        //             gmtime_r(&_ntpTime, &timeinfo);
        //         #elif defined(ESP32)
        //             localtime_r(&_ntpTime, &timeinfo);
        //         #endif
                
        //         return timeinfo.tm_hour;
        //     }
        //     return -1;
        // }

        // int8_t  minute()
        // {
        //     if (_ntpTime)
        //     {
        //         struct tm timeinfo;
        //         #if defined(ESP8266) || defined(__AVR__)
        //             gmtime_r(&_ntpTime, &timeinfo);
        //         #elif defined(ESP32)
        //             localtime_r(&_ntpTime, &timeinfo);
        //         #endif
                
        //         return timeinfo.tm_min;
        //     }
        //     return -1;
        // }

        // int8_t  second()
        // {
        //     if (_ntpTime)
        //     {
        //         struct tm timeinfo;
        //         #if defined(ESP8266) || defined(__AVR__)
        //             gmtime_r(&_ntpTime, &timeinfo);
        //         #elif defined(ESP32)
        //             localtime_r(&_ntpTime, &timeinfo);
        //         #endif
                
        //         return timeinfo.tm_sec;
        //     }
        //     return -1;
        // }

        // time_t  time()
        // {
        //     if (_ntpTime)
        //     {
        //         return _ntpTime - _timezone * 3600;
        //     }
        //     return millis();
        // }

        // // time_t  now_ntp()
        // // {
        // //     if (_ntpTime)
        // //     {
        // //         return _ntpTime;
        // //     }
        // //     return millis();
        // // }

        // int32_t dtime()
        // {
        //     if (_ntpTime)
        //     {
        //         struct tm timeinfo;
        //         #if defined(ESP8266) || defined(__AVR__)
        //             gmtime_r(&_ntpTime, &timeinfo);
        //         #elif defined(ESP32)
        //             localtime_r(&_ntpTime, &timeinfo);
        //         #endif
                
        //         return timeinfo.tm_hour * 60 * 60 + timeinfo.tm_min * 60 + timeinfo.tm_sec;
        //     }
        //     return -1;
        // }

        bool getSNTP(float _tz = 8.0, char _url[] = "120.25.108.11")
        {
            uint32_t os_time = millis();
            _timezone = _tz;
            streamPrint(STRING_format(BLINKER_CMD_CSNTPSTART_REQ) + \
                        "=" + STRING_format(_url));

            while(millis() - os_time < _simTimeout * 10)
            {
                if (available())
                {
                    _masterAT = new BlinkerMasterAT();
                    _masterAT->update(STRING_format(streamData));

                    if (_masterAT->getState() != AT_M_NONE &&
                        _masterAT->reqName() == BLINKER_CMD_CSNTP)
                    {
                        struct tm timeinfo;

                        timeinfo.tm_year = _masterAT->getParam(0).substring(1, 3).toInt() + 130;
                        timeinfo.tm_mon  = _masterAT->getParam(0).substring(4, 6).toInt() - 1;
                        timeinfo.tm_mday = _masterAT->getParam(0).substring(7, 9).toInt() - 1;

                        timeinfo.tm_hour = _masterAT->getParam(1).substring(0, 2).toInt();
                        timeinfo.tm_min  = _masterAT->getParam(1).substring(3, 5).toInt();
                        timeinfo.tm_sec  = _masterAT->getParam(1).substring(6, 8).toInt();

                        #if defined(ESP8266) || defined(ESP32)
                        _ntpTime = mktime(&timeinfo) + (uint32_t)(_timezone * 3600);
                        #else
                        _ntpTime = mk_gmtime(&timeinfo) + (uint32_t)(_timezone * 3600);
                        #endif
                        // _ntpTime = mk_gmtime(&timeinfo) + (uint32_t)(_timezone * 3600);

                        BLINKER_LOG_ALL(BLINKER_F("year: "), timeinfo.tm_year);
                        BLINKER_LOG_ALL(BLINKER_F("mon: "), timeinfo.tm_mon);
                        BLINKER_LOG_ALL(BLINKER_F("mday: "), timeinfo.tm_mday);
                        BLINKER_LOG_ALL(BLINKER_F("hour: "), timeinfo.tm_hour);
                        BLINKER_LOG_ALL(BLINKER_F("mins: "), timeinfo.tm_min);
                        BLINKER_LOG_ALL(BLINKER_F("secs: "), timeinfo.tm_sec);

                        BLINKER_LOG_ALL(BLINKER_F("_ntpTime: "), _ntpTime);
                        // BLINKER_LOG_ALL(BLINKER_F("==Current time: "), mk_gmtime(&timeinfo));

                        free(_masterAT);
                        return true;
                    }

                    free(_masterAT);
                }
            }
            return false;
        }

        int checkPDN()
        {
            uint32_t sim_time = millis();
            sim7020_status_t pdn_status = sim7020_cpin_REQ;

            streamPrint(BLINKER_CMD_CPIN_REQ);

            while(millis() - sim_time < _simTimeout)
            {
                if (available())
                {
                    _masterAT = new BlinkerMasterAT();
                    _masterAT->update(STRING_format(streamData));

                    if (_masterAT->getState() != AT_M_NONE &&
                        _masterAT->reqName() == BLINKER_CMD_CPIN &&
                        _masterAT->getParam(0) == BLINKER_CMD_READY)
                    {
                        BLINKER_LOG_ALL(BLINKER_F("sim7020_cpin_success"));
                        pdn_status = sim7020_cpin_success;
                        free(_masterAT);
                        break;
                    }
                    free(_masterAT);
                }
            }

            if (pdn_status != sim7020_cpin_success) return false;

            streamPrint(BLINKER_CMD_CSQ_REQ);
            sim_time = millis();

            while(millis() - sim_time < _simTimeout)
            {
                if (available())
                {
                    _masterAT = new BlinkerMasterAT();
                    _masterAT->update(STRING_format(streamData));

                    if (_masterAT->getState() != AT_M_NONE &&
                        _masterAT->reqName() == BLINKER_CMD_CSQ &&
                        _masterAT->getParam(0).toInt() != 99)
                    {
                        BLINKER_LOG_ALL(BLINKER_F("sim7020_csq_success"));
                        pdn_status = sim7020_csq_success;
                        free(_masterAT);
                        break;
                    }
                    free(_masterAT);
                }
            }

            if (pdn_status != sim7020_csq_success) return false;

            streamPrint(BLINKER_CMD_CGREG_REQ);
            sim_time = millis();

            while(millis() - sim_time < _simTimeout)
            {
                if (available())
                {
                    _masterAT = new BlinkerMasterAT();
                    _masterAT->update(STRING_format(streamData));

                    if (_masterAT->getState() != AT_M_NONE &&
                        _masterAT->reqName() == BLINKER_CMD_CGREG &&
                        _masterAT->getParam(0).toInt() == 0)
                    {
                        BLINKER_LOG_ALL(BLINKER_F("sim7020_cgact_success"));
                        pdn_status = sim7020_cgact_success;
                        free(_masterAT);
                        break;
                    }
                    free(_masterAT);
                }
            }

            if (pdn_status != sim7020_cgact_success) return false;

            streamPrint(BLINKER_CMD_COPS_REQ);
            sim_time = millis();

            while(millis() - sim_time < _simTimeout)
            {
                if (available())
                {
                    _masterAT = new BlinkerMasterAT();
                    _masterAT->update(STRING_format(streamData));

                    if (_masterAT->getState() != AT_M_NONE &&
                        _masterAT->reqName() == BLINKER_CMD_COPS)
                        //  &&
                        // _masterAT->getParam(3).toInt() == 9)
                    {
                        BLINKER_LOG_ALL(BLINKER_F("sim7020_cops_success"));
                        pdn_status = sim7020_cops_success;
                        free(_masterAT);
                        break;
                    }
                    free(_masterAT);
                }
            }

            if (pdn_status != sim7020_cops_success) return false;

            streamPrint(BLINKER_CMD_CGCONTRDP_REQ);
            sim_time = millis();

            while(millis() - sim_time < _simTimeout)
            {
                if (available())
                {
                    _masterAT = new BlinkerMasterAT();
                    _masterAT->update(STRING_format(streamData));

                    if (_masterAT->getState() != AT_M_NONE &&
                        _masterAT->reqName() == BLINKER_CMD_CGCONTRDP &&
                        _masterAT->getParam(2).toInt() == 5)
                    {
                        BLINKER_LOG_ALL(BLINKER_F("sim7020_contrdp_success"));
                        // pdn_status = sim7020_cops_success;
                        free(_masterAT);
                        return true;
                    }
                    free(_masterAT);
                }
            }

            return true;
        }

        String getIMEI()
        {
            uint32_t dev_time = millis();

            streamPrint(BLINEKR_CMD_GSN_REQ);

            char _imei[16];

            while(millis() - dev_time < _simTimeout)
            {
                if (available())
                {
                    BLINKER_LOG_ALL(BLINKER_F("get IMEI: "), streamData, 
                                    BLINKER_F(", length: "), strlen(streamData));
                    if (strlen(streamData) == 15)
                    {
                        strcpy(_imei, streamData);
                    }
                }
            }

            dev_time = millis();

            while(millis() - dev_time < _simTimeout)
            {
                if (available())
                {
                    if (strcmp(streamData, BLINKER_CMD_OK) == 0)
                    {
                        BLINKER_LOG_ALL(BLINKER_F("get IMEI: "), _imei,
                                        BLINKER_F(", length: "), strlen(streamData));
                        return _imei;
                    }       
                }
            }

            BLINKER_LOG_ALL(BLINKER_F("get IMEI: "), _imei,
                            BLINKER_F(", length: "), strlen(streamData));

            return _imei;
        }

        String getICCID()
        {
            uint32_t dev_time = millis();

            streamPrint(BLINKER_CMD_CCID_REQ);

            char _iccid[21];

            while(millis() - dev_time < _simTimeout)
            {
                if (available())
                {
                    BLINKER_LOG_ALL(BLINKER_F("get ICCID: "), streamData, 
                                    BLINKER_F(", length: "), strlen(streamData));
                    if (strlen(streamData) == 20)
                    {
                        strcpy(_iccid, streamData);
                    }
                }
            }

            dev_time = millis();

            while(millis() - dev_time < _simTimeout)
            {
                if (available())
                {
                    if (strcmp(streamData, BLINKER_CMD_OK) == 0)
                    {
                        BLINKER_LOG_ALL(BLINKER_F("get ICCID: "), _iccid,
                                        BLINKER_F(", length: "), strlen(streamData));
                        return _iccid;
                    }       
                }
            }

            BLINKER_LOG_ALL(BLINKER_F("get ICCID: "), _iccid,
                            BLINKER_F(", length: "), strlen(streamData));

            return _iccid;
        }

        bool powerCheck()
        {
            streamPrint(BLINKER_CMD_AT);
            streamPrint("ATE0");

            if (!checkPDN()) return false;

            BLINKER_LOG_ALL(BLINKER_F("power check"));
            streamPrint(BLINKER_CMD_AT);
            uint32_t dev_time = millis();

            while(millis() - dev_time < _simTimeout)
            {
                if (available())
                {
                    if (strcmp(streamData, BLINKER_CMD_OK) == 0)
                    {
                        BLINKER_LOG_ALL(BLINKER_F("power on"));
                        
                        // SAPBR();

                        return true;
                    }
                }
            }

            return false;
        }

        bool isReboot()
        {
            streamPrint(BLINKER_CMD_AT);
            uint32_t dev_time = millis();

            while(millis() - dev_time < _simTimeout)
            {
                if (available())
                {
                    BLINKER_LOG_ALL(BLINKER_F("data len: "), strlen(streamData));
                    if (strncmp(streamData, BLINKER_CMD_AT, 2) == 0 || \
                        (strlen(streamData) != 0 && strncmp(streamData, BLINKER_CMD_OK, 2) != 0 && \
                        strncmp(streamData, BLINKER_CMD_ERROR, 5) != 0))
                    {
                        BLINKER_LOG_ALL(BLINKER_F("device reboot"));
                        
                        // SAPBR();
                        // streamPrint("ATE0");

                        return true;
                    }

                    if (strncmp(streamData, BLINKER_CMD_OK, 2) == 0) return false;
                    // else if (strcmp(streamData, BLINKER_CMD_OK) == 0)
                    // {
                    //     BLINKER_LOG_ALL(BLINKER_F("device not reboot"));
                    //     return false;
                    // }                    
                }
            }

            return false;
        }

        bool isAlive()
        {
            streamPrint(BLINKER_CMD_AT, "isAlive");
            uint32_t dev_time = millis();

            while(millis() - dev_time < _simTimeout * 2)
            {
                if (available()) 
                {
                    BLINKER_LOG_ALL(BLINKER_F("alive"));
                    return true;
                }
            }

            BLINKER_LOG_ALL(BLINKER_F("not alive"));

            return false;
        }

        void flush()
        {
            if (isFresh) free(streamData);

            BLINKER_LOG_ALL(BLINKER_F("flush sim7020"));
        }

        bool checkStream(char * data)
        {
            bool _check_ = strcmp(data, streamData);

            BLINKER_LOG_ALL(BLINKER_F("checkStream: "), _check_);

            return _check_ == 0;
        }

        bool available()
        {
            yield();

            if (!isHWS)
            {
                // #if defined(__AVR__) || defined(ESP8266)
                //     if (!SSerial_API->isListening())
                //     {
                //         SSerial_API->listen();
                //         ::delay(100);
                //     }
                // #endif

                if (listenFunc) listenFunc();
            }

            // char _data[BLINKER_SIM7020_DATA_BUFFER_SIZE];// = { '\0' };
            // memset(_data, '\0', BLINKER_SIM7020_DATA_BUFFER_SIZE);

            if (stream->available())
            {
                // strcpy(_data, stream->readStringUntil('\n').c_str());
                String _data = stream->readStringUntil('\n');
                BLINKER_LOG_ALL(BLINKER_F("handleSerial rs: "), _data);
                // _data[strlen(_data) - 1] = '\0';
                if (isFresh) 
                {
                    free(streamData);
                    isFresh = false;
                }

                if (_data.length() <= 1) return false;
                
                streamData = (char*)malloc((_data.length() + 1)*sizeof(char));
                strcpy(streamData, _data.c_str());
                if (_data.length() > 0) streamData[_data.length() - 1] = '\0';
                isFresh = true;
                return true;
//                 // streamData = "";
//                 // memset(streamData, '\0', 128);
//                 if (isFresh) free(streamData);
//                 streamData = (char*)malloc(1*sizeof(char));

//                 // strcpy(streamData, stream->readStringUntil('\n').c_str());

//                 // int16_t dNum = strlen(streamData);

//                 // BLINKER_LOG_ALL(BLINKER_F("handleSerial rs: "), streamData,
//                 //                 BLINKER_F(", dNum: "), dNum);
//                 // // stream->readString();
                
//                 int16_t dNum = 0;
//                 int c_d = timedRead();
//                 while (dNum < BLINKER_MAX_READ_SIZE && 
//                     c_d >=0 && c_d != '\n')
//                 {
//                     // if (c_d != '\r')
//                     {
//                         streamData[dNum] = (char)c_d;
//                         dNum++;
//                         streamData = (char*)realloc(streamData, (dNum+1)*sizeof(char));
//                     }

//                     c_d = timedRead();
//                 }
//                 // dNum++;
//                 // // // streamData = (char*)realloc(streamData, dNum*sizeof(char));

//                 // streamData[dNum-1] = '\0';
//                 // stream->flush();
// // 7b2264657461696c223a207b2262726f6b6572223a2022616c6979756e222c20226465766963654e616d65223a20223237383636394232304d3235423634323230354e33435850222c2022696f744964223a20225346455467613255784b784e386a69716e4e516730303130356435343030222c2022696f74546f6b656e223a20226331353530643464346334623432666338376431343639373333363166353539222c202270726f647563744b6579223a20224a67434762486c6e64677a222c202275756964223a20223733633762356134623266323231633061373264376234313238653430323337227d2c20226d657373616765223a20313030307d

//                 // BLINKER_LOG_ALL(BLINKER_F("handleSerial: "), streamData,
//                 //                 BLINKER_F(" , dNum: "), dNum);
//                 BLINKER_LOG_FreeHeap_ALL();
                
//                 if (dNum < BLINKER_MAX_READ_SIZE && dNum > 0)
//                 {
//                     // if (streamData[dNum - 1] == '\r')
//                     streamData[dNum - 1] = '\0';
//                     BLINKER_LOG_ALL(BLINKER_F("handleSerial: "), streamData,
//                                     BLINKER_F(" , dNum: "), dNum);

//                     isFresh = true;
//                     return true;
//                 }
//                 else
//                 {
//                     return false;
//                 }
            }
            else
            {
                return false;
            }
        }

    protected :
        class BlinkerMasterAT * _masterAT;
        blinker_callback_t listenFunc = NULL;
        Stream* stream;
        // char    streamData[128];
        char*   streamData;
        bool    isFresh = false;
        bool    isHWS = false;
        uint16_t    _simTimeout = 1000;

        // time_t  _ntpTime = 0;

        float   _timezone = 8.0;

        void streamPrint(const String & s, String from = "")
        {
            stream->println(s);
            BLINKER_LOG_ALL(from, BLINKER_F(" SIM: "), s);
        }

        int timedRead()
        {
            int c;
            uint32_t _startMillis = millis();
            do {
                c = stream->read();
                if (c >= 0) return c;
            } while(millis() - _startMillis < 1000);
            return -1; 
        }
};

#endif