/*Using LVGL with Arduino requires some extra steps:
 *Be sure to read the docs here: https://docs.lvgl.io/master/get-started/platforms/arduino.html  */

#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include <Adafruit_MCP23X17.h>
#include <Wire.h>
#include "SensorPCF85063.hpp"

#define GFX_BL 6
#define SPI_MISO 11
#define SPI_MOSI 13
#define SPI_SCLK 12
#define LCD_CS 10
#define LCD_DC 16
#define LCD_RST 10
#define LCD_HOR_RES 240
#define LCD_VER_RES 240
#define I2C_SDA 8
#define I2C_SCL 7

Adafruit_MCP23X17 mcp;
SensorPCF85063 rtc;

Arduino_DataBus *bus = new Arduino_ESP32SPI(LCD_DC, LCD_CS, SPI_SCLK, SPI_MOSI, SPI_MISO);
Arduino_GFX *gfx = new Arduino_ST7789(bus, LCD_RST, 0, true, LCD_HOR_RES, LCD_VER_RES);

uint32_t screenWidth;
uint32_t screenHeight;
uint32_t bufSize;
lv_disp_draw_buf_t draw_buf;
lv_color_t *disp_draw_buf1;
lv_color_t *disp_draw_buf2;
lv_disp_drv_t disp_drv;

lv_obj_t *label_time;
lv_obj_t *label_data;
lv_obj_t *label_date;
lv_timer_t *pcf85063_timer = NULL;

// Weekday names (Chinese)
const char* weekday_names[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

// Function to calculate weekday
int calculateWeekday(int year, int month, int day) {
  if (month < 3) {
    month += 12;
    year--;
  }
  int k = year % 100;
  int j = year / 100;
  // Zeller's congruence formula: returns 0=Saturday, 1=Sunday, 2=Monday, ..., 6=Friday
  int weekday = (day + (13 * (month + 1)) / 5 + k + k / 4 + j / 4 - 2 * j);
  weekday = weekday % 7;
  if (weekday < 0) weekday += 7;  // Handle negative modulo
  // Convert to 0=Sunday, 1=Monday, 2=Tuesday, ..., 6=Saturday
  // Zeller: 0=Sat, 1=Sun, 2=Mon, 3=Tue, 4=Wed, 5=Thu, 6=Fri
  // Need:   0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu, 5=Fri, 6=Sat
  return (weekday + 6) % 7;
}

/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp_drv, 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_drv);
}

// LCD reset function
void lcd_reset(void) {
  mcp.digitalWrite(1, HIGH);
  delay(10);
  mcp.digitalWrite(1, LOW);
  delay(10);
  mcp.digitalWrite(1, HIGH);
  delay(50);  // Reduced delay for faster screen clearing
}

// Initialize PCF85063 RTC module
void pcf85063_init(void) {
  if (!rtc.begin(Wire)) {
    while (1) {
      Serial.println("Failed to find PCF85063 - check your wiring!");
      delay(1000);
    }
  }
  RTC_DateTime datetime = rtc.getDateTime();

  // Set initial time if year is earlier than 2025
  if (datetime.getYear() < 2025) {
    rtc.setDateTime(2025, 1, 1, 12, 0, 0);
  }
}

// Callback function for PCF85063 time update
static void pcf85063_callback(lv_timer_t *timer) {
  RTC_DateTime datetime = rtc.getDateTime();
  int year = datetime.getYear();
  int month = datetime.getMonth();
  int day = datetime.getDay();
  int hour = datetime.getHour();
  int minute = datetime.getMinute();
  int second = datetime.getSecond();
  int weekday = calculateWeekday(year, month, day);
  
  // Update time display
  lv_label_set_text_fmt(label_time, "%02d:%02d:%02d", hour, minute, second);
  
  // Update date display
  lv_label_set_text_fmt(label_data, "%04d-%02d-%02d", year, month, day);
  lv_label_set_text_fmt(label_date, "%s", weekday_names[weekday]);
  
  // Serial output
  Serial.printf("Time: %02d:%02d:%02d  Date: %04d-%02d-%02d Weekday%s\n", 
                hour, minute, second, year, month, day, weekday_names[weekday]);
}

