
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_lv_adapter.h"
#include "esp_lv_decoder.h"
#include "lvgl.h"
#include "Display.hpp"
#include "Touch.hpp"
#include "Gui.hpp"
#include "WidgetManager.hpp"
#include "Nvs.hpp"
#include "KnxWorker.hpp"
#include "Wifi.hpp"
#include "Hardware/Eth.hpp"
#include "WebServer.hpp"
#include "SdCard.hpp"
#include "Fonts.hpp"

#define TAG "App"

static void knx_task(void* arg) {
    KnxWorker* worker = static_cast<KnxWorker*>(arg);
    worker->init();
    while (true) {
        worker->loop();
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

// This is a simple wrapper for the application logic
class Application {
public:
    void init() {
        // Initialize hardware
        nvs.init();
        //Wifi::instance().init();
        Eth::instance().init();
        display.init();
        touch.init();

        // Initialize LVGL adapter
        esp_lv_adapter_config_t cfg = ESP_LV_ADAPTER_DEFAULT_CONFIG();
        cfg.stack_in_psram = true; // Use PSRAM for stack to save internal RAM
        cfg.task_stack_size = 48 * 1024;
        ESP_ERROR_CHECK(esp_lv_adapter_init(&cfg));

        // Register display
        esp_lv_adapter_display_config_t disp_cfg = ESP_LV_ADAPTER_DISPLAY_MIPI_DEFAULT_CONFIG(
            display.getPanelHandle(),
            NULL,
            800, // Horizontal resolution
            1280, // Vertical resolution
            ESP_LV_ADAPTER_ROTATE_90 // Rotation
        );
        disp_cfg.profile.buffer_height = 40; // Reduced to 10 (~25KB) to fit in Internal RAM
        disp_cfg.profile.use_psram = true;
        lv_disp_t* lv_display = esp_lv_adapter_register_display(&disp_cfg);
        assert(lv_display != NULL);

        // Register touch
        esp_lv_adapter_touch_config_t touch_cfg = ESP_LV_ADAPTER_TOUCH_DEFAULT_CONFIG(lv_display, touch.getTouchHandle());
        lv_indev_t* lv_touch = esp_lv_adapter_register_touch(&touch_cfg);
        assert(lv_touch != NULL);
        lv_indev_set_user_data(lv_touch, &touch); // Set 'this' Touch object as user data
        lv_indev_set_read_cb(lv_touch, Touch::lv_indev_read_cb); // Register the static callback
      
        ESP_ERROR_CHECK(esp_lv_adapter_start());

        // Test LVGL lock right after start
        ESP_LOGI(TAG, "Testing LVGL lock after adapter start...");
        vTaskDelay(pdMS_TO_TICKS(100));
        if (esp_lv_adapter_lock(1000) == ESP_OK) {
            ESP_LOGI(TAG, "LVGL lock OK after adapter start");
            esp_lv_adapter_unlock();
        } else {
            ESP_LOGE(TAG, "LVGL lock BLOCKED right after adapter start!");
        }

        ESP_LOGI(TAG, "INIT SUCCESS");
    }

    void run() {
        // Initialize SD card for web content and config storage
        if (!SdCard::instance().init()) {
            ESP_LOGW(TAG, "SD card not available, using defaults");
        }

        // Initialize hardware JPEG/PNG decoder for LVGL (uses ESP32-P4 hardware acceleration)
        ESP_LOGI(TAG, "INIT IMAGE DECODER");
        esp_lv_decoder_handle_t decoder_handle = NULL;
        esp_err_t dec_err = esp_lv_decoder_init(&decoder_handle);
        if (dec_err != ESP_OK) {
            ESP_LOGW(TAG, "Failed to init image decoder: %s", esp_err_to_name(dec_err));
        } else {
            ESP_LOGI(TAG, "Hardware image decoder initialized");
        }

        ESP_LOGI(TAG, "START KNX");
        BaseType_t knx_ok = xTaskCreatePinnedToCore(
            knx_task, "knx", 4096, &Gui::knxWorker, 5, nullptr, 1);
        if (knx_ok != pdPASS) {
            ESP_LOGE(TAG, "Failed to start KNX task");
        }
        ESP_LOGI(TAG, "START WEBSERVER");
        WebServer::instance().start();
        ESP_LOGI(TAG, "INIT FONTS");
        Fonts::init();
        ESP_LOGI(TAG, "CREATE GUI");
        gui.create();

        // Force full screen redraw to initialize DMA2D hardware resources at startup
        // This prevents lazy initialization during first button press which can cause freezes
        ESP_LOGI(TAG, "Warming up display hardware...");
        vTaskDelay(pdMS_TO_TICKS(100));
        if (esp_lv_adapter_lock(1000) == ESP_OK) {
            lv_obj_invalidate(lv_scr_act());
            lv_refr_now(lv_display_get_default());
            esp_lv_adapter_unlock();
        }
        vTaskDelay(pdMS_TO_TICKS(200)); // Let the refresh complete
        ESP_LOGI(TAG, "Display hardware ready");

        ESP_LOGI(TAG, "Application running");
        while (true) {
            vTaskDelay(pdMS_TO_TICKS(10));
        }
    }

private:
    Display display;
    Touch touch;
    Gui gui;
    Nvs nvs;
};

extern "C" void app_main(void)
{
    ESP_LOGI(TAG, "Starting Application");
    Application app;
    app.init();
    app.run();
}
