#include <Arduino.h>
#include <FS.h>
#include <LittleFS.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// #include "freertos/FreeRTOSConfig.h"
#include "sdkconfig.h"
#include <HTTPClient.h>

#include <esp_system.h>
#include <esp_sleep.h>
#include "soc/ledc_reg.h"
#include "soc/ledc_struct.h"
#include "esp_adc_cal.h"
#include "esp_log.h"

#include <Wire.h>
#include <ArduinoJson.h>
#include <anyrtttl.h>
#include "system.h"
#include "JsonConstrain.h"
#include "common/fileSystem.h"
#include "my_board.h"
#include "global.h"
#include "my_wifi.h"
#include "my_buzzer.h"
#include "my_tsensor.h"
#include "my_buttons.h"
#include "PWM_Output.h"
#include "Ramp_Lights.h"
#include "BleServer.h"
#include "ATALights.h"
#include "OnEveryN.h"
#include "BLE_SP110E.h"
#include "BleSettings.h"

#define FREERTOs_DIAGNOSTICS 0
#define OLED_ENABLED 0
#define WIFI_ENABLED 0
#define STRIPS_ENABLED 1
#define LUMASTIK_ENABLED 0
#define PRINT_SYSTEM_STATUS 0

#if OLED_ENABLED
#include "my_oled.h"
#endif

#if WIFI_ENABLED
#include "my_wifi.h"
#endif

#if STRIPS_ENABLED
#include "led_strip.h"
#endif

#if LUMASTIK_ENABLED
#include "luma_master.h"
#endif

#if LUMASTIK_ENABLED
#define LumaCountReset (20000 / 25)
int LumaCountdown = LumaCountReset;
LUMA_PACKET lumaPacket;
#endif

static const char *tag = "main";
SYS_SETTINGS sys_settings;
PWM_Output *pwmOutputs[4];
RAMP_LIGHT *rampLight1;
RAMP_LIGHT *rampLight2;

bool UpgradeMode = false;

void Init_ADC(void);
float readBoardInputVoltage(void);
void setupLogLevels(esp_log_level_t logLevel);
void Get_Board_and_Booth_File_Paths(const char *, String &, String &);
void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath);
void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4]);
void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4]);

void checkLEDCChannels()
{
    for (int i = 0; i < 8; i++)
    {
        if (LEDC.channel_group[0].channel[i].conf0.val != 0 || LEDC.channel_group[1].channel[i].conf0.val != 0)
        {
            ESP_LOGD(tag, "Channel %d is configured\n", i);
        }
    }
}

