#ifndef BLINKER_WLAN_H
#define BLINKER_WLAN_H

#if defined(ESP8266) || defined(ESP32)

// #if defined(BLINKER_PRO)

// #include "Blinker/BlinkerConfig.h"
// #include "modules/ArduinoJson/ArduinoJson.h"
// #if defined(ESP8266)
//     #include <ESP8266WiFi.h>
// #elif defined(ESP32)
//     #include <WiFi.h>
// #endif
// #include <EEPROM.h>

// static WiFiServer *_server;
// static WiFiClient _client;
// static IPAddress apIP(192, 168, 4, 1);
// #if defined(ESP8266)
// static IPAddress netMsk(255, 255, 255, 0);
// #endif

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

#include "Blinker/BlinkerConfig.h"
#include "Blinker/BlinkerDebug.h"
#include "Blinker/BlinkerUtility.h"
#include "Functions/BlinkerWlan.h"
#include "modules/ArduinoJson/ArduinoJson.h"

#if defined(ESP8266)
    #include <ESP8266WiFi.h>
#elif defined(ESP32)
    #include <WiFi.h>
#endif

#include <EEPROM.h>

static WiFiServer *_server;
static WiFiClient _client;
static IPAddress apIP(192, 168, 4, 1);
#if defined(ESP8266)
    static IPAddress netMsk(255, 255, 255, 0);
#endif


enum bwl_status_t
{
    BWL_CONFIG_CKECK,
    BWL_CONFIG_FAIL,
    BWL_CONFIG_SUCCESS,
    BWL_CONNECTING,
    BWL_CONNECTED,
    BWL_DISCONNECTED,
    BWL_SMARTCONFIG_BEGIN,
    BWL_SMARTCONFIG_DONE,
    BWL_SMARTCONFIG_TIMEOUT,
    BWL_STACONFIG_BEGIN,
    BWL_APCONFIG_BEGIN,
    BWL_APCONFIG_DONE,
    BWL_APCONFIG_TIMEOUT,
    BWL_CONNECTED_CHECK,
    BWL_RESET
};

class BlinkerWlan
{
    public :
        BlinkerWlan()
            : _status(BWL_CONFIG_CKECK)
        {}

        bool checkConfig();
        void loadConfig(char *_ssid, char *_pswd);
        void saveConfig(char *_ssid, char *_pswd);
        void deleteConfig();
        void smartconfigBegin(uint16_t _time = 15000);
        bool smartconfigDone();
        void connect();
        bool connected();
        void disconnect();
        void reset();
        bool run();
        bwl_status_t status() { return _status; }

        void setType(const char* _type) {
            _deviceType = _type;

#ifdef BLINKER_DEBUG_ALL
            BLINKER_LOG(BLINKER_F("API deviceType: "), _type);
#endif
        }

        void softAPinit();
        void serverClient();
        void parseUrl(String data);
        void connectWiFi(String _ssid, String _pswd);
        void connectWiFi(const char* _ssid, const char* _pswd);

        // uint8_t status() { return _status; }

    private :

    protected :
        char *SSID;
        char *PSWD;
        const char* _deviceType;
        uint32_t connectTime;
        uint16_t timeout;
        bwl_status_t _status;
        uint32_t debugStatusTime;
};

bool BlinkerWlan::checkConfig() {
    char ok[2 + 1];
    EEPROM.begin(BLINKER_EEP_SIZE);
    EEPROM.get(BLINKER_EEP_ADDR_WLAN_CHECK, ok);
    EEPROM.commit();
    EEPROM.end();

    if (String(ok) != String("OK")) {
        
        BLINKER_LOG(BLINKER_F("wlan config check,fail"));

        _status = BWL_CONFIG_FAIL;
        return false;
    }
    else {

        BLINKER_LOG(BLINKER_F("wlan config check,success"));

        _status = BWL_CONFIG_SUCCESS;
        return true;
    }
}

