#include <Arduino.h>
#include <Wire.h>
#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include "pin_config.h"

#define EXAMPLE_LVGL_TICK_PERIOD_MS 2

static lv_disp_draw_buf_t gDrawBuf;
static lv_color_t *gBuf1 = nullptr;
static lv_color_t *gBuf2 = nullptr;

Arduino_DataBus *gBus = new Arduino_ESP32QSPI(
    LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
    gBus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);

static void lvglRounderCb(lv_disp_drv_t *dispDrv, lv_area_t *area) {
  LV_UNUSED(dispDrv);
  if (area->x1 % 2 != 0) area->x1--;
  if (area->y1 % 2 != 0) area->y1--;
  if (area->x2 % 2 == 0) area->x2++;
  if (area->y2 % 2 == 0) area->y2++;
}

static void lvglFlushCb(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *colorP) {
  uint32_t w = area->x2 - area->x1 + 1;
  uint32_t h = area->y2 - area->y1 + 1;
#if (LV_COLOR_16_SWAP != 0)
  gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#else
  gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&colorP->full, w, h);
#endif
  lv_disp_flush_ready(disp);
}

static void lvglTick(void *arg) {
  LV_UNUSED(arg);
  lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}

static void createUi() {
  lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0x0B1320), 0);
  lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0);

  lv_obj_t *title = lv_label_create(lv_scr_act());
  lv_label_set_text(title, "LVGL BASIC TEST");
  lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
  lv_obj_set_style_text_font(title, &lv_font_montserrat_22, 0);
  lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 18);

  lv_obj_t *subtitle = lv_label_create(lv_scr_act());
  lv_label_set_text(subtitle, "If this text is visible, LVGL path works.");
  lv_obj_set_width(subtitle, 420);
  lv_label_set_long_mode(subtitle, LV_LABEL_LONG_WRAP);
  lv_obj_set_style_text_color(subtitle, lv_color_hex(0xD5DCE5), 0);
  lv_obj_set_style_text_font(subtitle, &lv_font_montserrat_16, 0);
  lv_obj_align(subtitle, LV_ALIGN_TOP_MID, 0, 58);

  const char *labels[3] = {"Button One", "Second Button", "Bottom Action"};
  const lv_color_t colors[3] = {
      lv_color_hex(0x2B4C7E),
      lv_color_hex(0x2E8B57),
      lv_color_hex(0xB45F06),
  };
  const lv_coord_t ys[3] = {150, 236, 340};
  const lv_coord_t hs[3] = {58, 58, 64};

  for (int i = 0; i < 3; i++) {
    lv_obj_t *btn = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn, 360, hs[i]);
    lv_obj_align(btn, LV_ALIGN_TOP_MID, 0, ys[i]);
    lv_obj_set_style_radius(btn, 14, 0);
    lv_obj_set_style_bg_color(btn, colors[i], 0);
    lv_obj_set_style_border_width(btn, 2, 0);
    lv_obj_set_style_border_color(btn, lv_color_hex(0x7F96B0), 0);

    lv_obj_t *label = lv_label_create(btn);
    lv_label_set_text(label, labels[i]);
    lv_obj_set_style_text_font(label, &lv_font_montserrat_20, 0);
    lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0);
    lv_obj_center(label);
  }
}

void setup() {
  Serial.begin(115200);
  Wire.begin(IIC_SDA, IIC_SCL);

  gfx->begin();
  gBus->writeC8D8(0x36, 0xA0);
  gfx->setBrightness(220);

  lv_init();

  uint32_t screenWidth = gfx->width();
  uint32_t screenHeight = gfx->height();
  gBuf1 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
  gBuf2 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
  lv_disp_draw_buf_init(&gDrawBuf, gBuf1, gBuf2, screenWidth * screenHeight / 4);

  static lv_disp_drv_t dispDrv;
  lv_disp_drv_init(&dispDrv);
  dispDrv.hor_res = screenWidth;
  dispDrv.ver_res = screenHeight;
  dispDrv.flush_cb = lvglFlushCb;
  dispDrv.rounder_cb = lvglRounderCb;
  dispDrv.draw_buf = &gDrawBuf;
  lv_disp_drv_register(&dispDrv);

  const esp_timer_create_args_t lvglTickTimerArgs = {
      .callback = &lvglTick,
      .name = "lvgl_tick"};
  esp_timer_handle_t lvglTickTimer = nullptr;
  esp_timer_create(&lvglTickTimerArgs, &lvglTickTimer);
  esp_timer_start_periodic(lvglTickTimer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);

  createUi();
  Serial.println("LVGL basic test ready");
}

void loop() {
  lv_timer_handler();
  delay(5);
}
