#include <stdint.h>

#define I2C_BASE      0x40005400
#define RCC_BASE      0x40021000
#define GPIOB_BASE    0x50000400

// I2C Registers
#define I2C_CR1       (*((volatile uint32_t *)(I2C_BASE + 0x00)))
#define I2C_CR2       (*((volatile uint32_t *)(I2C_BASE + 0x04)))
#define I2C_TIMINGR   (*((volatile uint32_t *)(I2C_BASE + 0x10)))
#define I2C_TXDR      (*((volatile uint32_t *)(I2C_BASE + 0x28)))
#define I2C_RXDR      (*((volatile uint32_t *)(I2C_BASE + 0x24)))
#define I2C_ISR       (*((volatile uint32_t *)(I2C_BASE + 0x18)))
#define RCC_IOPENR    (*((volatile uint32_t *)(RCC_BASE + 0x34)))
#define RCC_APBENR1   (*((volatile uint32_t *)(RCC_BASE + 0x3C)))
#define GPIOB_MODER   (*((volatile uint32_t *)(GPIOB_BASE + 0x00)))
#define GPIOB_OTYPER  (*((volatile uint32_t *)(GPIOB_BASE + 0x04)))
#define GPIOB_OSPEEDR (*((volatile uint32_t *)(GPIOB_BASE + 0x08)))
#define GPIOB_PUPDR   (*((volatile uint32_t *)(GPIOB_BASE + 0x0C)))
#define GPIOB_AFRL    (*((volatile uint32_t *)(GPIOB_BASE + 0x20)))

// Macros for I2C configuration
#define I2C_CR1_PE        (1 << 0)     // Peripheral Enable
#define I2C_CR2_START     (1 << 13)    // Generate Start Condition
#define I2C_CR2_STOP      (1 << 14)    // Generate Stop Condition
#define I2C_ISR_TXE       (1 << 0)     // Transmit Data Register Empty
#define I2C_ISR_TXIS      (1 << 1)     // Transmit Interrupt Status
#define I2C_ISR_TC        (1 << 6)     // Transfer Complete
#define I2C_ISR_NACKF     (1 << 4)     // NACK flag

// LCD Configuration
#define I2C_ADDR          0x27         // I2C address for the LCD
#define LCD_BACKLIGHT     0x08         // Backlight bit
#define LCD_ENABLE_BIT    0x04         // Enable bit
#define LCD_COLUMNS       20           // Number of LCD columns
#define LCD_LINES         4            // Number of LCD lines

// Delay function
void delay_ms(uint32_t ms) {
    while (ms--) {
        volatile uint32_t i = 1000;
        while (i--);
    }
}

// GPIO Initialization for I2C
void GPIO_Init_I2C(void) {
    // Enable GPIOB clock
    RCC_IOPENR |= (1 << 1);

    // Configure PB8 (SCL) and PB9 (SDA) as Alternate Function mode
    GPIOB_MODER &= ~((0x3 << (8 * 2)) | (0x3 << (9 * 2))); // Clear MODER
    GPIOB_MODER |= (0x2 << (8 * 2)) | (0x2 << (9 * 2));    // Set AF mode (0b10)

    // Set open-drain output type
    GPIOB_OTYPER |= (1 << 8) | (1 << 9);

    // Set high speed
    GPIOB_OSPEEDR |= (0x3 << (8 * 2)) | (0x3 << (9 * 2));

    // Enable internal pull-up resistors
    GPIOB_PUPDR &= ~((0x3 << (8 * 2)) | (0x3 << (9 * 2)));
    GPIOB_PUPDR |= (0x1 << (8 * 2)) | (0x1 << (9 * 2));

    // Configure Alternate Function for PB8 and PB9 (AF4 for I2C)
    GPIOB_AFRL &= ~((0xF << ((8 - 8) * 4)) | (0xF << ((9 - 8) * 4)));
    GPIOB_AFRL |= (0x4 << ((8 - 8) * 4)) | (0x4 << ((9 - 8) * 4));
}

