/*
 *
 */


#include <SPIFFS.h>

#include <WiFi.h>
#include "ESPAsyncWebServer.h"
#include <DNSServer.h>

#include "led/ledprog.h"

// bugfix workaround
#include "hw/hal/timer.h"


const uint8_t emj_ospace[] = {0xF0, 0x9F, 0x8C, 0x8C, 0x00};
const uint8_t emj_rocket[] = {0xF0, 0x9F, 0x9A, 0x80, 0x00};


AsyncWebServer httpd(80);
DNSServer dns;

String conf_name;
String conf_psk2;
String conf_titl;
String conf_yoff = "0";

char ssid[32];
char psk2[32];

String http_prog_table[6];


// processing
static void _http_save_name()
{
    if (SPIFFS.exists("/sf-name.conf")) {
        SPIFFS.remove("/sf-name.conf");
    }

    File f = SPIFFS.open("/sf-name.conf", "w");
    
    if (f) {
        f.println(conf_name);
        f.println(conf_psk2);
        f.println(conf_titl);
        f.println(conf_yoff);
        f.close();
    }
}

static void _http_load_name()
{
    if (SPIFFS.exists("/sf-name.conf")) {
        File f = SPIFFS.open("/sf-name.conf", "r");

        if (f) {
            conf_name = f.readStringUntil('\n');
            conf_name.trim();
            conf_psk2 = f.readStringUntil('\n');
            conf_psk2.trim();
            conf_titl = f.readStringUntil('\n');
            conf_titl.trim();
            conf_yoff = f.readStringUntil('\n');
            conf_yoff.trim();
            f.close();
            return;
        }
    }

    uint8_t mac[6];
    char mac_last[3];

    esp_read_mac(mac, ESP_MAC_WIFI_STA);
    sprintf(mac_last, "%01X", mac[5]);
    
    conf_name = "Space Hacker";
    conf_psk2 = "ChangeMeNow" + String(mac_last);
}

void http_generate_prog_str()
{
    int i;
    int j;

    String s;
    char w[6];

    // burteforce, memory hungry way of fixing this for now
    // if we do this in the callback handler, it takes too long for it to work correctly
    // but I've got plenty of RAM and a little time so fuck it, let's do this shit

    for (j = 0; j < 6; j++) {
        s = String();

        for (i = 0; i < (sizeof(ledprog_list) / sizeof(ledprog_list[0])); i++) {
            if (ledprog_list[i].name != NULL) {
                itoa(i, w, 10);
                s += "<option value=\"" + String(w) + "\"";
                if (progu[j].prog_idx == i) {
                    s += String(" selected");
                }
                s += ">" + String(ledprog_list[i].name) + "</option>";
            }
        }

        http_prog_table[j] = s;
    }
}

static String http_index(const String &str)
{
    char c;
    char w[6];
    uint8_t idx;
    uint8_t opt;
    
    // name
    if (str == "NAME") {
        return conf_name;
    }

    // password
    /*
    if (str == "PSK2") {
        return conf_psk2;
    }
    */

    // custom title
    if (str == "TITLE") {
        return conf_titl;
    }

    // y offset
    if (str == "YOFF") {
        return conf_yoff;
    }

    // program
    if (str.charAt(0) == 'P') {
        c = str.charAt(1);
        idx = atoi(&c) - 1;

        // valid index?
        if (idx >= 6) return String();

        // simple variables
        if (str.endsWith("RUNENA")) {
            return progu[idx].runena ? " CHECKED" : String();
        } else
        if (str.endsWith("SPEED")) {
            itoa(progu[idx].speed, w, 10);
            return String(w);
        } else
        if (str.substring(3, 7) == "CONF") {
            opt = str.charAt(7) - 0x30;

            if (opt > 6) {
                return String();
            }

            itoa(progu[idx].option[opt], w, 10);  
            return String(w);  
        } else

        // program select
        // processing moved elsewhere as we run
        // into a race or something doing it here
        if (str.endsWith("PROGSEL")) {
            return http_prog_table[idx];
        }
    }

    return String();
}

// using
void httpd_start()
{
    uint8_t mac[6];
    char mac_sub[5];
    
    static IPAddress local_ip(172, 16, 101, 1);
    static IPAddress gateway(172, 16, 101, 1);
    static IPAddress subnet(255, 255, 255, 128);

    esp_read_mac(mac, ESP_MAC_WIFI_STA);
    sprintf(mac_sub, "%01X%01X", mac[3], mac[4]);

    strcpy(ssid, (const char *)emj_ospace);
    strcat(ssid, " ");
    strcat(ssid, conf_name.c_str());
    strcat(ssid, " [");
    strcat(ssid, mac_sub);
    strcat(ssid, "] ");
    strcat(ssid, (const char *)emj_rocket);

    WiFi.softAP(ssid, conf_psk2.c_str());
    WiFi.softAPConfig(local_ip, gateway, subnet);

    // dns conf (captive portal)
    dns.start(53, "*", WiFi.softAPIP());

    // httpd conf
    httpd.begin();
}