#define Button1Pin 8
void setup()
{
    // Serial Port
    Serial.begin(115200);
    while (!Serial);

    // Initialize I2C Port for TSensor, ...
    Wire.begin(I2C_SDA1_Pin, I2C_SCL1_Pin);

    sys_settings.ledStripSettings[0] = &ledSettings[0];
    sys_settings.ledStripSettings[1] = &ledSettings[1];

    // Set Logging Levels
    // esp_log_level_t logLevel = ESP_LOG_INFO;
    // pinMode(Button1Pin, INPUT); // Button1
    // if(!digitalRead(Button1Pin)){
    //  logLevel = ESP_LOG_VERBOSE;
    //}
    // setupLogLevels(logLevel);
    setupLogLevels(ESP_LOG_INFO);

    // chip id, mac,
    print_chip_info();

    // Init LittleFS
    Init_File_System();

    // Print all system files
    if (digitalRead(sys_settings.boardPins.btn[0]) == LOW){
        printAllSystemFiles();
    }

    String board_file_path, booth_file_path;
    Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path);

    // Load Board Pins
    Load_Board_Pins(sys_settings.boardPins, board_file_path);

    // Set status pins off
    setStatusPin1(false);
    setStatusPin2(false);

    // Load Booth Settings
    Load_Booth_Settings(sys_settings, booth_file_path);

    // Load Wifi Settings
    Wifi_Load_Settings("/system/wifi.json");

    // config stat, relays, buttons
    Init_Board_Basic(sys_settings.boardPins);

    // Load tunes.json and initialize
    Init_Buzzer(sys_settings.boardPins.buzzer, "/system/tunes.json");

    // Initialize PWM Outputs
    Init_PWM_Outputs(sys_settings.boardPins.relay, sys_settings.pwmOutSettings);

    // activate button clicks
    Init_ButtonEvents(sys_settings.boardPins.btn);

    // Initialize Ramp Lights
    Init_Ramp_Lights(sys_settings.rampLightSettings, boardButtons, pwmOutputs);

    // Initialize ADC
    Init_ADC();

    // Initialize Temperature Sensor
    Init_TSensor(72);


    float val = readBoardInputVoltage();
    ESP_LOGI(tag, "Input Volage = %f", val);

    // Initialize BLE
    Load_BLE_Settings("/system/ble.json");
    if (digitalRead(sys_settings.boardPins.btn[0]) == LOW)
    {
        setStatusPin1(true);
        UpgradeMode = true;
        ESP_LOGW(tag, "Enabling BLE and Update Service");
        Init_BleServer(true, true);
        ESP_LOGW(tag, "Enabling Wifi AP and Client");
        Wifi_Init();
    }
    else
    {
        ESP_LOGI(tag, "Enabling BLE, No Update Service");
        Init_BleServer(true, false); // Dont start the Upgrade service
    }

    // If lightstick mode is enabled, start ble lightstick client task
    if (sys_settings.mode == BOOTH_MODE_STIK)
    {
        Init_BLE_LightStick_Client();
    }

    #if OLED_ENABLED
        // Init OLED
        Init_OLED(sys_settings.oledSettings.width, sys_settings.oledSettings.height, sys_settings.boardPins.oled_mosi, sys_settings.boardPins.oled_sck, sys_settings.boardPins.oled_dc, sys_settings.boardPins.oled_rst, sys_settings.boardPins.oled_cs);
    #endif

    #if STRIPS_ENABLED
        Init_Lights_Task();
    #endif

    Buzzer_Play_Tune(TUNE_BOOT, true, true);

    // TODO... Test if this is still necessary need to configure pin 0 for some reason
    // pinMode(0, INPUT); // button0/boot pin

    #if LUMASTIK_ENABLED
        Init_Luma_Master();
    #endif

    vTaskDelay(100);
    Lights_Control_Task_Resume();
}

