#include <lvgl.h>
#include "Arduino_GFX_Library.h"
#include "pin_config.h"
#include "TouchDrvCSTXXX.hpp"
#include "lv_conf.h"
#include "HWCDC.h"
#include <Wire.h>

HWCDC USBSerial;

#define EXAMPLE_LVGL_TICK_PERIOD_MS 2

static lv_disp_draw_buf_t draw_buf;
static lv_color_t *buf1 = nullptr;
static lv_color_t *buf2 = nullptr;
static lv_obj_t *status_label = nullptr;
static uint32_t click_count = 0;

Arduino_DataBus *bus = new Arduino_ESP32QSPI(
  LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);

Arduino_CO5300 *gfx = new Arduino_CO5300(
  bus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);

TouchDrvCST92xx touch;
int16_t touch_x[5], touch_y[5];
bool isPressed = false;
uint32_t screenWidth = 0;
uint32_t screenHeight = 0;

#if LV_USE_LOG != 0
void my_print(const char *buf) {
  USBSerial.printf(buf);
  USBSerial.flush();
}
#endif

static void example_lvgl_rounder_cb(struct _lv_disp_drv_t *disp_drv, lv_area_t *area) {
  LV_UNUSED(disp_drv);
  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 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);

#if (LV_COLOR_16_SWAP != 0)
  gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#else
  gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
#endif

  lv_disp_flush_ready(disp);
}

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

static void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
  LV_UNUSED(indev_driver);
  if (isPressed) {
    uint8_t touched = touch.getPoint(touch_x, touch_y, touch.getSupportTouchPoint());
    if (touched) {
      isPressed = false;
      data->state = LV_INDEV_STATE_PR;
      data->point.x = touch_x[0];
      data->point.y = touch_y[0];
      USBSerial.print("Touch x=");
      USBSerial.print(touch_x[0]);
      USBSerial.print(" y=");
      USBSerial.println(touch_y[0]);
    } else {
      data->state = LV_INDEV_STATE_REL;
    }
  } else {
    data->state = LV_INDEV_STATE_REL;
  }
}

static void button_event_cb(lv_event_t *event) {
  if (lv_event_get_code(event) != LV_EVENT_CLICKED) {
    return;
  }

  const char *name = (const char *)lv_event_get_user_data(event);
  click_count++;
  String text = "Pressed: ";
  text += name;
  text += " (#";
  text += String(click_count);
  text += ")";
  lv_label_set_text(status_label, text.c_str());
  USBSerial.println(text);
}

static lv_obj_t *make_button(lv_obj_t *parent, const char *label_text, lv_coord_t x, lv_coord_t y, lv_coord_t w, lv_coord_t h, lv_color_t bg) {
  lv_obj_t *btn = lv_btn_create(parent);
  lv_obj_set_size(btn, w, h);
  lv_obj_set_pos(btn, x, y);
  lv_obj_set_style_radius(btn, 16, 0);
  lv_obj_set_style_bg_color(btn, bg, 0);
  lv_obj_set_style_border_width(btn, 2, 0);
  lv_obj_set_style_border_color(btn, lv_color_hex(0x8AA4BF), 0);
  lv_obj_set_style_shadow_width(btn, 0, 0);
  lv_obj_add_event_cb(btn, button_event_cb, LV_EVENT_CLICKED, (void *)label_text);

  lv_obj_t *label = lv_label_create(btn);
  lv_label_set_text(label, label_text);
  lv_obj_set_style_text_font(label, &lv_font_montserrat_18, 0);
  lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0);
  lv_obj_center(label);
  return btn;
}

