#include "Display_ST77916.h"
#include "LVGL_Driver.h"
#include <math.h>

// BLE and OBD Libraries
#include <NimBLEDevice.h>
#include "ELMduino.h"

// ── Gauge Geometry ────────────────────────────────────────────────
#define CX          180
#define CY          180
#define SCREEN_W    360
#define SCREEN_H    360

#define ANGLE_START  120.0f   
#define ANGLE_END    380.0f   
#define GAUGE_MIN   -1.0f
#define GAUGE_MAX    3.0f

#define R_OUTER_TICK   172
#define R_INNER_ORANGE 150
#define R_TICK_MAJOR   150
#define R_TICK_MINOR   160
#define R_LABELS       125
#define R_NEEDLE_TIP   160
#define R_HUB          22

// -- Engine Load Position --
#define LOAD_W         100
#define LOAD_X         (CX - (LOAD_W / 2))
#define LOAD_Y         (CY - 70) 

// ── Shared State & Concurrency ────────────────────────────────────
SemaphoreHandle_t xDataMutex;

static float boost_target = 0.0f;
static float boost_current = -1.0f;
static float peak_boost = -1.0f;
static uint32_t peak_time = 0;

static float clt_temp = 0.0f; 
static float iat_temp = 0.0f; 
static float engine_load = 0.0f; 

static char pbuf[16] = "0.00 bar";
static char clt_buf[24] = "COOLANT: 0 °C";
static char iat_buf[24] = "INTAKE:   0 °C";

static bool startup_anim = true;
static int anim_stage = 0; 
static float anim_speed = 0.35f;
static int ble_status = 1; 

static lv_obj_t *face_obj;
static lv_obj_t *needle_obj;

// OBD Hardware
NimBLEClient* pClient = nullptr;
ELM327 myELM327;

// ── Helpers ───────────────────────────────────────────────────────
static float bar_to_rad(float bar) {
    float pct = (bar - GAUGE_MIN) / (GAUGE_MAX - GAUGE_MIN);
    float deg = ANGLE_START + pct * (ANGLE_END - ANGLE_START);
    return deg * (float)M_PI / 180.0f;
}

static lv_coord_t px(float r, float rad) { return (lv_coord_t)(CX + cosf(rad) * r); }
static lv_coord_t py(float r, float rad) { return (lv_coord_t)(CY + sinf(rad) * r); }

