#include <stdint.h>
#include <stdbool.h>
#include <string.h>

// TivaWare
#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/pin_map.h"
#include "driverlib/i2c.h"
#include "driverlib/uart.h"

// System Clock
uint32_t g_ui32SysClock;

// OLED Config
#define OLED_ADDR 0x3C
#define OLED_WIDTH 128
#define OLED_HEIGHT 64

uint8_t buffer[OLED_WIDTH * OLED_HEIGHT / 8];

//-------------------------------------
// UART0 INIT
//-------------------------------------
void UART0_Init(void)
{
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);

    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_UART0));

    GPIOPinConfigure(GPIO_PA0_U0RX);
    GPIOPinConfigure(GPIO_PA1_U0TX);

    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);

    UARTConfigSetExpClk(UART0_BASE, g_ui32SysClock, 115200,
                        (UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE));

    UARTEnable(UART0_BASE);
}

void UART_Print(char *str)
{
    while(*str)
    {
        UARTCharPut(UART0_BASE, *str++);
    }
}

//-------------------------------------
// Accurate millisecond delay
//-------------------------------------
void Delay_ms(uint32_t ms)
{
    SysCtlDelay((g_ui32SysClock / 3000) * ms);
}

//-------------------------------------
// I2C0 INIT - Using 100kHz for reliability
//-------------------------------------
void I2C0_Init(void)
{
    UART_Print("I2C Init...\r\n");

    // Enable clocks
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);

    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_I2C0));
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB));

    // Configure pins for I2C
    GPIOPinConfigure(GPIO_PB2_I2C0SCL);
    GPIOPinConfigure(GPIO_PB3_I2C0SDA);

    // Set pins as I2C with internal pull-ups
    GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
    GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);

    // Enable internal pull-ups
    GPIOPadConfigSet(GPIO_PORTB_BASE, GPIO_PIN_2 | GPIO_PIN_3,
                     GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);

    // Initialize I2C at 100kHz (false = 100kHz, true = 400kHz)
    // Use 100kHz for better reliability
    I2CMasterInitExpClk(I2C0_BASE, g_ui32SysClock, false);

    UART_Print("I2C Ready (100kHz)\r\n");
}

//-------------------------------------
// I2C Write with error checking
//-------------------------------------
bool I2C_Write(uint8_t addr, uint8_t *data, uint8_t len)
{
    uint8_t i;

    I2CMasterSlaveAddrSet(I2C0_BASE, addr, false);

    for(i = 0; i < len; i++)
    {
        I2CMasterDataPut(I2C0_BASE, data[i]);

        if(i == 0 && len == 1)
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
        else if(i == 0)
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_START);
        else if(i == len - 1)
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
        else
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_CONT);

        while(I2CMasterBusy(I2C0_BASE));

        if(I2CMasterErr(I2C0_BASE) != I2C_MASTER_ERR_NONE)
            return false;
    }
    return true;
}

//-------------------------------------
// OLED Command
//-------------------------------------
void OLED_Command(uint8_t cmd)
{
    uint8_t data[2] = {0x00, cmd};
    I2C_Write(OLED_ADDR, data, 2);
    Delay_ms(1);  // Small delay after each command
}

//-------------------------------------
// OLED Data
//-------------------------------------
void OLED_Data(uint8_t *data, uint8_t len)
{
    uint8_t temp[129];
    temp[0] = 0x40;
    memcpy(&temp[1], data, len);
    I2C_Write(OLED_ADDR, temp, len + 1);
}

