/* LVGL + SHT41 Temperature & Humidity Display
 * I2C: SDA=8, SCL=7
 * Display: ST7789 240x240 (1.54-inch)
 */

#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include <Adafruit_MCP23X17.h>
#include <Wire.h>

// Pin definitions
#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_PIN 8    // SHT41 I2C SDA
#define I2C_SCL_PIN 7    // SHT41 I2C SCL

// SHT41 Configuration
#define SHT41_ADDR_44 0x44
#define SHT41_ADDR_45 0x45
#define I2C_CLOCK_SPEED 10000
#define COMM_RETRY_COUNT 3
#define MEASURE_DELAY 200
// SHT41 Commands
#define SHT41_CMD_MEASURE_HIGH 0xFD
#define SHT41_CMD_SOFT_RESET 0x94

// Global objects
Adafruit_MCP23X17 mcp;
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);

// LVGL display buffer
uint32_t screenWidth, screenHeight, 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;

// UI elements (only SHT41 related)
lv_obj_t *label_title;
lv_obj_t *label_temp;  // SHT41 Temperature
lv_obj_t *label_hum;   // SHT41 Humidity
lv_obj_t *label_status;// Connection status
lv_timer_t *sht41_timer = NULL;

// SHT41 Data Structure
struct SHT41Data {
  float temperature;
  float humidity;
  bool isValid;
  uint8_t detectedAddr;
} sht41Data = {0, 0, false, 0};

/* -------------------------- SHT41 Core Functions -------------------------- */
// CRC8 Calculation for SHT41
uint8_t crc8(const uint8_t* data, uint8_t len) {
  const uint8_t polynomial = 0x31;
  uint8_t crc = 0xFF;
  for (uint8_t i = 0; i < len; i++) {
    crc ^= data[i];
    for (uint8_t j = 0; j < 8; j++) {
      crc = (crc & 0x80) ? (crc << 1) ^ polynomial : (crc << 1);
    }
  }
  return crc;
}

// I2C Address Scan for SHT41
void scanI2CAddresses() {
  sht41Data.detectedAddr = 0;
  Serial.println("\nScanning I2C bus for SHT41...");
  
  for (uint8_t addr = 0x40; addr < 0x48; addr++) {
    Wire.beginTransmission(addr);
    uint8_t ret = Wire.endTransmission();
    if (ret == 0) {
      Serial.printf("I2C device found at address 0x%02X\n", addr);
      sht41Data.detectedAddr = addr;
    }
  }
  
  if (sht41Data.detectedAddr == 0) {
    Serial.println("No SHT41-like device found on I2C bus!");
  } else {
    Serial.printf("Detected SHT41 address: 0x%02X\n", sht41Data.detectedAddr);
  }
}

// Reset SHT41 Sensor
bool resetSHT41(uint8_t addr) {
  Wire.beginTransmission(addr);
  Wire.write(SHT41_CMD_SOFT_RESET);
  uint8_t ret = Wire.endTransmission();
  if (ret == 0) {
    Serial.println("SHT41 reset successful");
    delay(500);
    return true;
  } else {
    Serial.printf("SHT41 reset failed (NACK: %d)\n", ret);
    return false;
  }
}