void httpd_stop()
{
    // we'll only turn off if not connected
    if (WiFi.getMode() != WIFI_MODE_NULL) {
        if (WiFi.status() == WL_DISCONNECTED || WiFi.status() == WL_NO_SHIELD) {
            // httpd.end();
            // dns.stop();

            WiFi.mode(WIFI_OFF);
        }
    }
}

void httpd_init()
{
    // name conf
    _http_load_name();

    // www pages conf
    httpd.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
        http_generate_prog_str();
        request->send(SPIFFS, "/www/index.htm", String(), false, http_index);
    });
    httpd.on("/prog-info", HTTP_GET, [](AsyncWebServerRequest *request) {
        request->send(SPIFFS, "/www/prog.htm", String(), false, NULL);
    });

    httpd.serveStatic("/", SPIFFS, "/www/").setCacheControl("max-age=300");

    // www submit forms conf
    httpd.on("/config-name", HTTP_POST, [](AsyncWebServerRequest * request) {
        int updated = 0;
        int params = request->params();
        for(int i = 0; i < params; i++) {
            AsyncWebParameter* p = request->getParam(i);
            // Serial.printf("GET: %s -> %s\n", p->name().c_str(), p->value().c_str());
            if (p->name() == "myname") {
                if (p->value().length() <= 15) {
                    if (conf_name != p->value()) {
                        conf_name = p->value();
                        updated = 1;
                    }
                }
            }
            if (p->name() == "mypsk2") {
                if (p->value().length() >= 8 && p->value().length() <= 31) {
                    if (conf_psk2 != p->value()) {
                        conf_psk2 = p->value();
                        updated = 2;
                    }
                }
            }
            if (p->name() == "mytitle") {
                if (p->value().length() <= 30) {
                    if (conf_titl != p->value()) {
                        conf_titl = p->value();
                        updated = 3;
                    }
                }
            }
            if (p->name() == "myyoff") {
                if (p->value().toInt() < 10) {
                    if (conf_yoff != p->value()) {
                        conf_yoff = p->value();
                        updated = 4;
                    }
                }
            }

            _http_save_name();

            // restart so new psk applies
            if (updated == 2) {
                httpd_stop();
                httpd_start();
            }
        }
        request->redirect("/");
    });  

    httpd.on("/config-prog", HTTP_POST, [](AsyncWebServerRequest * request) {
        int i;
        char c;
        uint8_t idx;
        uint8_t opt;

        String s;

        uint8_t runena = 0;
    
        int params = request->params();

        for(i = 0; i < params; i++) {
            AsyncWebParameter* p = request->getParam(i);
            // Serial.printf("POST: %s -> %s\n", p->name().c_str(), p->value().c_str());
            if (p->name().charAt(0) == 'p') {
                s = p->name();

                c = s.charAt(1);
                
                // not numeric
                if (c < 0x30 || c > 0x39) {
                    continue;
                }
                
                idx = c - 0x30;
                idx -= 1; // we're actually 0-based here

                // out of bounds
                if (idx >= 6) {
                    continue;
                }

                if (s.endsWith("progsel")) {
                    progu[idx].prog_idx = p->value().toInt();
                } else
                if (s.endsWith("runena")) {
                    // only those set will return "on"
                    runena |= (1 << idx);
                } else
                if (s.endsWith("speed")) {
                    progu[idx].speed = p->value().toInt();
                } else
                if (s.substring(3, 7) == "conf") {
                    opt = s.charAt(7) - 0x30;
                    
                    if (opt > 6) {
                        continue;
                    }

                    progu[idx].option[opt] = p->value().toInt();
                }
            }
        }

        // now set runena as needed
        for (i = 0; i < 6; i++) {
            progu[i].runena = (runena & (1 << i)) ? 1 : 0;
        }

        ledprog_save();

        request->redirect("/");
    });
    httpd.on("/do", HTTP_GET, [](AsyncWebServerRequest * request) {
        int params = request->params();
        for(int i = 0; i < params; i++) {
            AsyncWebParameter* p = request->getParam(i);
            // Serial.printf("GET: %s -> %s\n", p->name().c_str(), p->value().c_str());
            if (p->name() == "test-prog") {
                if (p->value().toInt() <= 6) {
                    int w = p->value().toInt() - 1;
                    prog = &progu[w];
                    ledprog_init(prog);
                }
            } else if (p->name() == "default-prog") {
                ledprog_load(1);
            }
        }
        request->redirect("/");
    });
}