void BlinkerWlan::loadConfig(char *_ssid, char *_pswd) {
    char loadssid[BLINKER_SSID_SIZE];
    char loadpswd[BLINKER_PSWD_SIZE];

    EEPROM.begin(BLINKER_EEP_SIZE);
    EEPROM.get(BLINKER_EEP_ADDR_SSID, loadssid);
    EEPROM.get(BLINKER_EEP_ADDR_PSWD, loadpswd);
    // char ok[2 + 1];
    // EEPROM.get(EEP_ADDR_WIFI_CFG + BLINKER_SSID_SIZE + BLINKER_PSWD_SIZE, ok);
    EEPROM.commit();
    EEPROM.end();

    strcpy(_ssid, loadssid);
    strcpy(_pswd, loadpswd);

    BLINKER_LOG(BLINKER_F("SSID: "), _ssid, BLINKER_F(" PASWD: "), _pswd);
}

void BlinkerWlan::saveConfig(char *_ssid, char *_pswd) {
    char loadssid[BLINKER_SSID_SIZE];
    char loadpswd[BLINKER_PSWD_SIZE];

    memcpy(loadssid, _ssid, BLINKER_SSID_SIZE);
    memcpy(loadpswd, _pswd, BLINKER_PSWD_SIZE);

    EEPROM.begin(BLINKER_EEP_SIZE);
    EEPROM.put(BLINKER_EEP_ADDR_SSID, loadssid);
    EEPROM.put(BLINKER_EEP_ADDR_PSWD, loadpswd);
    char ok[2 + 1] = "OK";
    EEPROM.put(BLINKER_EEP_ADDR_WLAN_CHECK, ok);
    EEPROM.commit();
    EEPROM.end();

    BLINKER_LOG(BLINKER_F("Save wlan config"));
}

void BlinkerWlan::deleteConfig() {
    char ok[3] = {0};
    EEPROM.begin(BLINKER_EEP_SIZE);
    // for (int i = BLINKER_EEP_ADDR_WLAN_CHECK; i < BLINKER_WLAN_CHECK_SIZE; i++)
    //     EEPROM.write(i, 0);
    EEPROM.put(BLINKER_EEP_ADDR_WLAN_CHECK, ok);
    EEPROM.commit();
    EEPROM.end();

    BLINKER_LOG(BLINKER_F("Erase wlan config"));
}

void BlinkerWlan::smartconfigBegin(uint16_t _time) {
    WiFi.mode(WIFI_STA);
    delay(100);
    String softAP_ssid = STRING_format(_deviceType) + "_" + macDeviceName();

#if defined(ESP8266)
    WiFi.hostname(softAP_ssid);
#elif defined(ESP32)
    WiFi.setHostname(softAP_ssid.c_str());
#endif

    WiFi.beginSmartConfig();
    connectTime = millis();
    timeout = _time;
    _status = BWL_SMARTCONFIG_BEGIN;

    BLINKER_LOG(BLINKER_F("Wait for Smartconfig"));
}

bool BlinkerWlan::smartconfigDone() {
    if (WiFi.smartConfigDone())
    {
        // WiFi.setAutoConnect(true);
        // WiFi.setAutoReconnect(true);
        connectTime = millis();

        _status = BWL_SMARTCONFIG_DONE;

        BLINKER_LOG(BLINKER_F("SmartConfig Success"));
#if defined(ESP8266)
        BLINKER_LOG(BLINKER_F("SSID: "), WiFi.SSID(), BLINKER_F(" PSWD: "), WiFi.psk());
        // WiFi.begin(WiFi.SSID().c_str(), WiFi.psk().c_str());
        connectWiFi(WiFi.SSID().c_str(), WiFi.psk().c_str());
#endif
        return true;
    }
    else {
        return false;
    }
}

