/*
   Inkplate4TEMPERA_APDS9960_LVGL example for Soldered Inkplate 4 TEMPERA

   This example shows how to read gestures, proximity, color and
   ambient light from the built-in APDS9960 sensor and display the
   results using LVGL over a converted LVGL v9 background image.

   IMPORTANT:
   - Convert your image using LVGL v9 converter
   - Put generated background.c into the same sketch folder
   - Do NOT include background.c manually
   - Just declare the symbol with:
       extern const lv_image_dsc_t background;
*/

// Next 3 lines are a precaution, you can ignore those, and the example would also work without them
#ifndef ARDUINO_INKPLATE4TEMPERA
#error "Wrong board selection for this example, please select Inkplate 4 TEMPERA in the boards menu."
#endif

#include <Inkplate-LVGL.h>

// This symbol comes from background.c generated by LVGL v9 image converter
extern const lv_image_dsc_t background;

// Inkplate in 1-bit mode
Inkplate inkplate(INKPLATE_1BIT);

// Display update settings
#define NUM_UPDATES_BEFORE_FULL_REFRESH 30

// Variables to help control program flow
bool dataHasChanged = true;
int numUpdates = 0;

// Remember last read values from the sensor
uint8_t lastProximityMeasured = 0;
uint16_t lastRedLightMeasured = 0;
uint16_t lastGreenLightMeasured = 0;
uint16_t lastBlueLightMeasured = 0;
uint16_t lastAmbientLightMeasured = 0;
String lastGestureMeasured = "";

// LVGL objects
static lv_obj_t *img_bg = nullptr;
static lv_obj_t *lbl_proximity = nullptr;
static lv_obj_t *lbl_gesture = nullptr;
static lv_obj_t *lbl_color = nullptr;
static lv_obj_t *lbl_ambient = nullptr;
static lv_obj_t *lbl_error = nullptr;

// Forward declarations
void createUI();
void updateDisplay();
void readProximity();
void detectGesture();
void readColor();
void readLight();
void refreshLVGL();

void refreshLVGL()
{
    lv_tick_inc(20);
    lv_timer_handler();
}

void setup()
{
    Serial.begin(115200);
    delay(200);

    inkplate.begin(LV_DISPLAY_RENDER_MODE_PARTIAL);
    inkplate.selectDisplayMode(INKPLATE_1BIT);

    createUI();

    // Wake APDS9960 peripheral
    inkplate.wakePeripheral(INKPLATE_APDS9960);

    // Initialize APDS9960
    if (!inkplate.apds9960.init())
    {
        lv_label_set_text(lbl_error, "Can't init APDS!");
        refreshLVGL();
        inkplate.display();
        esp_deep_sleep_start();
    }

    // Enable proximity sensor
    inkplate.apds9960.enableProximitySensor(false);
    inkplate.apds9960.setProximityGain(1);

    // Enable gesture sensor
    inkplate.apds9960.enableGestureSensor();
    inkplate.apds9960.setGestureGain(0);

    // Enable ambient/color light sensor
    inkplate.apds9960.enableLightSensor(false);

    // Initial full refresh
    refreshLVGL();
    inkplate.display();
}

void loop()
{
    readProximity();
    detectGesture();
    readColor();
    readLight();

    if (dataHasChanged)
    {
        updateDisplay();
    }

    delay(250);
}

