#include "./main.h"
#include <homekit/led.h>
#include <homekit/mqtt/mqtt.h>
#include <homekit/mqtt/module/diagnostics.h>
#include <homekit/mqtt/module/ota.h>

namespace homekit::main {

enum WorkingMode working_mode = WorkingMode::NORMAL;
static const uint16_t recovery_boot_detection_ms = 2000;
static const uint8_t recovery_boot_delay_ms = 100;

static volatile enum WiFiConnectionState wifi_state = WiFiConnectionState::WAITING;
static void* service = nullptr;
static WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler;
static Ticker wifiTimer;
static mqtt::MqttDiagnosticsModule* mqttDiagModule;
static mqtt::MqttOtaModule* mqttOtaModule;

#if MQTT_BLINK
static StopWatch blinkStopWatch;
#endif

#ifndef CONFIG_TARGET_ESP01
static DNSServer* dnsServer = nullptr;
#endif

static void onWifiConnected(const WiFiEventStationModeGotIP& event);
static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event);

static void wifiConnect() {
    const char *ssid, *psk, *hostname;
    auto cfg = config::read();
    wifi::getConfig(cfg, &ssid, &psk, &hostname);

    PRINTF("Wi-Fi STA creds: ssid=%s, psk=%s, hostname=%s\n", ssid, psk, hostname);

    wifi_state = WiFiConnectionState::WAITING;

    WiFi.mode(WIFI_STA);
    WiFi.hostname(hostname);
    WiFi.begin(ssid, psk);

    PRINT("connecting to wifi..");
}

#ifndef CONFIG_TARGET_ESP01
static void wifiHotspot() {
    led::mcu_led->on();

    auto scanResults = wifi::scan();

    WiFi.mode(WIFI_AP);
    WiFi.softAP(wifi::AP_SSID);

    dnsServer = new DNSServer();
    dnsServer->start(53, "*", WiFi.softAPIP());

    service = new HttpServer(scanResults);
    ((HttpServer*)service)->start();
}

static void waitForRecoveryPress() {
    pinMode(CONFIG_FLASH_GPIO, INPUT_PULLUP);
    for (uint16_t i = 0; i < recovery_boot_detection_ms; i += recovery_boot_delay_ms) {
        delay(recovery_boot_delay_ms);
        if (digitalRead(CONFIG_FLASH_GPIO) == LOW) {
            working_mode = WorkingMode::RECOVERY;
            break;
        }
    }
}
#endif


void setup() {
    WiFi.disconnect();
#ifndef CONFIG_TARGET_ESP01
    homekit::main::waitForRecoveryPress();
#endif

#ifdef DEBUG
    Serial.begin(115200);
#endif

    auto cfg = config::read();
    if (config::isDirty(cfg)) {
        PRINTLN("config is dirty, erasing...");
        config::erase(cfg);
#ifdef CONFIG_TARGET_NODEMCU
        led::board_led->blink(10, 50);
#else
        led::mcu_led->blink(10, 50);
#endif
    }

#ifndef CONFIG_TARGET_ESP01
    switch (working_mode) {
    case WorkingMode::RECOVERY:
        wifiHotspot();
        break;

    case WorkingMode::NORMAL:
#endif
        wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnected);
        wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnected);
        wifiConnect();
#ifndef CONFIG_TARGET_ESP01
        break;
    }
#endif
}

void loop(LoopConfig* config) {
#ifndef CONFIG_TARGET_ESP01
    if (working_mode == WorkingMode::NORMAL) {
#endif
        if (wifi_state == WiFiConnectionState::WAITING) {
            PRINT(".");
            led::mcu_led->blink(2, 50);
            delay(1000);
            return;
        }

        if (wifi_state == WiFiConnectionState::JUST_CONNECTED) {
#ifdef CONFIG_TARGET_NODEMCU
            led::board_led->blink(3, 300);
#else
            led::mcu_led->blink(3, 300);
#endif
            wifi_state = WiFiConnectionState::CONNECTED;

            if (service == nullptr) {
                service = new mqtt::Mqtt();
                mqttDiagModule = new mqtt::MqttDiagnosticsModule();
                mqttOtaModule = new mqtt::MqttOtaModule();

                ((mqtt::Mqtt*)service)->addModule(mqttDiagModule);
                ((mqtt::Mqtt*)service)->addModule(mqttOtaModule);

                if (config != nullptr)
                    config->onMqttCreated(*(mqtt::Mqtt*)service);
            }

            ((mqtt::Mqtt*)service)->connect();
#if MQTT_BLINK
            blinkStopWatch.save();
#endif
        }

        auto mqtt = (mqtt::Mqtt*)service;
        if (static_cast<int>(wifi_state) >= 1 && mqtt != nullptr) {
            mqtt->loop();

            if (mqttOtaModule != nullptr && mqttOtaModule->isReadyToRestart()) {
                mqtt->disconnect();
            }

#if MQTT_BLINK
            // periodically blink board led
            if (blinkStopWatch.elapsed(5000)) {
#ifdef CONFIG_TARGET_NODEMCU
                board_led->blink(1, 10);
#endif
                blinkStopWatch.save();
            }
#endif
        }
#ifndef CONFIG_TARGET_ESP01
    } else {
        if (dnsServer != nullptr)
            dnsServer->processNextRequest();

        auto httpServer = (HttpServer*)service;
        if (httpServer != nullptr)
            httpServer->loop();
    }
#endif
}

static void onWifiConnected(const WiFiEventStationModeGotIP& event) {
    PRINTF("connected (%s)\n", WiFi.localIP().toString().c_str());
    wifi_state = WiFiConnectionState::JUST_CONNECTED;
}

static void onWifiDisconnected(const WiFiEventStationModeDisconnected& event) {
    PRINTLN("disconnected from wi-fi");
    wifi_state = WiFiConnectionState::WAITING;
    if (service != nullptr)
        ((mqtt::Mqtt*)service)->disconnect();
    wifiTimer.once(2, wifiConnect);
}

}