void BlinkerWlan::connect() {
    switch (_status) {
        case BWL_CONFIG_SUCCESS :
            // WiFi.setAutoConnect(false);
            // WiFi.setAutoReconnect(true);

            SSID = (char*)malloc(BLINKER_SSID_SIZE*sizeof(char));
            PSWD = (char*)malloc(BLINKER_PSWD_SIZE*sizeof(char));

            loadConfig(SSID, PSWD);
            // WiFi.begin(SSID, PSWD);
            connectWiFi(SSID, PSWD);

            free(SSID);
            free(PSWD);

            _status = BWL_CONNECTING;
            break;
        case BWL_DISCONNECTED :
            if (millis() - connectTime > 30000 && WiFi.status() != WL_CONNECTED) {
                BLINKER_LOG(BLINKER_F("status: "), WiFi.status());

                disconnect();
                // SSID = (char*)malloc(BLINKER_SSID_SIZE*sizeof(char));
                // PSWD = (char*)malloc(BLINKER_PSWD_SIZE*sizeof(char));
                // WiFi.reconnect();

                char _ssid_[BLINKER_SSID_SIZE];
                char _pswd_[BLINKER_PSWD_SIZE];

                loadConfig(_ssid_, _pswd_);
                connectWiFi(_ssid_, _pswd_);

                // WiFi.setAutoConnect(false);
                // WiFi.setAutoReconnect(true);

                // free(SSID);
                // free(PSWD);
                connectTime = millis();
                BLINKER_LOG(BLINKER_F("connecting BWL_DISCONNECTED"));
                
                BLINKER_LOG(BLINKER_F("_ssid_: "), _ssid_, BLINKER_F(" _pswd_: "), _pswd_);
            }
            else if(WiFi.status() == WL_CONNECTED) {
                _status = BWL_CONNECTED;
            }
            break;
    }
}

bool BlinkerWlan::connected() {
    switch (_status) {
        case BWL_SMARTCONFIG_DONE :
            if (WiFi.status() != WL_CONNECTED) {
                if (millis() - connectTime > 15000)
                {
                    BLINKER_LOG(BLINKER_F("smartConfig time out"));
                    
                    WiFi.stopSmartConfig();
                    _status = BWL_SMARTCONFIG_TIMEOUT;
                }
                return false;
            }
            else if (WiFi.status() == WL_CONNECTED) {
                // WiFi.stopSmartConfig();

                IPAddress deviceIP = WiFi.localIP();
                BLINKER_LOG(BLINKER_F("WiFi connected"));
                BLINKER_LOG(BLINKER_F("IP address: "));
                BLINKER_LOG(deviceIP);
                BLINKER_LOG(BLINKER_F("SSID: "), WiFi.SSID(), BLINKER_F(" PSWD: "), WiFi.psk());
                
                SSID = (char*)malloc(BLINKER_SSID_SIZE*sizeof(char));
                PSWD = (char*)malloc(BLINKER_PSWD_SIZE*sizeof(char));
                memcpy(SSID,"\0",BLINKER_SSID_SIZE);
                memcpy(SSID,WiFi.SSID().c_str(),BLINKER_SSID_SIZE);
                memcpy(PSWD,"\0",BLINKER_PSWD_SIZE);
                memcpy(PSWD,WiFi.psk().c_str(),BLINKER_PSWD_SIZE);
                saveConfig(SSID, PSWD);
                free(SSID);
                free(PSWD);

                // WiFi.setAutoConnect(true);
                // WiFi.setAutoReconnect(true);

                _status = BWL_CONNECTED_CHECK;

                BLINKER_LOG_FreeHeap_ALL();
                
                return true;
            }
            // break;
        case BWL_APCONFIG_DONE :
            if (WiFi.status() != WL_CONNECTED) {
                if (millis() - connectTime > 15000)
                {
                    BLINKER_LOG(BLINKER_F("APConfig time out"));
                    
                    // WiFi.stopSmartConfig();
                    _status = BWL_APCONFIG_TIMEOUT;
                }
                return false;
            }
            else if (WiFi.status() == WL_CONNECTED) {
                IPAddress deviceIP = WiFi.localIP();
                BLINKER_LOG(BLINKER_F("WiFi connected"));
                BLINKER_LOG(BLINKER_F("IP address: "));
                BLINKER_LOG(deviceIP);
                BLINKER_LOG(BLINKER_F("SSID: "), WiFi.SSID(), BLINKER_F(" PSWD: "), WiFi.psk());
                
                // SSID = (char*)malloc(BLINKER_SSID_SIZE*sizeof(char));
                // PSWD = (char*)malloc(BLINKER_PSWD_SIZE*sizeof(char));
                // memcpy(SSID,"\0",BLINKER_SSID_SIZE);
                // memcpy(SSID,WiFi.SSID().c_str(),BLINKER_SSID_SIZE);
                // memcpy(PSWD,"\0",BLINKER_PSWD_SIZE);
                // memcpy(PSWD,WiFi.psk().c_str(),BLINKER_PSWD_SIZE);
                saveConfig(SSID, PSWD);
                free(SSID);
                free(PSWD);

                // WiFi.setAutoConnect(true);
                // WiFi.setAutoReconnect(true);

                _status = BWL_CONNECTED_CHECK;
                return true;
            }
            break;
        case BWL_CONNECTING :
            if (WiFi.status() == WL_CONNECTED) {
                IPAddress deviceIP = WiFi.localIP();
                BLINKER_LOG(BLINKER_F("WiFi connected"));
                BLINKER_LOG(BLINKER_F("IP address: "));
                BLINKER_LOG(deviceIP);
                BLINKER_LOG(BLINKER_F("SSID: "), WiFi.SSID(), BLINKER_F(" PSWD: "), WiFi.psk());
                
                _status = BWL_CONNECTED_CHECK;
                return true;
            }
            else if (WiFi.status() != WL_CONNECTED) {
                return false;
            }
        case BWL_CONNECTED_CHECK :
            // if (WiFi.status() != WL_CONNECTED)
            //     _status = BWL_DISCONNECTED;
            if (WiFi.status() == WL_CONNECTED)
            {
                return true;
            }
            else
            {
                _status = BWL_DISCONNECTED;
                return false;
            }
        case BWL_RESET :
            return false;
        default :
            if (WiFi.status() == WL_CONNECTED) {
                IPAddress deviceIP = WiFi.localIP();
                BLINKER_LOG(BLINKER_F("WiFi connected"));
                BLINKER_LOG(BLINKER_F("IP address: "));
                BLINKER_LOG(deviceIP);
                BLINKER_LOG(BLINKER_F("SSID: "), WiFi.SSID(), BLINKER_F(" PSWD: "), WiFi.psk());
                
                _status = BWL_CONNECTED_CHECK;
                return true;
            }
            return false;
    }
    return false;
}