// Read SHT41 Temperature & Humidity
void readSHT41() {
  uint8_t dataBuffer[6];
  sht41Data.isValid = false;
  uint8_t targetAddr = (sht41Data.detectedAddr != 0) ? sht41Data.detectedAddr : SHT41_ADDR_44;

  // Try dual addresses if no address detected
  if (sht41Data.detectedAddr == 0) {
    Serial.println("\nTrying address 0x44...");
    if (!resetSHT41(SHT41_ADDR_44)) {
      Serial.println("Trying address 0x45...");
      targetAddr = SHT41_ADDR_45;
      if (!resetSHT41(targetAddr)) {
        Serial.println("Failed to reset SHT41 at both addresses");
        return;
      }
    }
  }

  // Send measurement command with retry
  bool cmdSent = false;
  for (int retry = 0; retry < COMM_RETRY_COUNT; retry++) {
    Wire.beginTransmission(targetAddr);
    Wire.write(SHT41_CMD_MEASURE_HIGH);
    uint8_t ret = Wire.endTransmission();
    
    if (ret == 0) {
      Serial.printf("SHT41 measure command sent (Addr: 0x%02X, Retry: %d)\n", targetAddr, retry);
      cmdSent = true;
      break;
    } else {
      Serial.printf("SHT41 retry %d: Command failed (Addr: 0x%02X, NACK: %d)\n", retry + 1, targetAddr, ret);
      delay(100);
    }
  }
  if (!cmdSent) return;

  // Wait for measurement completion
  delay(MEASURE_DELAY);

  // Read 6 bytes of data
  int readCount = Wire.requestFrom(targetAddr, 6);
  if (readCount != 6) {
    Serial.printf("SHT41 incomplete data (Received: %d/6 bytes)\n", readCount);
    return;
  }

  // Fill data buffer
  for (int i = 0; i < 6; i++) {
    dataBuffer[i] = Wire.read();
  }

  // CRC check
  uint8_t tempCrc = crc8(dataBuffer, 2);
  uint8_t humCrc = crc8(dataBuffer + 3, 2);
  if (tempCrc != dataBuffer[2] || humCrc != dataBuffer[5]) {
    Serial.println("SHT41 CRC check failed");
    return;
  }

  // Calculate temperature and humidity
  uint16_t tempRaw = (dataBuffer[0] << 8) | dataBuffer[1];
  uint16_t humRaw = (dataBuffer[3] << 8) | dataBuffer[4];
  sht41Data.temperature = -45.0 + 175.0 * (float)tempRaw / 65535.0;
  sht41Data.humidity = 0.0 + 100.0 * (float)humRaw / 65535.0;
  sht41Data.isValid = true;
}

/* -------------------------- LVGL Display Functions -------------------------- */
// LVGL display flushing callback
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
}

/* -------------------------- Sensor Initialization -------------------------- */
// Initialize I2C & SHT41
void init_sht41() {
  // Initialize I2C with pull-up
  pinMode(I2C_SDA_PIN, INPUT_PULLUP);
  pinMode(I2C_SCL_PIN, INPUT_PULLUP);
  delay(200);

  Wire.end();
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  Wire.setClock(I2C_CLOCK_SPEED);
  Wire.setTimeout(1000000);
  delay(300);
  Serial.printf("I2C initialized (Clock: %dkHz)\n", I2C_CLOCK_SPEED / 1000);

  // Scan for SHT41
  scanI2CAddresses();
  
  // Reset SHT41 if detected
  if (sht41Data.detectedAddr != 0) {
    resetSHT41(sht41Data.detectedAddr);
  }
}

/* -------------------------- UI Helper Functions -------------------------- */
// Convert float to string (fix LVGL %f issue)
void float_to_str(char *buf, float value, int decimals) {
  if (isnan(value) || !isfinite(value)) {
    strcpy(buf, "---.-");
    return;
  }
  
  if (value < 0) {
    *buf++ = '-';
    value = -value;
  }
  
  long integer_part = (long)value;
  long fractional_part = (long)((value - integer_part) * pow(10, decimals) + 0.5);
  
  sprintf(buf, "%ld.", integer_part);
  buf += strlen(buf);
  sprintf(buf, "%0*d", decimals, fractional_part);
}

/* -------------------------- SHT41 Data Update Callback -------------------------- */
static void sht41_data_callback(lv_timer_t *timer) {
  char buf[32];

  // Read SHT41 Data
  readSHT41();

  // Update Temperature Label
  if (sht41Data.isValid) {
    float_to_str(buf, sht41Data.temperature, 2);
    char temp_str[32];
    sprintf(temp_str, "Temperature: %s °C", buf);
    lv_label_set_text(label_temp, temp_str);
  } else {
    lv_label_set_text(label_temp, "Temperature: No Data");
  }

  // Update Humidity Label
  if (sht41Data.isValid) {
    float_to_str(buf, sht41Data.humidity, 2);
    char hum_str[32];
    sprintf(hum_str, "Humidity:    %s %%RH", buf);
    lv_label_set_text(label_hum, hum_str);
  } else {
    lv_label_set_text(label_hum, "Humidity:    No Data");
  }

  // Update Status Label
  if (sht41Data.isValid) {
    lv_label_set_text(label_status, "Status: Normal");
    lv_obj_set_style_text_color(label_status, lv_color_hex(0x00FF00), 0); // Green
  } else {
    lv_label_set_text(label_status, "Status: Error");
    lv_obj_set_style_text_color(label_status, lv_color_hex(0xFF0000), 0); // Red
  }

  // Serial output (only SHT41 data)
  Serial.println("\n==================== SHT41 Data ====================");
  if (sht41Data.isValid) {
    Serial.printf("Detected Address: 0x%02X\n", sht41Data.detectedAddr);
    Serial.printf("Temperature: %.2f °C\n", sht41Data.temperature);
    Serial.printf("Humidity:    %.2f %%RH\n", sht41Data.humidity);
  } else {
    Serial.println("Status: No Valid Data (SHT41 not responding)");
  }
  Serial.println("==================================================\n");
}

