// by mircemk April, 2026

#include <Arduino.h>
#include <lvgl.h>
#include <TFT_eSPI.h>
#include "ui.h"

#define SCREEN_WIDTH   480
#define SCREEN_HEIGHT  320

#define AUDIO_PIN      35

// --------------------------------------------------
// Peak LED settings
// --------------------------------------------------
#define PEAK_LED_PIN         21
#define PEAK_HOLD_MS         120
#define PEAK_ANGLE_THRESHOLD 270

// --------------------------------------------------
// Backlight PWM settings
// --------------------------------------------------
#define TFT_BL        27
#define BL_CHANNEL    0
#define BL_FREQ       5000
#define BL_RESOLUTION 8      // 0-255
#define BL_LEVEL      155    // <-- ova doteraj go za sekoj displej posebno

// --------------------------------------------------
// TFT + LVGL
// --------------------------------------------------
TFT_eSPI tft = TFT_eSPI();

static lv_disp_draw_buf_t draw_buf;
static lv_color_t *buf1 = nullptr;
static lv_color_t *buf2 = nullptr;

// --------------------------------------------------
// VU meter variables
// --------------------------------------------------
float smoothed_val = 0.01f;

int adcMin = 120;
int adcMax = 1000;

int16_t meterAngleMin = -400;
int16_t meterAngleMax =  400;

int16_t current_angle = 0;
uint32_t last_update = 0;
uint32_t peakTimer = 0;

// --------------------------------------------------
// LVGL flush
// --------------------------------------------------
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

    tft.startWrite();
    tft.setAddrWindow(area->x1, area->y1, w, h);
    tft.pushColors((uint16_t *)&color_p->full, w * h, true);
    tft.endWrite();

    lv_disp_flush_ready(disp);
}

// --------------------------------------------------
// Intro screen
// --------------------------------------------------
void showIntroText()
{
    tft.fillScreen(TFT_BLACK);
    tft.setTextDatum(MC_DATUM);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);

    tft.setTextFont(4);
    tft.drawCentreString("VU - Meter", SCREEN_WIDTH / 2, 95, 4);
    tft.drawCentreString("by", SCREEN_WIDTH / 2, 145, 4);
    tft.drawCentreString("mircemk", SCREEN_WIDTH / 2, 190, 4);
    delay(1800);
}

// --------------------------------------------------
// Read audio
// --------------------------------------------------
int readVuLevel()
{
    const int samples = 16;
    int minVal = 1700;
    int maxVal = 0;

    for (int i = 0; i < samples; i++) {
        int v = analogRead(AUDIO_PIN);
        if (v < minVal) minVal = v;
        if (v > maxVal) maxVal = v;
    }

    int peakToPeak = maxVal - minVal;

    const float attackCoeff  = 0.20f;
    const float releaseCoeff = 0.20f;

    if (peakToPeak > smoothed_val) {
        smoothed_val = peakToPeak * attackCoeff + smoothed_val * (1.0f - attackCoeff);
    } else {
        smoothed_val = peakToPeak * releaseCoeff + smoothed_val * (1.0f - releaseCoeff);
    }

    return (int)smoothed_val;
}

// --------------------------------------------------
// Setup needle
// --------------------------------------------------
void setupNeedle()
{
    if (ui_Image2 == NULL) return;

    const int needlePivotX = 6;
    const int needlePivotY = 300;
    const int needleX = 0;
    const int needleY = -10;

    lv_img_set_pivot(ui_Image2, needlePivotX, needlePivotY);
    lv_obj_set_pos(ui_Image2, needleX, needleY);
    lv_img_set_angle(ui_Image2, 0);

    Serial.printf("needle x=%d y=%d\n", lv_obj_get_x(ui_Image2), lv_obj_get_y(ui_Image2));
    Serial.printf("parent w=%d h=%d\n",
                  lv_obj_get_width(lv_obj_get_parent(ui_Image2)),
                  lv_obj_get_height(lv_obj_get_parent(ui_Image2)));
}

// --------------------------------------------------
// Arduino setup
// --------------------------------------------------
void setup()
{
    Serial.begin(115200);
    delay(300);

    analogReadResolution(12);
    pinMode(AUDIO_PIN, INPUT);

    pinMode(PEAK_LED_PIN, OUTPUT);
    digitalWrite(PEAK_LED_PIN, LOW);   // LED OFF



    tft.begin();
    tft.setRotation(1);
    tft.fillScreen(TFT_BLACK);

        // Backlight PWM
    ledcSetup(BL_CHANNEL, BL_FREQ, BL_RESOLUTION);
    ledcAttachPin(TFT_BL, BL_CHANNEL);
    ledcWrite(BL_CHANNEL, BL_LEVEL);

    showIntroText();

    lv_init();

    const uint32_t buf_pixels = SCREEN_WIDTH * 100;
    buf1 = (lv_color_t *)heap_caps_malloc(buf_pixels * sizeof(lv_color_t), MALLOC_CAP_DMA);
    buf2 = (lv_color_t *)heap_caps_malloc(buf_pixels * sizeof(lv_color_t), MALLOC_CAP_DMA);

    if (!buf1 || !buf2) {
        Serial.println("LVGL buffer allocation failed!");
        while (1) delay(100);
    }

    lv_disp_draw_buf_init(&draw_buf, buf1, buf2, buf_pixels);

    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = SCREEN_WIDTH;
    disp_drv.ver_res = SCREEN_HEIGHT;
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.draw_buf = &draw_buf;
    lv_disp_drv_register(&disp_drv);

    ui_init();
    setupNeedle();

    Serial.println("UI initialized.");
}

// --------------------------------------------------
// LOOP
// --------------------------------------------------
void loop()
{
    lv_timer_handler();

    if (millis() - last_update >= 20) {
        last_update = millis();

        int vuLevel = readVuLevel();

        int16_t target_angle = map(vuLevel, adcMin, adcMax, meterAngleMin, meterAngleMax);

        if (target_angle > meterAngleMax) target_angle = meterAngleMax;
        if (target_angle < meterAngleMin) target_angle = meterAngleMin;

        lv_img_set_angle(ui_Image2, target_angle);
        current_angle = target_angle;

        // Peak LED spored realnata pozicija na iglata
        if (current_angle >= PEAK_ANGLE_THRESHOLD) {
            digitalWrite(PEAK_LED_PIN, HIGH);   // LED ON
            peakTimer = millis();
        }
        else if (millis() - peakTimer > PEAK_HOLD_MS) {
            digitalWrite(PEAK_LED_PIN, LOW);    // LED OFF
        }
    }
}