void BlinkerWlan::disconnect() {
    WiFi.disconnect();
    delay(100);
    _status = BWL_DISCONNECTED;
    BLINKER_LOG(BLINKER_F("WiFi disconnected"));
}

void BlinkerWlan::reset() {
    disconnect();
    _status = BWL_RESET;
}

void BlinkerWlan::softAPinit() {
    _server = new WiFiServer(80);

    WiFi.mode(WIFI_AP);
    String softAP_ssid = STRING_format(_deviceType) + "_" + macDeviceName();
    
#if defined(ESP8266)
    WiFi.hostname(softAP_ssid);
#elif defined(ESP32)
    WiFi.setHostname(softAP_ssid.c_str());
#endif

#if defined(ESP8266)
    WiFi.softAPConfig(apIP, apIP, netMsk);
#elif defined(ESP32)
    WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
#endif

    WiFi.softAP(softAP_ssid.c_str(), "12345678");
    delay(100);

    _server->begin();
    BLINKER_LOG(BLINKER_F("AP IP address: "), WiFi.softAPIP());
    BLINKER_LOG(BLINKER_F("HTTP _server started"));
    BLINKER_LOG(String("URL: http://" + WiFi.softAPIP()));

    _status = BWL_APCONFIG_BEGIN;

    BLINKER_LOG(BLINKER_F("Wait for APConfig"));
}

void BlinkerWlan::serverClient()
{
    if (!_client)
    {
        _client = _server->available();
    }
    else
    {
        // if (_client.status() == CLOSED)
        if (!_client.connected())
        {
            _client.stop();
            BLINKER_LOG(BLINKER_F("Connection closed on _client"));
        }
        else
        {
            if (_client.available())
            {
                String data = _client.readStringUntil('\r');

                // data = data.substring(4, data.length() - 9);
                _client.flush();

                // BLINKER_LOG("clientData: ", data);

                if (STRING_contains_string(data, "ssid") && STRING_contains_string(data, "pswd")) {

                    String msg = BLINKER_F("{\"hello\":\"world\"}");
                    
                    String s= BLINKER_F("HTTP/1.1 200 OK\r\nContent-Type: application/json;charset=utf-8\r\n");
                    s += BLINKER_F("Content-Length: ");
                    s += String(msg.length());
                    s += BLINKER_F("\r\nConnection: Keep Alive\r\n\r\n");
                    s += msg;
                    s += BLINKER_F("\r\n");

                    _client.print(s);
                    
                    _client.stop();

                    parseUrl(data);
                }
            }
        }
    }
}