void loop()
{
    // Button Scanning
    ON_EVERY_N_MILLISECONDS(10)
    {
        for (int i = 0; i < 3; i++)
        {
            if (boardButtons[i] != NULL)
            {
                boardButtons[i]->tick();
            }
        }
        /*
        {
            boardButtons[0]->tick();
        }
        if (boardButtons[1] != NULL)
        {
            boardButtons[1]->tick();
        }
        if (boardButtons[2] != NULL)
        {
            boardButtons[2]->tick();
        }
            */
    }

    // Temperature Monitor
    ON_EVERY_N_MILLISECONDS(5000)
    {
        static float boardTemperature;

        // Read temperature if the sensor is enabled
        if (sys_settings.tSensorSettings.enabled)
        {
            boardTemperature = tSensor->readTemperatureF();
            // ESP_LOGD(tag, "Board T: %F", boardTemperature);
        }

        // Fan Control
        if (sys_settings.tSensorSettings.enabled)
        {
            UpdateFanControl(boardTemperature, pwmOutputs[sys_settings.tSensorSettings.pwmIndex]);
        }
    }

    // Update Tune Playing
    //if (anyrtttl::nonblocking::isPlaying())
    //{
    //    anyrtttl::nonblocking::play();
    //}

    // Animation TestMode Timeout
    #if LEDS_ENABLED
        if (animStatus.EventTestCountdown)
        {
            if (--animStatus.EventTestCountdown == 0)
            {
                ESP_LOGD(tag, "Test Timeout trigger");
                PostLastNormalEvent();
                if (Strip1_Task_Handle)
                {
                    xTaskNotifyGive(Strip1_Task_Handle);
                } // trigger exit of animation loop
            }
        };
    #endif

    // Reboot requested
    #if WIFI_ENABLED
        if (RebootSystem)
        {
            if (--RebootSystem == 0)
            {
                for (int i = 0; i < 3; i++)
                {
    #if BUZZER_ENABLED
                    Buzzer_Play_Tune(TUNE_BEEP, false); // blocking
    #endif
                    vTaskDelay(200);
                }
                ESP_LOGW(tag, "Restarting...");
                vTaskDelay(200);
                ESP.restart();
            }
        }
    #endif

    // Toggle Status LED L2
    ON_EVERY_N_MILLISECONDS(500)
    {
        if (sys_settings.boardPins.stat[1] >= 0)
        {
            static bool ledState = false;
            // digitalWrite(sys_settings.boardPins.stat[1], ledState = !ledState);
            setStatusPin2(ledState = !ledState);
        }
    }

    // Upgrade Mode Tune
    if(UpgradeMode){
        ON_EVERY_N_MILLISECONDS(5000)
        {
            Buzzer_Play_Tune(TUNE_ACK, true, true);
        }
    }

    #if FREERTOs_DIAGNOSTICS
        ON_EVERY_N_MILLISECONDS(60000)
        {
            print_task_watermarks();
        }
    #endif

    // Turn off white light after timeout
    ON_EVERY_N_MILLISECONDS(100)
    {
        if(whiteTimeout > 0){
            whiteTimeout--;
            if(whiteTimeout == 0){
                Lights_Set_White(0);
            }
        }
    }

}

void setupLogLevels(esp_log_level_t logLevel)
{
    // Options: ESP_LOG_ERROR,ESP_LOG_WARN,ESP_LOG_INFO,ESP_LOG_DEBUG,ESP_LOG_VERBOSE
    esp_log_level_set("*", logLevel);
    esp_log_level_set("fs", logLevel);
    esp_log_level_set("main", logLevel);
    esp_log_level_set("board", logLevel);
    esp_log_level_set("ramp", logLevel);
    esp_log_level_set("button", logLevel);
    esp_log_level_set("buzzer", logLevel);
    esp_log_level_set("global", logLevel);
    esp_log_level_set("jsCon", logLevel);
    esp_log_level_set("ble", logLevel);
    esp_log_level_set("pwmout", logLevel);
    esp_log_level_set("wifi", logLevel);
    esp_log_level_set("stiks", logLevel);
    esp_log_level_set("oled", logLevel);
    esp_log_level_set("trx433", logLevel);
    esp_log_level_set("tsensor", logLevel);
}

// TODO Restore original setOutput code..
#define RELAY_RES 10
void Init_PWM_Outputs(int8_t (&pin)[4], PWM_OUT_SETTINGS (&pwmSettings)[4])
{
    for (int i = 0; i < 4; i++)
    {
        int chIndex = findUnusedLedcChannel();
        if (chIndex < 0)
        {
            ESP_LOGE(tag, "No available LEDC channel for PWM Output%d", i);
            continue;
        }
        pwmOutputs[i] = new PWM_Output(pin[i], chIndex, RELAY_RES, pwmSettings[i].freq, pwmSettings[i].max, false);
        pwmOutputs[i]->setOutput(pwmSettings[i].def);
        // pwmOutputs[i]->setOutput(5.0);
        ESP_LOGI(tag, "PWM Output%d: Pin=%d, Freq=%d, ch=%d", i, pin[i], pwmSettings[i].freq, chIndex);
    }
}