// ── Drawing Functions (Run on Core 1) ─────────────────────────────
static void draw_gauge_face(lv_event_t *e) {
    lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e);
    lv_point_t center = {CX, CY};

    // 1. Inner Orange Arc
    lv_draw_arc_dsc_t orange_arc;
    lv_draw_arc_dsc_init(&orange_arc);
    orange_arc.color = lv_color_make(0xFF, 0x44, 0x00);
    orange_arc.width = 2;
    lv_draw_arc(draw_ctx, &orange_arc, &center, R_INNER_ORANGE, (int)ANGLE_START, (int)ANGLE_END);

    // 2. Ticks
    for (int i = 0; i <= 40; i++) {
        float val = GAUGE_MIN + (i * 0.1f);
        float rad = bar_to_rad(val);
        bool is_major = (fmod(val + 0.01f, 0.5f) < 0.05f);
        lv_draw_line_dsc_t tick_dsc;
        lv_draw_line_dsc_init(&tick_dsc);
        tick_dsc.color = lv_color_white();
        tick_dsc.width = is_major ? 3 : 1;
        lv_point_t p_out = {px(R_OUTER_TICK, rad), py(R_OUTER_TICK, rad)};
        lv_point_t p_in  = {px(is_major ? R_TICK_MAJOR : R_TICK_MINOR, rad), py(is_major ? R_TICK_MAJOR : R_TICK_MINOR, rad)};
        lv_draw_line(draw_ctx, &tick_dsc, &p_in, &p_out);
    }

    // 3. Scale Labels
    lv_draw_label_dsc_t label_dsc;
    lv_draw_label_dsc_init(&label_dsc);
    label_dsc.color = lv_color_white();
    label_dsc.font = &lv_font_montserrat_14; 
    float labels[] = {-1.0, -0.5, 0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0};
    for (float l : labels) {
        float rad = bar_to_rad(l);
        char l_buf[8];
        sprintf(l_buf, "%g", (l < 0 ? fabsf(l) : l));
        int lx = px(R_LABELS, rad) - 15;
        int ly = py(R_LABELS, rad) - 10;
        lv_area_t area = {lx, ly, lx + 40, ly + 20};
        lv_draw_label(draw_ctx, &label_dsc, &area, l_buf, NULL);
    }

    // 4. ENGINE LOAD SECTION
    lv_draw_rect_dsc_t load_bg_dsc;
    lv_draw_rect_dsc_init(&load_bg_dsc);
    load_bg_dsc.bg_color = lv_color_make(0x1A, 0x1A, 0x1A);
    load_bg_dsc.radius = 2;
    lv_area_t load_bg_area = {LOAD_X, LOAD_Y, LOAD_X + LOAD_W, LOAD_Y + 5};
    lv_draw_rect(draw_ctx, &load_bg_dsc, &load_bg_area);

    lv_draw_rect_dsc_t load_fill_dsc;
    lv_draw_rect_dsc_init(&load_fill_dsc);
    load_fill_dsc.bg_color = lv_color_make(0x00, 0xFF, 0x00); 
    int fill_w = (int)((engine_load / 100.0f) * LOAD_W);
    if(fill_w > LOAD_W) fill_w = LOAD_W;
    lv_area_t load_fill_area = {LOAD_X, LOAD_Y, LOAD_X + fill_w, LOAD_Y + 5};
    lv_draw_rect(draw_ctx, &load_fill_dsc, &load_fill_area);

    lv_draw_label_dsc_t load_lbl_dsc;
    lv_draw_label_dsc_init(&load_lbl_dsc);
    load_lbl_dsc.color = lv_color_make(150, 150, 150);
    load_lbl_dsc.font = &lv_font_montserrat_10;
    lv_area_t load_txt_area = {LOAD_X, LOAD_Y + 8, LOAD_X + LOAD_W, LOAD_Y + 20};
    lv_draw_label(draw_ctx, &load_lbl_dsc, &load_txt_area, "ENGINE LOAD", NULL);

    // 5. DATA HUB
    lv_draw_rect_dsc_t box_dsc;
    lv_draw_rect_dsc_init(&box_dsc);
    box_dsc.bg_color = lv_color_make(0x1A, 0x1A, 0x1A);
    box_dsc.bg_opa = 200; 
    box_dsc.radius = 4;
    lv_area_t data_box = {CX - 35, CY + 55, CX + 115, CY + 145};
    lv_draw_rect(draw_ctx, &box_dsc, &data_box);

    lv_draw_rect_dsc_t accent_dsc;
    lv_draw_rect_dsc_init(&accent_dsc);
    accent_dsc.bg_color = lv_color_make(0xFF, 0x44, 0x00);
    accent_dsc.bg_opa = (ble_status != 2 && !startup_anim) ? (150 + 100 * sin(millis() / 200.0)) : 255;
    lv_area_t accent_bar = {CX - 33, CY + 60, CX - 30, CY + 140};
    lv_draw_rect(draw_ctx, &accent_dsc, &accent_bar);

    label_dsc.color = lv_color_make(180, 180, 180);
    label_dsc.font = &lv_font_montserrat_12;
    lv_area_t pk_label_area = {CX - 20, CY + 62, CX + 100, CY + 75};
    lv_draw_label(draw_ctx, &label_dsc, &pk_label_area, "PEAK BOOST", NULL);

    label_dsc.color = lv_color_make(0xFF, 0xAA, 0x00);
    label_dsc.font = &lv_font_montserrat_22;
    lv_area_t peak_val_area = {CX - 20, CY + 76, CX + 110, CY + 102};
    lv_draw_label(draw_ctx, &label_dsc, &peak_val_area, pbuf, NULL);

    lv_draw_line_dsc_t line_dsc;
    lv_draw_line_dsc_init(&line_dsc);
    line_dsc.color = lv_color_make(0x33, 0x33, 0x33);
    line_dsc.width = 1;
    lv_point_t lp1 = {CX - 20, CY + 105}, lp2 = {CX + 100, CY + 105};
    lv_draw_line(draw_ctx, &line_dsc, &lp1, &lp2);

    label_dsc.font = &lv_font_montserrat_14;
    label_dsc.color = lv_color_make(220, 220, 220);
    lv_area_t clt_area = {CX - 20, CY + 110, CX + 110, CY + 125};
    lv_draw_label(draw_ctx, &label_dsc, &clt_area, clt_buf, NULL);
    lv_area_t iat_area = {CX - 20, CY + 127, CX + 110, CY + 142};
    lv_draw_label(draw_ctx, &label_dsc, &iat_area, iat_buf, NULL);
}

static void draw_needle(lv_event_t *e) {
    lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e);
    float rad = bar_to_rad(boost_current);
    lv_draw_line_dsc_t needle_dsc;
    lv_draw_line_dsc_init(&needle_dsc);
    needle_dsc.color = lv_color_make(0xFF, 0x22, 0x00);
    needle_dsc.width = 5;
    needle_dsc.round_end = 1;
    lv_point_t p0 = {px(R_HUB - 5, rad), py(R_HUB - 5, rad)};
    lv_point_t p1 = {px(R_NEEDLE_TIP, rad), py(R_NEEDLE_TIP, rad)};
    lv_draw_line(draw_ctx, &needle_dsc, &p0, &p1);

    lv_draw_rect_dsc_t hub_dsc;
    lv_draw_rect_dsc_init(&hub_dsc);
    hub_dsc.bg_color = lv_color_make(0x11, 0x11, 0x11);
    hub_dsc.radius = LV_RADIUS_CIRCLE;
    hub_dsc.border_width = 2;
    hub_dsc.border_color = lv_color_make(0x66, 0x66, 0x66);
    lv_area_t hub_area = {CX - R_HUB, CY - R_HUB, CX + R_HUB, CY + R_HUB};
    lv_draw_rect(draw_ctx, &hub_dsc, &hub_area);
}