void BlinkerWlan::parseUrl(String data)
{
    BLINKER_LOG(BLINKER_F("APCONFIG data: "), data);
    DynamicJsonBuffer jsonBuffer;
    JsonObject& wifi_data = jsonBuffer.parseObject(data);

    if (!wifi_data.success()) {
        return;
    }
                    
    String _ssid = wifi_data["ssid"];
    String _pswd = wifi_data["pswd"];

    BLINKER_LOG(BLINKER_F("ssid: "), _ssid);
    BLINKER_LOG(BLINKER_F("pswd: "), _pswd);

    free(_server);

    SSID = (char*)malloc(BLINKER_SSID_SIZE*sizeof(char));
    PSWD = (char*)malloc(BLINKER_PSWD_SIZE*sizeof(char));

    strcpy(SSID, _ssid.c_str());
    strcpy(PSWD, _pswd.c_str());
    connectWiFi(_ssid, _pswd);
    connectTime = millis();
    _status = BWL_APCONFIG_DONE;

    BLINKER_LOG(BLINKER_F("APConfig Success"));
}

void BlinkerWlan::connectWiFi(String _ssid, String _pswd)
{
    connectWiFi(_ssid.c_str(), _pswd.c_str());
}

void BlinkerWlan::connectWiFi(const char* _ssid, const char* _pswd)
{
    uint32_t connectTime = millis();

    BLINKER_LOG(BLINKER_F("Connecting to "), _ssid);

    WiFi.mode(WIFI_STA);
    String _hostname = STRING_format(_deviceType) + "_" + macDeviceName();

    #if defined(ESP8266)
        WiFi.hostname(_hostname);
    #elif defined(ESP32)
        WiFi.setHostname(_hostname.c_str());
    #endif

    if (_pswd && strlen(_pswd)) {
        WiFi.begin(_ssid, _pswd);
    }
    else {
        WiFi.begin(_ssid);
    }

    // while (WiFi.status() != WL_CONNECTED) {
    //     ::delay(50);

    //     if (millis() - connectTime > BLINKER_CONNECT_TIMEOUT_MS && WiFi.status() != WL_CONNECTED) {
    //         connectTime = millis();
    //         BLINKER_LOG(("WiFi connect timeout, please check ssid and pswd!"));
    //         BLINKER_LOG(("Retring WiFi connect again!"));
    //     }
    // }
    // BLINKER_LOG(("Connected"));

    // IPAddress myip = WiFi.localIP();
    // BLINKER_LOG(("Your IP is: "), myip);

    // mDNSInit();
}

bool BlinkerWlan::run()
{
    // if (millis() - debugStatusTime > 10000) {
    //     debugStatusTime = millis();

    //     BLINKER_LOG_ALL("WLAN status: ", _status);
    // }

    switch (_status) {
        case BWL_CONFIG_CKECK :
            checkConfig();
            break;
        case BWL_CONFIG_FAIL :
            #if defined(BLINKER_ESP_SMARTCONFIG)
                smartconfigBegin();
            #elif defined(BLINKER_APCONFIG)
                softAPinit();
            #endif
            break;
        case BWL_CONFIG_SUCCESS :
            connect();
            break;
        case BWL_CONNECTING :
            return connected();
            break;
        case BWL_CONNECTED :
            return connected();
            break;
        case BWL_DISCONNECTED :
            connect();
            break;
        case BWL_SMARTCONFIG_BEGIN :
            smartconfigDone();
            break;
        case BWL_SMARTCONFIG_DONE :
            return connected();
            break;
        case BWL_SMARTCONFIG_TIMEOUT :
            _status = BWL_CONFIG_FAIL;
            break;
        case BWL_STACONFIG_BEGIN :
            connect();
            break;
        case BWL_APCONFIG_BEGIN :
            serverClient();
            break;
        case BWL_APCONFIG_DONE :
            return connected();
            break;
        case BWL_APCONFIG_TIMEOUT :
            _status = BWL_CONFIG_FAIL;
            break;
        case BWL_CONNECTED_CHECK :
            return connected();
            break;
        case BWL_RESET:
            break;
        default :
            break;
    }
    return false;
}

// #endif
#endif

#endif