// I2C Initialization
void I2C_Init(void) {
    // Enable I2C1 clock
    RCC_APBENR1 |= (1 << 21);

    // Reset I2C
    I2C_CR1 &= ~I2C_CR1_PE; // Disable peripheral
    while (I2C_CR1 & I2C_CR1_PE); // Ensure PE = 0
    for (volatile int i = 0; i < 3; i++); // Wait for 3 APB clock cycles

    // Configure timing for 100kHz I2C (16 MHz system clock)
    I2C_TIMINGR |= (0x9<<0)|(0x3<<8)|(0x3<<16)|(0x3<<20)|(0x5<<28); // Adjust as needed

    // Enable I2C peripheral
    I2C_CR1 |= I2C_CR1_PE;
}

// I2C Start Condition
void I2C_Start(uint8_t slaveAddr, uint8_t numBytes, uint8_t direction) {
    // Wait for transfer to be ready
    while (I2C_ISR & I2C_ISR_BUSY);

    // Configure slave address, number of bytes, and direction
    I2C_CR2 = (slaveAddr << 1) |              // 7-bit slave address
              (numBytes << 16) |             // Number of bytes
              (direction << 10);            // Direction: 0 = Write, 1 = Read

    // Generate Start condition
    I2C_CR2 |= I2C_CR2_START;

    // Wait for the TXIS flag (ready to send data) or NACK flag
    while (!(I2C_ISR & (I2C_ISR_TXIS | I2C_ISR_NACKF))) {
        if (I2C_ISR & I2C_ISR_NACKF) {
            // Handle NACK error
            I2C_Stop();
            return;
        }
    }
}

// I2C Write Data
void I2C_Write(uint8_t data) {
    // Wait until TXIS is set (ready to transmit)
    while (!(I2C_ISR & I2C_ISR_TXIS));

    // Write data to TXDR
    I2C_TXDR = data;

    // Wait until TXE (Transmit Buffer Empty)
    while (!(I2C_ISR & I2C_ISR_TXE));
}

// I2C Stop Condition
void I2C_Stop(void) {
    // Generate STOP condition
    I2C_CR2 |= I2C_CR2_STOP;

    // Wait until the STOP flag is cleared
    while (I2C_CR2 & I2C_CR2_STOP);
}

// LCD Functions
void lcd_expanderWrite(uint8_t data) {
    I2C_Start(I2C_ADDR, 1, 0);
    I2C_Write(data | LCD_BACKLIGHT); // Write data with backlight
    I2C_Stop();
}

void lcd_pulseEnable(uint8_t data) {
    lcd_expanderWrite(data | LCD_ENABLE_BIT); // Enable pulse
    delay_ms(1);                              // Small delay
    lcd_expanderWrite(data & ~LCD_ENABLE_BIT); // Disable pulse
    delay_ms(1);                              // Small delay
}

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_sendCommand(uint8_t command) {
    lcd_send(command, 0); // Command mode
}

void lcd_sendData(uint8_t data) {
    lcd_send(data, 0x01); // Data mode
}

void lcd_init() {
    GPIO_Init_I2C();
    I2C_Init();
    delay_ms(50);                   // Wait for LCD to power up

    lcd_sendCommand(0x33);          // Initialize in 8-bit mode
    lcd_sendCommand(0x32);          // Switch to 4-bit mode
    lcd_sendCommand(0x28);          // 4-bit mode, 2-line, 5x8 dots
    lcd_sendCommand(0x0C);          // Display ON, cursor OFF
    lcd_sendCommand(0x06);          // Entry mode: Increment cursor
    lcd_sendCommand(0x01);          // Clear display
    delay_ms(2);
}

void lcd_setCursor(uint8_t col, uint8_t row) {
    uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
    lcd_sendCommand(0x80 | (col + row_offsets[row]));
}

void lcd_print(const char* str) {
    while (*str) {
        lcd_sendData(*str++);
    }
}

void lcd_backlight() {
    lcd_expanderWrite(0);
}

// Main Setup Function
void setup1() {
    lcd_init();
    lcd_backlight();

    lcd_setCursor(3, 0);
    lcd_print("STM32 I2C LCD");
}

// Main Function
int main(void) {
    setup1();
    while (1) {
        // Main loop does nothing in this example
    }
}