//-------------------------------------
// OLED INIT - Following SSD1306 Datasheet exactly
//-------------------------------------
void OLED_Init(void)
{
    UART_Print("OLED Init...\r\n");

    // Wait for power to stabilize
    Delay_ms(100);

    // Step 1: Display OFF
    OLED_Command(0xAE);
    Delay_ms(10);

    // Step 2: Set Display Clock Divide Ratio / Oscillator Frequency
    OLED_Command(0xD5);
    OLED_Command(0x80);  // Suggested ratio

    // Step 3: Set Multiplex Ratio
    OLED_Command(0xA8);
    OLED_Command(0x3F);  // 64 lines

    // Step 4: Set Display Offset
    OLED_Command(0xD3);
    OLED_Command(0x00);  // No offset

    // Step 5: Set Start Line
    OLED_Command(0x40);  // Start at line 0

    // Step 6: Enable Charge Pump (CRITICAL!)
    OLED_Command(0x8D);
    OLED_Command(0x14);  // Enable charge pump
    Delay_ms(50);        // Wait for charge pump to stabilize

    // Step 7: Set Memory Addressing Mode
    OLED_Command(0x20);
    OLED_Command(0x00);  // Horizontal addressing mode

    // Step 8: Set Segment Re-map
    OLED_Command(0xA1);  // Column address 127 mapped to SEG0

    // Step 9: Set COM Output Scan Direction
    OLED_Command(0xC8);  // Scan from COM[N-1] to COM0

    // Step 10: Set COM Pins Hardware Configuration
    OLED_Command(0xDA);
    OLED_Command(0x12);  // Alternative COM pin configuration

    // Step 11: Set Contrast
    OLED_Command(0x81);
    OLED_Command(0xFF);  // Maximum contrast

    // Step 12: Set Pre-charge Period
    OLED_Command(0xD9);
    OLED_Command(0xF1);  // Phase1=15, Phase2=1

    // Step 13: Set VCOMH Deselect Level
    OLED_Command(0xDB);
    OLED_Command(0x40);  // 0.77 x VCC

    // Step 14: Entire Display ON (Resume to RAM content)
    OLED_Command(0xA4);

    // Step 15: Set Normal Display
    OLED_Command(0xA6);

    // Step 16: Clear screen
    OLED_Clear();

    // Step 17: Display ON
    OLED_Command(0xAF);

    Delay_ms(100);

    UART_Print("OLED Ready\r\n");
}

//-------------------------------------
// UPDATE DISPLAY
//-------------------------------------
void OLED_Update(void)
{
    uint8_t page;

    for(page = 0; page < 8; page++)
    {
        OLED_Command(0xB0 + page);  // Set page address
        OLED_Command(0x00);         // Lower column start address
        OLED_Command(0x10);         // Higher column start address
        OLED_Data(&buffer[128 * page], 128);
    }
}

//-------------------------------------
// CLEAR DISPLAY
//-------------------------------------
void OLED_Clear(void)
{
    memset(buffer, 0x00, sizeof(buffer));
    OLED_Update();
}

//-------------------------------------
// DRAW PIXEL
//-------------------------------------
void OLED_DrawPixel(uint8_t x, uint8_t y, bool color)
{
    if(x >= OLED_WIDTH || y >= OLED_HEIGHT) return;

    uint16_t index = x + ((y >> 3) * OLED_WIDTH);
    uint8_t bit = 1 << (y & 0x07);

    if(color)
        buffer[index] |= bit;
    else
        buffer[index] &= ~bit;
}

//-------------------------------------
// DRAW CHARACTER - Fixed for SSD1306
//-------------------------------------
void OLED_DrawChar(uint8_t x, uint8_t y, char ch)
{
    uint8_t i, j;

    // 8x8 font for P, A, V, N
    const uint8_t fontP[8] = {0x7E, 0x41, 0x41, 0x7E, 0x40, 0x40, 0x40, 0x40};
    const uint8_t fontA[8] = {0x3E, 0x09, 0x09, 0x3E, 0x48, 0x48, 0x48, 0x3E};
    const uint8_t fontV[8] = {0x1C, 0x22, 0x41, 0x41, 0x41, 0x22, 0x1C, 0x00};
    const uint8_t fontN[8] = {0x7F, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7F};

    const uint8_t* font;

    switch(ch)
    {
        case 'P': font = fontP; break;
        case 'A': font = fontA; break;
        case 'V': font = fontV; break;
        case 'N': font = fontN; break;
        default: return;
    }

    for(i = 0; i < 8; i++)
    {
        for(j = 0; j < 8; j++)
        {
            if(font[i] & (1 << j))
                OLED_DrawPixel(x + j, y + i, true);
        }
    }
}

