#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include "FS.h"
#include <LittleFS.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>


#define CONFIG_PIN   0

typedef struct {
    char ip[20];
    uint16_t port;
    char ssid[64];
    char password[128];
    char is_info_valid;
} Config;

uint8_t uart_rx_buffer[512];
size_t uart_rx_buffer_index = 0;

uint8_t uart_tx_buffer[512];
size_t uart_tx_buffer_index = 0;


uint8_t config_mode_counter = 0;
uint8_t config_buffer[1024];
size_t config_buffer_index = 0;

// static uint8_t test_buff[40960] = {0};


WiFiClient client;
Config config;
ESP8266WebServer server(80);
const char* host = "300w-esp";
const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";



void connectToServer(Config *config) {
    if (!client.connect(config->ip, config->port)) {
        return;
    }
    client.keepAlive();
    client.setTimeout(1);
}

bool parseConfig(const uint8_t* buffer, size_t length, Config *config) {
    StaticJsonDocument<1024> doc;
    auto error = deserializeJson(doc, buffer, length);
    if (error) {
        return false;
    }

    const char* serverIP = doc["ip"];
    const uint16_t serverPort = doc["port"];
    const char* wifiSSID = doc["ssid"];
    const char* wifiPassword = doc["password"];

    if (serverIP == nullptr || serverPort == 0 || wifiSSID == nullptr || wifiPassword == nullptr) {
        return false;
    }
    strncpy(config->ip, serverIP, sizeof(config->ip) - 1);
    config->ip[sizeof(config->ip) - 1] = '\0';
    config->port = serverPort;
    strncpy(config->ssid, wifiSSID, sizeof(config->ssid) - 1);
    config->ssid[sizeof(config->ssid) - 1] = '\0';
    strncpy(config->password, wifiPassword, sizeof(config->password) - 1);
    config->password[sizeof(config->password) - 1] = '\0';
    config->is_info_valid = 1;
    return true;
}

bool loadConfig(Config *config) {
    File configFile = LittleFS.open("/config.json", "r");
    config->is_info_valid = 0;
    if (!configFile) {
        return false;
    }
    return parseConfig((const uint8_t*)configFile.readString().c_str(), configFile.size(), config);
}

bool saveConfig(Config *config) {
    if (!config->is_info_valid) {
        return false;
    }
    StaticJsonDocument<1024> doc;
    doc["ip"] = config->ip;
    doc["port"] = config->port;
    doc["ssid"] = config->ssid;
    doc["password"] = config->password;


    File configFile = LittleFS.open("/config.json", "w");
    if (!configFile) {
        return false;
    }
    serializeJson(doc, configFile);
    Serial.println("OK");
    return true;
}

bool getConfigFromSerial(Config *config) {
    Serial.setTimeout(2000);
    Serial.println("CONFIG");
    size_t len = Serial.readBytesUntil('\n', config_buffer, sizeof(config_buffer));
    if (len > 0) {
        if (parseConfig(config_buffer, len, config)) {
            Serial.setTimeout(1);
            return saveConfig(config);
        }
    }
    Serial.setTimeout(1);
    return false;
}

void setUpWebServer(){
    if(MDNS.isRunning()){
        MDNS.close();
    }
    // server.close();
    if (WiFi.waitForConnectResult() == WL_CONNECTED) {
        MDNS.begin(host);
        server.on("/", HTTP_GET, []() {
            server.sendHeader("Connection", "close");
            server.send(200, "text/html", serverIndex);
        });
        server.onNotFound([]() {
            server.sendHeader("Connection", "close");
            server.send(404, "text/html", "Page Not Found");
        });
        server.on(
        "/update", HTTP_POST, []() {
            server.sendHeader("Connection", "close");
            server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
            ESP.restart();
        },
        []() {
            HTTPUpload& upload = server.upload();
            if (upload.status == UPLOAD_FILE_START) {
                Serial.setDebugOutput(true);
                WiFiUDP::stopAll();
                Serial.printf("Update: %s\n", upload.filename.c_str());
                uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
                if (!Update.begin(maxSketchSpace)) {  // start with max available size
                    Update.printError(Serial);
                }
            } else if (upload.status == UPLOAD_FILE_WRITE) {
                if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
                    Update.printError(Serial);
                }
            } else if (upload.status == UPLOAD_FILE_END) {
                if (Update.end(true)) {  // true to set the size to the current progress
                    Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
                } else {
                    Update.printError(Serial);
                }
                Serial.setDebugOutput(false);
            }
            yield();
        });
        server.begin();
        MDNS.addService("http", "tcp", 80);

        Serial.printf("Ready! Open http://%s.local in your browser\n", host);
    } else {
        Serial.println("WiFi Failed");
    }
}


void setup() {
    LittleFS.begin();
    Serial.begin(115200);
    Serial.setTimeout(1);
    Serial.setRxBufferSize(512);
    loadConfig(&config);
    while (!config.is_info_valid) {
        
        getConfigFromSerial(&config);
        delay(500);
    }
    WiFi.mode(WIFI_STA);
    WiFi.begin(config.ssid, config.password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        // Serial.print(".");
    }
    // Serial.println(WiFi.localIP());
    connectToServer(&config);
    setUpWebServer();
    
}



void loop() {
    server.handleClient();
    MDNS.update();
    uart_rx_buffer_index = Serial.readBytes(uart_rx_buffer,64);
    if (client.connected()) {
        client.write(uart_rx_buffer,uart_rx_buffer_index);
    }
    else {
        connectToServer(&config);
    }

    while (client.available()) {
        uart_tx_buffer_index = client.read(uart_tx_buffer,32);
        Serial.write(uart_tx_buffer,uart_tx_buffer_index);
    }


    config_mode_counter++;
    if (config_mode_counter >= 100) {
        config_mode_counter = 0;
        if(!digitalRead(CONFIG_PIN)) {
            uint8_t get_ok = getConfigFromSerial(&config);
            if(get_ok) {
                saveConfig(&config);
                WiFi.disconnect();
                WiFi.mode(WIFI_STA);
                WiFi.begin(config.ssid, config.password);
                while (WiFi.status() != WL_CONNECTED) {
                    delay(500);
                    // Serial.print(".");
                }
                // Serial.println(WiFi.localIP());
                connectToServer(&config);
                setUpWebServer();
            }
        }
    }

}