/* -------------------------- LVGL UI Initialization -------------------------- */
void lvgl_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);

  // Title style (bigger font)
  static lv_style_t title_style;
  lv_style_init(&title_style);
  lv_style_set_text_color(&title_style, lv_color_white());
  lv_style_set_text_font(&title_style, &lv_font_montserrat_16); // 16px title
  lv_style_set_text_align(&title_style, LV_TEXT_ALIGN_CENTER);

  // Data style (medium font)
  static lv_style_t data_style;
  lv_style_init(&data_style);
  lv_style_set_text_color(&data_style, lv_color_white());
  lv_style_set_text_font(&data_style, &lv_font_montserrat_14); // 14px data
  lv_style_set_text_align(&data_style, LV_TEXT_ALIGN_CENTER);

  // Status style
  static lv_style_t status_style;
  lv_style_init(&status_style);
  lv_style_set_text_font(&status_style, &lv_font_montserrat_12); // 12px status
  lv_style_set_text_align(&status_style, LV_TEXT_ALIGN_CENTER);

  // Title label (center top)
  label_title = lv_label_create(parent);
  lv_obj_add_style(label_title, &title_style, 0);
  lv_label_set_text(label_title, "SHT41 Sensor Monitor");
  lv_obj_align(label_title, LV_ALIGN_TOP_MID, 0, 20);

  // Temperature label (center)
  label_temp = lv_label_create(parent);
  lv_obj_add_style(label_temp, &data_style, 0);
  lv_label_set_text(label_temp, "Temperature: --.- °C");
  lv_obj_align(label_temp, LV_ALIGN_CENTER, 0, -20);

  // Humidity label (center)
  label_hum = lv_label_create(parent);
  lv_obj_add_style(label_hum, &data_style, 0);
  lv_label_set_text(label_hum, "Humidity:    --.- %%RH");
  lv_obj_align(label_hum, LV_ALIGN_CENTER, 0, 20);

  // Status label (bottom center)
  label_status = lv_label_create(parent);
  lv_obj_add_style(label_status, &status_style, 0);
  lv_label_set_text(label_status, "Status: Initializing");
  lv_obj_align(label_status, LV_ALIGN_BOTTOM_MID, 0, -20);

  // Create timer (1000ms interval = 1s update)
  sht41_timer = lv_timer_create(sht41_data_callback, 1000, NULL);
}

/* -------------------------- Main Setup & Loop -------------------------- */
void setup() {
  // Initialize serial
  Serial.begin(115200);
  Serial.println("=== SHT41 Temperature & Humidity Monitor ===");

  // Initialize I2C
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  
  // 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();
  
  // Initialize display
  if (!gfx->begin()) {
    Serial.println("Display initialization failed!");
    while (1);
  }
  gfx->fillScreen(RGB565_BLACK);

  // Turn on backlight
#ifdef GFX_BL
  pinMode(GFX_BL, OUTPUT);
  digitalWrite(GFX_BL, HIGH);
#endif

  // Initialize LVGL
  lv_init();
  screenWidth = LCD_HOR_RES;
  screenHeight = LCD_VER_RES;
  bufSize = screenWidth * 20; // Reduce buffer size to save memory

  // Allocate LVGL display buffers
  disp_draw_buf1 = (lv_color_t *)malloc(bufSize * sizeof(lv_color_t));
  disp_draw_buf2 = (lv_color_t *)malloc(bufSize * sizeof(lv_color_t));
  if (!disp_draw_buf1 || !disp_draw_buf2) {
    Serial.println("LVGL buffer allocation failed!");
    while (1);
  }
  lv_disp_draw_buf_init(&draw_buf, disp_draw_buf1, disp_draw_buf2, bufSize);

  // Register LVGL 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 SHT41
  init_sht41();

  // Initialize LVGL UI
  lvgl_ui_init(lv_scr_act());

  Serial.println("Setup complete - displaying SHT41 data");
}

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