static void create_ui() {
  lv_obj_t *screen = lv_scr_act();
  lv_obj_set_style_bg_color(screen, lv_color_hex(0x0B1320), 0);
  lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);

  lv_obj_t *title = lv_label_create(screen);
  lv_label_set_text(title, "LVGL OFFICIAL BASED TEST");
  lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
  lv_obj_set_style_text_color(title, lv_color_hex(0xFFFFFF), 0);
  lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 14);

  lv_obj_t *hint = lv_label_create(screen);
  lv_label_set_text(hint, "Based on official LVGL_Widgets init. Tap buttons and watch the status.");
  lv_obj_set_width(hint, 430);
  lv_label_set_long_mode(hint, LV_LABEL_LONG_WRAP);
  lv_obj_set_style_text_font(hint, &lv_font_montserrat_14, 0);
  lv_obj_set_style_text_color(hint, lv_color_hex(0xD4DDE7), 0);
  lv_obj_align(hint, LV_ALIGN_TOP_MID, 0, 44);

  make_button(screen, "Status",   22, 120, 136, 60, lv_color_hex(0x355C7D));
  make_button(screen, "Connect", 174, 120, 136, 60, lv_color_hex(0x2A9D8F));
  make_button(screen, "Wallet",  326, 120, 132, 60, lv_color_hex(0x457B9D));

  make_button(screen, "Requests", 22, 196, 136, 60, lv_color_hex(0xE76F51));
  make_button(screen, "Settings",174, 196, 136, 60, lv_color_hex(0x8D5A97));
  make_button(screen, "Register",326, 196, 132, 60, lv_color_hex(0x6A994E));

  make_button(screen, "Approve",  22, 272, 136, 60, lv_color_hex(0xBC6C25));
  make_button(screen, "Reject",  174, 272, 136, 60, lv_color_hex(0xB56576));
  make_button(screen, "Back",    326, 272, 132, 60, lv_color_hex(0x6C757D));

  lv_obj_t *bottom_btn = make_button(screen, "Bottom Action", 22, 350, 436, 64, lv_color_hex(0x1D6F42));
  lv_obj_set_style_text_font(lv_obj_get_child(bottom_btn, 0), &lv_font_montserrat_20, 0);

  lv_obj_t *status_panel = lv_obj_create(screen);
  lv_obj_set_size(status_panel, 436, 42);
  lv_obj_set_pos(status_panel, 22, 426);
  lv_obj_set_style_radius(status_panel, 12, 0);
  lv_obj_set_style_bg_color(status_panel, lv_color_hex(0x17263A), 0);
  lv_obj_set_style_border_width(status_panel, 2, 0);
  lv_obj_set_style_border_color(status_panel, lv_color_hex(0x496582), 0);
  lv_obj_set_style_pad_all(status_panel, 6, 0);

  status_label = lv_label_create(status_panel);
  lv_label_set_text(status_label, "Pressed: none");
  lv_obj_set_style_text_font(status_label, &lv_font_montserrat_16, 0);
  lv_obj_set_style_text_color(status_label, lv_color_hex(0xFFFFFF), 0);
  lv_obj_center(status_label);
}

void setup() {
  USBSerial.begin(115200);

  Wire.begin(IIC_SDA, IIC_SCL);

  digitalWrite(TP_RST, LOW);
  delay(30);
  digitalWrite(TP_RST, HIGH);
  delay(50);
  delay(1000);

  Wire.begin(IIC_SDA, IIC_SCL);

  touch.setPins(TP_RST, TP_INT);
  bool result = touch.begin(Wire, 0x5A, IIC_SDA, IIC_SCL);
  if (result == false) {
    USBSerial.println("touch is not online...");
    while (1) delay(1000);
  }
  USBSerial.print("Touch model: ");
  USBSerial.println(touch.getModelName());
  touch.sleep();
  touch.reset();
  touch.setMaxCoordinates(480, 480);
  touch.setSwapXY(true);
  touch.setMirrorXY(true, false);
  attachInterrupt(
    TP_INT, []() {
      isPressed = true;
    },
    FALLING);

  gfx->begin();
  gfx->setBrightness(200);
  bus->writeC8D8(0x36, 0xA0);

  screenWidth = gfx->width();
  screenHeight = gfx->height();

  lv_init();

  buf1 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);
  buf2 = (lv_color_t *)heap_caps_malloc(screenWidth * screenHeight / 4 * sizeof(lv_color_t), MALLOC_CAP_DMA);

#if LV_USE_LOG != 0
  lv_log_register_print_cb(my_print);
#endif

  lv_disp_draw_buf_init(&draw_buf, buf1, buf2, screenWidth * screenHeight / 4);

  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.rounder_cb = example_lvgl_rounder_cb;
  disp_drv.draw_buf = &draw_buf;
  disp_drv.sw_rotate = 1;
  lv_disp_drv_register(&disp_drv);

  static lv_indev_drv_t indev_drv;
  lv_indev_drv_init(&indev_drv);
  indev_drv.type = LV_INDEV_TYPE_POINTER;
  indev_drv.read_cb = my_touchpad_read;
  lv_indev_drv_register(&indev_drv);

  const esp_timer_create_args_t lvgl_tick_timer_args = {
    .callback = &example_increase_lvgl_tick,
    .name = "lvgl_tick"
  };

  esp_timer_handle_t lvgl_tick_timer = NULL;
  esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer);
  esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);

  create_ui();
  USBSerial.println("LVGL official based test ready");
}

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