//-------------------------------------
// DRAW STRING
//-------------------------------------
void OLED_DrawString(uint8_t x, uint8_t y, char *str)
{
    while(*str)
    {
        OLED_DrawChar(x, y, *str);
        x += 8;  // Move 8 pixels
        str++;
    }
}

//-------------------------------------
// DRAW "PAVAN" - Centered
//-------------------------------------
void OLED_DrawPAVAN(void)
{
    OLED_Clear();
    // Center "PAVAN" (5 chars * 8 = 40 pixels) at x=44, y=28
    OLED_DrawString(44, 28, "PAVAN");
    OLED_Update();
    UART_Print("PAVAN Drawn\r\n");
}

//-------------------------------------
// Test Pattern - Verify OLED is working
//-------------------------------------
void OLED_TestPattern(void)
{
    uint8_t i;

    UART_Print("Test Pattern...\r\n");

    // Fill screen with checkerboard
    for(i = 0; i < sizeof(buffer); i++)
    {
        buffer[i] = 0xAA;  // 10101010 pattern
    }
    OLED_Update();
    Delay_ms(2000);

    // Fill screen with inverse checkerboard
    for(i = 0; i < sizeof(buffer); i++)
    {
        buffer[i] = 0x55;  // 01010101 pattern
    }
    OLED_Update();
    Delay_ms(2000);

    // Clear screen
    OLED_Clear();
    Delay_ms(500);
}

//-------------------------------------
// I2C Scan to find OLED
//-------------------------------------
void I2C_FindOLED(void)
{
    bool found = false;

    UART_Print("Looking for OLED at 0x3C...\r\n");

    // Test address 0x3C
    I2CMasterSlaveAddrSet(I2C0_BASE, 0x3C, false);
    I2CMasterDataPut(I2C0_BASE, 0x00);
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
    while(I2CMasterBusy(I2C0_BASE));

    if(I2CMasterErr(I2C0_BASE) == I2C_MASTER_ERR_NONE)
    {
        UART_Print("OLED found at 0x3C!\r\n");
        found = true;
    }
    else
    {
        UART_Print("OLED NOT found at 0x3C\r\n");

        // Try 0x3D
        I2CMasterSlaveAddrSet(I2C0_BASE, 0x3D, false);
        I2CMasterDataPut(I2C0_BASE, 0x00);
        I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
        while(I2CMasterBusy(I2C0_BASE));

        if(I2CMasterErr(I2C0_BASE) == I2C_MASTER_ERR_NONE)
        {
            UART_Print("OLED found at 0x3D!\r\n");
            found = true;
        }
    }

    if(!found)
    {
        UART_Print("\r\n!!! OLED NOT FOUND !!!\r\n");
    }
}

//-------------------------------------
// MAIN
//-------------------------------------
int main(void)
{
    g_ui32SysClock = SysCtlClockFreqSet(
                        (SYSCTL_XTAL_25MHZ |
                         SYSCTL_OSC_MAIN |
                         SYSCTL_USE_PLL |
                         SYSCTL_CFG_VCO_480),
                        120000000);

    UART0_Init();
    UART_Print("\r\n");
    UART_Print("   SSD1306 OLED DRIVER - FIXED         \r\n");

    I2C0_Init();

    // Find OLED
    I2C_FindOLED();

    if(I2CMasterErr(I2C0_BASE) == I2C_MASTER_ERR_NONE)
    {
        // Initialize OLED
        OLED_Init();

        // Run test pattern first
        OLED_TestPattern();

        // Draw PAVAN
        OLED_DrawPAVAN();

        UART_Print("\r\n'PAVAN' should be displayed on OLED!\r\n");
    }
    else
    {
        UART_Print("\r\nCannot proceed - OLED not detected!\r\n");
    }


    while(1)
    {
        Delay_ms(1000);
        UART_Print(".");
    }
}