void Init_Ramp_Lights(RAMP_LIGHT_SETTINGS (&settings)[2], OneButton *(&btn)[3], PWM_Output *(&pwm)[4])
{
    if (settings[0].enabled)
    {
        rampLight1 = new RAMP_LIGHT(btn[settings[0].btnIndex], pwm[settings[0].pwmOutIndex], 5.0, 100.0, 1.5);
        ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 1, settings[0].btnIndex, settings[0].pwmOutIndex);
    }

    if (settings[1].enabled)
    {
        rampLight2 = new RAMP_LIGHT(btn[settings[1].btnIndex], pwm[settings[1].pwmOutIndex], 5.0, 100.0, 1.5);
        ESP_LOGD(tag, "RampLight%d: btn=%d, pwmIndex=%d", 2, settings[1].btnIndex, settings[1].pwmOutIndex);
    }
}

// Get the files that should be used to setup the system
void Get_Board_and_Booth_File_Paths(const char *sysPath, String &boardPath, String &boothPath)
{
    File file = LittleFS.open(sysPath);

    if (!file)
    {
        ESP_LOGE(tag, "Error opening %s...", sysPath);
        return;
    }

    JsonDocument doc;
    DeserializationError error = deserializeJson(doc, file);
    file.close();

    if (error)
    {
        ESP_LOGE(tag, "%s deserialize error!..", sysPath);
        return;
    }

    // get hardware version string
    boardPath = jsonConstrainString(tag, doc.as<JsonObject>(), "boardfile", "/cfg/boards/board15.json");
    boothPath = jsonConstrainString(tag, doc.as<JsonObject>(), "configfile", "/cfg/booths/custom.json");
}