// ── OBD Communication (Core 0 Task) ───────────────────────────────
void connect_to_vlink() {
    NimBLEScan* pScan = NimBLEDevice::getScan();
    pScan->setActiveScan(true);
    pScan->start(1, false); 
    auto results = pScan->getResults();
    for (auto it = results.begin(); it != results.end(); ++it) {
        NimBLEAdvertisedDevice* device = *it;
        if (device->getName().find("V-LINK") != std::string::npos || device->getName().find("OBDII") != std::string::npos) {
            pClient = NimBLEDevice::createClient();
            if (pClient->connect(device)) {
                if (myELM327.begin(Serial1, false, 2000)) {
                    ble_status = 2; 
                    return;
                }
            }
        }
    }
}

void DriverTask(void *parameter) {
    while (1) {
        if (ble_status != 2) {
            connect_to_vlink();
            vTaskDelay(pdMS_TO_TICKS(1000));
        } else {
            // Poll hardware
            float t_clt = myELM327.engineCoolantTemp();
            float t_iat = myELM327.intakeAirTemp();
            float t_load = myELM327.engineLoad();
            float map_kpa = myELM327.manifoldPressure();

            // Safely write to shared variables
            if (xSemaphoreTake(xDataMutex, pdMS_TO_TICKS(20))) {
                if (myELM327.nb_rx_state == ELM_SUCCESS) {
                    clt_temp = t_clt;
                    iat_temp = t_iat;
                    engine_load = t_load;
                    boost_target = (map_kpa / 100.0f) - 1.01f;
                    
                    if (boost_target > peak_boost) {
                        peak_boost = boost_target;
                        peak_time = millis();
                    }
                    
                    sprintf(pbuf, "%.2f bar", peak_boost < 0 ? 0.00 : peak_boost);
                    sprintf(clt_buf, "COOLANT: %.0f °C", clt_temp);
                    sprintf(iat_buf, "INTAKE:   %.0f °C", iat_temp);
                }
                xSemaphoreGive(xDataMutex);
            }
            vTaskDelay(pdMS_TO_TICKS(20)); // High polling frequency
        }
    }
}

// ── Initialization ────────────────────────────────────────────────
void gauge_create(void) {
    lv_obj_t *scr = lv_scr_act();
    lv_obj_clean(scr); 
    lv_obj_set_style_bg_color(scr, lv_color_black(), 0);
    
    face_obj = lv_obj_create(scr);
    lv_obj_set_size(face_obj, SCREEN_W, SCREEN_H);
    lv_obj_set_style_bg_opa(face_obj, 0, 0);
    lv_obj_set_style_border_width(face_obj, 0, 0);
    lv_obj_add_event_cb(face_obj, draw_gauge_face, LV_EVENT_DRAW_MAIN, NULL);
    
    needle_obj = lv_obj_create(scr);
    lv_obj_set_size(needle_obj, SCREEN_W, SCREEN_H);
    lv_obj_set_style_bg_opa(needle_obj, 0, 0);
    lv_obj_set_style_border_width(needle_obj, 0, 0);
    lv_obj_add_event_cb(needle_obj, draw_needle, LV_EVENT_DRAW_MAIN, NULL);
}

void setup() {
    Serial.begin(115200);
    Serial1.begin(38400); 

    xDataMutex = xSemaphoreCreateMutex();

    TCA9554PWR_Init(0x00);
    Backlight_Init(); LCD_Init(); Lvgl_Init();
    NimBLEDevice::init("ESP32_Gauge");
    gauge_create();

    // Start Worker on Core 0
    xTaskCreatePinnedToCore(DriverTask, "DriverTask", 8192, NULL, 1, NULL, 0);
}

void loop() {
    static uint32_t last_face_update = 0;

    // 1. Logic & Smoothing (Core 1)
    if (startup_anim) {
        if (anim_stage == 0) {
            boost_current += anim_speed;
            if (boost_current >= GAUGE_MAX) anim_stage = 1;
        } else if (anim_stage == 1) {
            boost_current -= (boost_current * 0.07f);
            if (boost_current <= 0.01f) {
                boost_current = 0.0f;
                startup_anim = false;
            }
        }
    } else {
        // Apply smoothing to target provided by Core 0
        if (xSemaphoreTake(xDataMutex, 0)) {
            boost_current += (boost_target - boost_current) * 0.15f;
            if (millis() - peak_time > 10000) peak_boost = boost_current;
            xSemaphoreGive(xDataMutex);
        }
    }

    // 2. UI Refresh
    lv_obj_invalidate(needle_obj); 

    if (millis() - last_face_update > 100) {
        lv_obj_invalidate(face_obj);
        last_face_update = millis();
    }

    Lvgl_Loop();
    delay(16); // Target 60fps
}