#include <stdint.h>

// Base address for I2C hardware registers (ESP32-specific)
#define I2C_BASE_ADDR        0x3FF53000

// Register offsets
#define I2C_FIFO_REG         (I2C_BASE_ADDR + 0x100)
#define I2C_SCL_LOW_PERIOD   (I2C_BASE_ADDR + 0x0)
#define I2C_SCL_HIGH_PERIOD  (I2C_BASE_ADDR + 0x38)
#define I2C_CMD_REG(n)       (I2C_BASE_ADDR + 0x58 + ((n) * 4))
#define I2C_CTRL_REG         (I2C_BASE_ADDR + 0x04)
#define I2C_FIFO_CONF_REG    (I2C_BASE_ADDR + 0x18)
#define I2C_INT_CLR_REG      (I2C_BASE_ADDR + 0x24)
#define I2C_INT_RAW_REG      (I2C_BASE_ADDR + 0x20)
#define I2C_STATUS_REG       (I2C_BASE_ADDR + 0x2C)

#define I2C_ADDR             0x27  // LCD I2C address (adjust as per your LCD module)
#define WRITE_BIT            0x0
#define BIT(x)               (1 << (x))
#define LCD_BACKLIGHT        0x08 // Backlight control bit
#define LCD_ENABLE_BIT       0x04 // Enable pin control bit
#define LCD_COLUMNS          20   // Number of columns on the LCD
#define LCD_LINES            4    // Number of lines on the LCD

// Functions to access registers
#define REG_WRITE(addr, val) (*((volatile unsigned int *)(addr)) = (val))
#define REG_READ(addr)       (*((volatile unsigned int *)(addr)))

// Delay function
static void delay(int count) {
    while (count--) {
        asm volatile ("nop");
    }
}

// I2C Master Initialization
void i2c_init() {
    REG_WRITE(I2C_SCL_LOW_PERIOD, 40);  // Configure SCL low period
    REG_WRITE(I2C_SCL_HIGH_PERIOD, 40); // Configure SCL high period
    REG_WRITE(I2C_FIFO_CONF_REG, BIT(10)); // Configure FIFO
    REG_WRITE(I2C_INT_CLR_REG, 0xFFFFFFFF); // Clear interrupts
    REG_WRITE(I2C_CTRL_REG, BIT(4));       // Enable I2C master mode
}

// I2C Master Write Byte
void i2c_master_write_byte(uint8_t *data, uint8_t length) {
    // Clear any pending interrupts
    REG_WRITE(I2C_INT_CLR_REG, 0xFFFFFFFF);

    // Write address with write bit to FIFO
    REG_WRITE(I2C_FIFO_REG, (I2C_ADDR << 1) | WRITE_BIT);

    // Write data to FIFO
    for (uint8_t i = 0; i < length; i++) {
        REG_WRITE(I2C_FIFO_REG, data[i]);
    }

    // Configure I2C commands
    REG_WRITE(I2C_CMD_REG(0), (0x0 << 11));          // Start
    REG_WRITE(I2C_CMD_REG(1), (0x1 << 11) | (length & 0xFF)); // Write data
    REG_WRITE(I2C_CMD_REG(2), (0x3 << 11));          // Stop

    // Start the I2C transaction
    REG_WRITE(I2C_CTRL_REG, BIT(5));

    // Wait for the transaction to complete
    while (REG_READ(I2C_STATUS_REG) & BIT(9)) {
        delay(1000); // Simple delay loop
    }
}

// LCD Functions
void lcd_expanderWrite(uint8_t data) {
    uint8_t payload[1] = {data | LCD_BACKLIGHT};
    i2c_master_write_byte(payload, 1);
}

void lcd_pulseEnable(uint8_t data) {
    lcd_expanderWrite(data | LCD_ENABLE_BIT); // Set enable bit
    delay(1);
    lcd_expanderWrite(data & ~LCD_ENABLE_BIT); // Clear enable bit
    delay(5);
}

void lcd_write4Bits(uint8_t value) {
    lcd_expanderWrite(value);
    lcd_pulseEnable(value);
}

void lcd_send(uint8_t value, uint8_t mode) {
    uint8_t highNibble = value & 0xF0;
    uint8_t lowNibble = (value << 4) & 0xF0;
    lcd_write4Bits(highNibble | mode);
    lcd_write4Bits(lowNibble | mode);
}

void lcd_send_command(uint8_t command) {
    lcd_send(command, 0);
}

void lcd_send_data(uint8_t data) {
    lcd_send(data, 0x01);
}

void lcd_backlight() {
    lcd_expanderWrite(LCD_BACKLIGHT); // Turn on backlight
}

// Initialize the LCD
void lcd_init() {
    i2c_init();
    delay(50); // Wait for LCD to power up
    lcd_send_command(0x33); // Initialize sequence
    lcd_send_command(0x32); // Set to 4-bit mode
    lcd_send_command(0x28); // 2-line, 5x7 matrix
    lcd_send_command(0x0C); // Display on, cursor off
    lcd_send_command(0x06); // Increment cursor
    lcd_send_command(0x01); // Clear display
    delay(2);               // Wait for clear display command to complete
}

// Print a string on the LCD
void lcd_print(const char *str) {
    while (*str) {
        lcd_send_data(*str++);
    }
}

// Set cursor position
void lcd_set_cursor(uint8_t col, uint8_t row) {
    uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
    if (row >= LCD_LINES) row = LCD_LINES - 1; // Limit to 4 rows
    lcd_send_command(0x80 | (col + row_offsets[row]));
}

// Main Function
void setup() {
    lcd_init();
    lcd_backlight();

    // Display messages
    lcd_set_cursor(3, 0);
    lcd_print("Hello, world!");
    lcd_set_cursor(2, 1);
    lcd_print("Wokwi IoT Sim");
    lcd_set_cursor(5, 2);
    lcd_print("Simulator");
    lcd_set_cursor(7, 3);
    lcd_print("Enjoy!");
}

void loop() {
    // Infinite loop
}