void Load_Booth_Settings(SYS_SETTINGS &sys, const String &boothPath)
{
    File file = LittleFS.open(boothPath);
    if (!file)
    {
        ESP_LOGE(tag, "Error opening %s...", boothPath.c_str());
        return;
    }

    JsonDocument doc;
    DeserializationError error = deserializeJson(doc, file);
    file.close();

    if (error)
    {
        ESP_LOGE(tag, "%s deserialize error!..", boothPath.c_str());
        return;
    }

    // ********** Mode ***********
    String modeStr = jsonConstrainString(tag, doc.as<JsonObject>(), "mode", "booth");
    if (modeStr == "roamer")
    {
        sys_settings.mode = BOOTH_MODE_ROAMER;
    }
    else if (modeStr == "stik")
    {
        sys_settings.mode = BOOTH_MODE_STIK;
    }
    else
    {
        sys_settings.mode = BOOTH_MODE_NONE;
    }

    // ********** PWM Out ***********
    JsonArray pwmJsonArray = doc["pwmout"];
    if (!pwmJsonArray.isNull())
    {
        int pwmIndex = 0;
        for (JsonObject obj : pwmJsonArray)
        {
            if (pwmIndex >= 4)
                break;
            sys_settings.pwmOutSettings[pwmIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
            sys_settings.pwmOutSettings[pwmIndex].freq = jsonConstrain<int>(tag, obj, "freq", 100, 5000, 250);
            sys_settings.pwmOutSettings[pwmIndex].min = jsonConstrain<float>(tag, obj, "min", 0.0, 95.0, 0.0);
            sys_settings.pwmOutSettings[pwmIndex].max = jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
            sys_settings.pwmOutSettings[pwmIndex].def = jsonConstrain<float>(tag, obj, "default", 0.0, 100.0, 0.0);
            sys_settings.pwmOutSettings[pwmIndex].deltaRate = 1.0;
            // sys_settings.pwmOutSettings[pwmIndex]. = jsonConstrainBool(tag, obj, "vision", true);
            pwmIndex++;
        }
        ESP_LOGI(tag, "Loaded PWmOutput settings...");
    }
    else
    {
        ESP_LOGE(tag, "Error!, %s key: pwmout not found..", boothPath);
    }

    // ********** Ramp Lights ***********
    JsonArray rampJsonArray = doc["ramp-lights"];
    if (!rampJsonArray.isNull())
    {
        int rampIndex = 0;
        for (JsonObject obj : rampJsonArray)
        {
            if (rampIndex >= 2)
                break;
            sys_settings.rampLightSettings[rampIndex].enabled = jsonConstrainBool(tag, obj, "en", true);
            sys_settings.rampLightSettings[rampIndex].vision = jsonConstrainBool(tag, obj, "vision", true);
            sys_settings.rampLightSettings[rampIndex].pwmOutIndex = jsonConstrain<int>(tag, obj, "relay-index", 0, 1, 0);
            sys_settings.rampLightSettings[rampIndex].btnIndex = jsonConstrain<int>(tag, obj, "button-index", 0, 1, 0);
            sys_settings.rampLightSettings[rampIndex].min = jsonConstrain<float>(tag, obj, "min", 0.0, 100.0, 0.0);
            sys_settings.rampLightSettings[rampIndex].max = jsonConstrain<float>(tag, obj, "max", 5.0, 100.0, 100.0);
            sys_settings.rampLightSettings[rampIndex].step = jsonConstrain<float>(tag, obj, "step", 0.01, 100.0, 1.5);
            rampIndex++;
        }
        ESP_LOGI(tag, "Loaded Ramp Lights settings...");
    }
    else
    {
        ESP_LOGE(tag, "Error!, %s key: ramp-lights not found..", boothPath);
    }

    // ********** Fan ***********
    JsonObject sensorJson = doc["t-sensor"];
    if (!sensorJson.isNull())
    {
        sys_settings.tSensorSettings.enabled = jsonConstrainBool(tag, sensorJson, "en", true);
        sys_settings.tSensorSettings.pwmIndex = jsonConstrain<int>(tag, sensorJson, "relay", 0, 3, 3);
        sys_settings.tSensorSettings.setpoint1 = jsonConstrain<float>(tag, sensorJson, "sp1", 0.0, 100.0, 80.0);
        sys_settings.tSensorSettings.setpoint2 = jsonConstrain<float>(tag, sensorJson, "sp2", 0.0, 100.0, 85.0);
        sys_settings.tSensorSettings.fanPower1 = jsonConstrain<float>(tag, sensorJson, "fan-pwr1", 0.0, 100.0, 50.0);
        sys_settings.tSensorSettings.fanPower2 = jsonConstrain<float>(tag, sensorJson, "fan-pwr2", 0.0, 100.0, 50.0);
        sys_settings.tSensorSettings.hyst = jsonConstrain<float>(tag, sensorJson, "hyst", 1.0, 10.0, 1.0);
        sys_settings.tSensorSettings.intervalMs = jsonConstrain<int>(tag, sensorJson, "interval", 1000, 30000, 5000);
        ESP_LOGI(tag, "Loaded TSensor settings...");
        ESP_LOGD(tag, "     SP1: %F, SP2 %F, Hyst: %F", sys_settings.tSensorSettings.setpoint1, sys_settings.tSensorSettings.setpoint2, sys_settings.tSensorSettings.hyst);
    }
    else
    {
        ESP_LOGE(tag, "Error!, %s key: t-sensor not found..", boothPath);
    }

    // ********** RGB Strips ***********
    JsonArray stripsJsonArray = doc["strips"];
    if (!stripsJsonArray.isNull())
    {
        int stripIndex = 0;
        for (JsonObject obj : stripsJsonArray)
        {
            if (stripIndex >= 2)
                break;
            sys_settings.ledStripSettings[stripIndex]->enabled = jsonConstrainBool(tag, obj, "en", true);
            sys_settings.ledStripSettings[stripIndex]->size = jsonConstrain<int>(tag, obj, "size", 1, 250, 25);
            sys_settings.ledStripSettings[stripIndex]->chip = jsonConstrainString(tag, obj, "chip", "WS2812B");
            sys_settings.ledStripSettings[stripIndex]->rgbOrder = jsonConstrainString(tag, obj, "rgb-order", "WS2812B");
            sys_settings.ledStripSettings[stripIndex]->shift = jsonConstrain<int>(tag, obj, "shift", -250, 250, 0);
            sys_settings.ledStripSettings[stripIndex]->offset = jsonConstrain<int>(tag, obj, "offset", -250, 250, 0);
            sys_settings.ledStripSettings[stripIndex]->bright = jsonConstrain<int>(tag, obj, "bright", 5, 255, 200);
            sys_settings.ledStripSettings[stripIndex]->powerDiv = 0;
            sys_settings.ledStripSettings[stripIndex]->i2sCh = 0;
            sys_settings.ledStripSettings[stripIndex]->core = jsonConstrain<int>(tag, obj, "core", 0, 1, 0);
            stripIndex++;
        }

        sys_settings.ledStripSettings[0]->pin = sys_settings.boardPins.rgb1;
        sys_settings.ledStripSettings[1]->pin = sys_settings.boardPins.rgb2;
        ESP_LOGI(tag, "Loaded LED Strip settings...");
    }
    else
    {
        ESP_LOGE(tag, "Error!, %s key: strips not found..");
    }

    // ********** BLE ***********
    JsonObject bleJson = doc["ble"];
    if (!bleJson.isNull())
    {
        sys_settings.bleSettings.enabled = jsonConstrainBool(tag, bleJson, "en", true);
        sys_settings.bleSettings.name = jsonConstrainString(tag, bleJson, "name", "ATA_LIGHTS");

        ESP_LOGI(tag, "Loaded BLE settings...");
    }
    else
    {
        ESP_LOGE(tag, "Error!, %s key: ble not found..", boothPath);
    }

    // ********** RF Remote***********
    /*
    JsonObject rfJson = doc["wifi-ap"];
    if(!rfJson.isNull()){
      //sys_settings.rampLights[0].enabled = jsonConstrainBool(tag, wifiApJson, "en", true);
      ESP_LOGI(tag, "Loaded RF Remote settings...");
    }else{
      ESP_LOGE(tag, "Error!, %s key: wifi-ap not found..", boothPath);
    }
    */
}

void Init_ADC(void)
{
    // Configure ADC
    analogReadResolution(12); // 12-bit ADC
    analogSetAttenuation(ADC_11db);
    if (sys_settings.boardPins.adc1 >= 0)
    {
        analogSetPinAttenuation(sys_settings.boardPins.adc1, ADC_11db);
    }

    // Enable ADC calibration
    esp_adc_cal_characteristics_t adc_chars;
    esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, &adc_chars);

    // Check calibration success
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK)
    {
        ESP_LOGI(tag, "ADC calibration: Using Two Point values from eFuse");
    }
    else if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK)
    {
        ESP_LOGI(tag, "ADC calibration: Using reference voltage from eFuse");
    }
    else
    {
        ESP_LOGW(tag, "ADC calibration: Using default reference voltage");
    }
}

float readBoardInputVoltage(void)
{
    const int SAMPLES = 64;
    uint32_t reading = 0;

    if (sys_settings.boardPins.adc1 < 0)
    {
        ESP_LOGE(tag, "ADC Pin not valid");
        return 0.0;
    }

    // Multiple readings for averaging
    for (int i = 0; i < SAMPLES; i++)
    {
        reading += analogRead(sys_settings.boardPins.adc1);
        delayMicroseconds(50); // Small delay between samples
    }
    reading /= SAMPLES;

    // Convert raw ADC to voltage using calibration
    uint32_t voltage_mv;
    esp_adc_cal_characteristics_t adc_chars;
    esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars);
    voltage_mv = esp_adc_cal_raw_to_voltage(reading, &adc_chars);

    // Scale to 12V range
    float voltage = (voltage_mv / 1000.0f) * (10470.0f / 470.0f);

    return voltage;
}