// Initialize LVGL UI for PCF85063 time display
void lvgl_pcf85063_ui_init(lv_obj_t *parent) {
  // Set screen background to black
  lv_obj_set_style_bg_color(parent, lv_color_black(), 0);
  lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);
  
  // Create time label style
  static lv_style_t time_style;
  lv_style_init(&time_style);
  lv_style_set_text_color(&time_style, lv_color_white());
  lv_style_set_text_font(&time_style, &lv_font_montserrat_16);
  lv_style_set_text_align(&time_style, LV_TEXT_ALIGN_CENTER);
  lv_style_set_text_letter_space(&time_style, 2);  // Increase letter spacing
  
  // Create date label style
  static lv_style_t date_style;
  lv_style_init(&date_style);
  lv_style_set_text_color(&date_style, lv_color_make(100, 200, 255));  // Light blue
  lv_style_set_text_font(&date_style, &lv_font_montserrat_16);
  lv_style_set_text_align(&date_style, LV_TEXT_ALIGN_CENTER);
  
  // Create weekday label style
  static lv_style_t weekday_style;
  lv_style_init(&weekday_style);
  lv_style_set_text_color(&weekday_style, lv_color_make(255, 200, 100));  // Light yellow
  lv_style_set_text_font(&weekday_style, &lv_font_montserrat_16);
  lv_style_set_text_align(&weekday_style, LV_TEXT_ALIGN_CENTER);
  
  // Create title label style
  static lv_style_t title_style;
  lv_style_init(&title_style);
  lv_style_set_text_color(&title_style, lv_color_make(150, 150, 150));  // Gray
  lv_style_set_text_font(&title_style, &lv_font_montserrat_16);
  lv_style_set_text_align(&title_style, LV_TEXT_ALIGN_CENTER);
  
  // Create bottom info style
  static lv_style_t info_style;
  lv_style_init(&info_style);
  lv_style_set_text_color(&info_style, lv_color_make(100, 100, 100));  // Dark gray
  lv_style_set_text_font(&info_style, &lv_font_montserrat_12);
  lv_style_set_text_align(&info_style, LV_TEXT_ALIGN_CENTER);
  
  // Create title label
  lv_obj_t *title_label = lv_label_create(parent);
  lv_obj_add_style(title_label, &title_style, 0);
  lv_label_set_text(title_label, "RTC_Time");
  lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 10);
  lv_obj_set_width(title_label, LCD_HOR_RES - 20);
  
  // Create time label
  label_time = lv_label_create(parent);
  lv_obj_add_style(label_time, &time_style, 0);
  lv_label_set_text(label_time, "00:00:00");
  lv_obj_align(label_time, LV_ALIGN_CENTER, 0, -25);
  lv_obj_set_width(label_time, LCD_HOR_RES - 20);
  
  // Create date label
  label_data = lv_label_create(parent);
  lv_obj_add_style(label_data, &date_style, 0);
  lv_label_set_text(label_data, "2025-01-01");
  lv_obj_align(label_data, LV_ALIGN_CENTER, 0, 10);
  lv_obj_set_width(label_data, LCD_HOR_RES - 20);
  
  // Create weekday label
  label_date = lv_label_create(parent);
  lv_obj_add_style(label_date, &weekday_style, 0);
  lv_label_set_text(label_date, "Wednesday");
  lv_obj_align(label_date, LV_ALIGN_CENTER, 0, 45);
  lv_obj_set_width(label_date, LCD_HOR_RES - 20);
  
  // Create bottom info label
  lv_obj_t *info_label = lv_label_create(parent);
  lv_obj_add_style(info_label, &info_style, 0);
  lv_label_set_text(info_label, "PCF85063 | ST7789 240x240");
  lv_obj_align(info_label, LV_ALIGN_BOTTOM_MID, 0, -10);
  lv_obj_set_width(info_label, LCD_HOR_RES - 20);
  
  // Create timer to update display (1 second interval)
  pcf85063_timer = lv_timer_create(pcf85063_callback, 1000, NULL);
  lv_timer_ready(pcf85063_timer);  // Execute immediately once
}

void setup() {
  Serial.begin(115200);
  Wire.begin(I2C_SDA, I2C_SCL);
  
  pcf85063_init();
  
  // Initialize MCP23017 (for LCD reset)
  if (!mcp.begin_I2C(0x20)) {
    Serial.println("ERROR: MCP23017 initialization failed!");
  } else {
    Serial.println("MCP23017 initialized at address 0x20");
    // Configure LCD reset pin as output
    mcp.pinMode(1, OUTPUT);
  }
  
  lcd_reset();
  Serial.println("ST7789 1.54-inch screen RTC clock initialized");
  Serial.println("Note: This version has no touch function");
  
  // Initialize display
  if (!gfx->begin()) {
    Serial.println("gfx->begin() failed!");
  }
  
  gfx->fillScreen(RGB565_BLACK);
  
#ifdef GFX_BL
  pinMode(GFX_BL, OUTPUT);
  digitalWrite(GFX_BL, HIGH);
#endif
  
  lv_init();
  
  screenWidth = gfx->width();
  screenHeight = gfx->height();
  bufSize = screenWidth * 40;  // Buffer size, adjust as needed
  
  // Allocate display buffers
  disp_draw_buf1 = (lv_color_t *)heap_caps_malloc(bufSize * 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT);
  disp_draw_buf2 = (lv_color_t *)heap_caps_malloc(bufSize * 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT);
  
  lv_disp_draw_buf_init(&draw_buf, disp_draw_buf1, disp_draw_buf2, bufSize);
  
  // Initialize display driver
  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.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);
  
  // Initialize UI
  lvgl_pcf85063_ui_init(lv_scr_act());
  
  Serial.println("Setup done");
  Serial.printf("Screen resolution: %dx%d\n", screenWidth, screenHeight);
  Serial.println("1.54-inch screen driven by ST7789");
  Serial.println("No touch function, only displays RTC time");
}

void loop() {
  lv_timer_handler();  // Handle LVGL tasks
  delay(5);
}