void createUI()
{
    lv_obj_clean(lv_screen_active());

    // Set white background for the whole screen
    lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0xFFFFFF), LV_PART_MAIN);
    lv_obj_set_style_bg_opa(lv_screen_active(), LV_OPA_COVER, LV_PART_MAIN);

    // Background image
    img_bg = lv_image_create(lv_screen_active());
    lv_image_set_src(img_bg, &background);
    lv_obj_align(img_bg, LV_ALIGN_TOP_LEFT, 0, 0);

    // Proximity value
    lbl_proximity = lv_label_create(lv_screen_active());
    lv_obj_set_style_text_color(lbl_proximity, lv_color_hex(0x000000), 0);
    lv_obj_set_style_text_font(lbl_proximity, &lv_font_montserrat_32, 0);
    lv_label_set_text(lbl_proximity, "0");
    lv_obj_set_pos(lbl_proximity, 75, 95);

    // Gesture value
    lbl_gesture = lv_label_create(lv_screen_active());
    lv_obj_set_style_text_color(lbl_gesture, lv_color_hex(0x000000), 0);
    lv_obj_set_style_text_font(lbl_gesture, &lv_font_montserrat_32, 0);
    lv_label_set_text(lbl_gesture, "-");
    lv_obj_set_pos(lbl_gesture, 423, 95);

    // RGB values
    lbl_color = lv_label_create(lv_screen_active());
    lv_obj_set_style_text_color(lbl_color, lv_color_hex(0x000000), 0);
    lv_obj_set_style_text_font(lbl_color, &lv_font_montserrat_24, 0);
    lv_label_set_long_mode(lbl_color, LV_LABEL_LONG_WRAP);
    lv_obj_set_width(lbl_color, 240);
    lv_label_set_text(lbl_color, "Red: 0\nGreen: 0\nBlue: 0");
    lv_obj_set_pos(lbl_color, 32, 409);

    // Ambient light value
    lbl_ambient = lv_label_create(lv_screen_active());
    lv_obj_set_style_text_color(lbl_ambient, lv_color_hex(0x000000), 0);
    lv_obj_set_style_text_font(lbl_ambient, &lv_font_montserrat_32, 0);
    lv_label_set_text(lbl_ambient, "0");
    lv_obj_set_pos(lbl_ambient, 413, 425);

    // Error label
    lbl_error = lv_label_create(lv_screen_active());
    lv_obj_set_style_text_color(lbl_error, lv_color_hex(0x000000), 0);
    lv_obj_set_style_text_font(lbl_error, &lv_font_montserrat_20, 0);
    lv_label_set_text(lbl_error, "");
    lv_obj_set_pos(lbl_error, 20, 560);
}

void detectGesture()
{
    if (inkplate.apds9960.isGestureAvailable())
    {
        int gesture = inkplate.apds9960.readGesture();
        String gestureText = "";

        switch (gesture)
        {
            case DIR_UP:
                gestureText = "Up";
                break;
            case DIR_DOWN:
                gestureText = "Down";
                break;
            case DIR_LEFT:
                gestureText = "Left";
                break;
            case DIR_RIGHT:
                gestureText = "Right";
                break;
            default:
                break;
        }

        if (gestureText.length() > 0 && gestureText != lastGestureMeasured)
        {
            lv_label_set_text(lbl_gesture, gestureText.c_str());
            lastGestureMeasured = gestureText;
            dataHasChanged = true;
        }
    }
}

void readProximity()
{
    uint8_t proximity = 0;
    inkplate.apds9960.readProximity(proximity);

    if (proximity != lastProximityMeasured)
    {
        char buf[16];
        snprintf(buf, sizeof(buf), "%u", proximity);
        lv_label_set_text(lbl_proximity, buf);

        lastProximityMeasured = proximity;
        dataHasChanged = true;
    }
}

void readColor()
{
    uint16_t redLight = 0;
    uint16_t greenLight = 0;
    uint16_t blueLight = 0;

    inkplate.apds9960.readRedLight(redLight);
    inkplate.apds9960.readGreenLight(greenLight);
    inkplate.apds9960.readBlueLight(blueLight);

    if (redLight != lastRedLightMeasured ||
        greenLight != lastGreenLightMeasured ||
        blueLight != lastBlueLightMeasured)
    {
        char buf[96];
        snprintf(buf, sizeof(buf),
                 "Red: %u\nGreen: %u\nBlue: %u",
                 redLight, greenLight, blueLight);

        lv_label_set_text(lbl_color, buf);

        lastRedLightMeasured = redLight;
        lastGreenLightMeasured = greenLight;
        lastBlueLightMeasured = blueLight;
        dataHasChanged = true;
    }
}

void readLight()
{
    uint16_t ambientLight = 0;
    inkplate.apds9960.readAmbientLight(ambientLight);

    if (ambientLight != lastAmbientLightMeasured)
    {
        char buf[16];
        snprintf(buf, sizeof(buf), "%u", ambientLight);
        lv_label_set_text(lbl_ambient, buf);

        lastAmbientLightMeasured = ambientLight;
        dataHasChanged = true;
    }
}

void updateDisplay()
{
    refreshLVGL();

    if (numUpdates >= NUM_UPDATES_BEFORE_FULL_REFRESH)
    {
        inkplate.display();
        numUpdates = 0;
    }
    else
    {
        inkplate.partialUpdate(false, true);
        numUpdates++;
    }

    dataHasChanged = false;
}