#include "ssd1306_oled.h"

#include "mcc_generated_files/i2c_host/i2c1.h"
#include <stddef.h>
#include <stdint.h>

#define SSD1306_I2C_ADDRESS             0x3CU
#define SSD1306_CONTROL_COMMAND         0x00U
#define SSD1306_CONTROL_DATA            0x40U
#define SSD1306_WIDTH_PIXELS            128U
#define SSD1306_PAGES                   8U
#define SSD1306_CHAR_WIDTH              8U
#define SSD1306_WAIT_LIMIT              100000U
#define SSD1306_DATA_CHUNK_SIZE         16U

/* Public-domain 8x8 basic latin bitmaps (U+0020..U+007E), row-oriented. */
static const uint8_t ssd1306Font8x8Basic[95][8] = {
    {0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}, /* space */
    {0x18U, 0x3CU, 0x3CU, 0x18U, 0x18U, 0x00U, 0x18U, 0x00U},
    {0x36U, 0x36U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U},
    {0x36U, 0x36U, 0x7FU, 0x36U, 0x7FU, 0x36U, 0x36U, 0x00U},
    {0x0CU, 0x3EU, 0x03U, 0x1EU, 0x30U, 0x1FU, 0x0CU, 0x00U},
    {0x00U, 0x63U, 0x33U, 0x18U, 0x0CU, 0x66U, 0x63U, 0x00U},
    {0x1CU, 0x36U, 0x1CU, 0x6EU, 0x3BU, 0x33U, 0x6EU, 0x00U},
    {0x06U, 0x06U, 0x03U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U},
    {0x18U, 0x0CU, 0x06U, 0x06U, 0x06U, 0x0CU, 0x18U, 0x00U},
    {0x06U, 0x0CU, 0x18U, 0x18U, 0x18U, 0x0CU, 0x06U, 0x00U},
    {0x00U, 0x66U, 0x3CU, 0xFFU, 0x3CU, 0x66U, 0x00U, 0x00U},
    {0x00U, 0x0CU, 0x0CU, 0x3FU, 0x0CU, 0x0CU, 0x00U, 0x00U},
    {0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x0CU, 0x0CU, 0x06U},
    {0x00U, 0x00U, 0x00U, 0x3FU, 0x00U, 0x00U, 0x00U, 0x00U},
    {0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x0CU, 0x0CU, 0x00U},
    {0x60U, 0x30U, 0x18U, 0x0CU, 0x06U, 0x03U, 0x01U, 0x00U},
    {0x3EU, 0x63U, 0x73U, 0x7BU, 0x6FU, 0x67U, 0x3EU, 0x00U},
    {0x0CU, 0x0EU, 0x0CU, 0x0CU, 0x0CU, 0x0CU, 0x3FU, 0x00U},
    {0x1EU, 0x33U, 0x30U, 0x1CU, 0x06U, 0x33U, 0x3FU, 0x00U},
    {0x1EU, 0x33U, 0x30U, 0x1CU, 0x30U, 0x33U, 0x1EU, 0x00U},
    {0x38U, 0x3CU, 0x36U, 0x33U, 0x7FU, 0x30U, 0x78U, 0x00U},
    {0x3FU, 0x03U, 0x1FU, 0x30U, 0x30U, 0x33U, 0x1EU, 0x00U},
    {0x1CU, 0x06U, 0x03U, 0x1FU, 0x33U, 0x33U, 0x1EU, 0x00U},
    {0x3FU, 0x33U, 0x30U, 0x18U, 0x0CU, 0x0CU, 0x0CU, 0x00U},
    {0x1EU, 0x33U, 0x33U, 0x1EU, 0x33U, 0x33U, 0x1EU, 0x00U},
    {0x1EU, 0x33U, 0x33U, 0x3EU, 0x30U, 0x18U, 0x0EU, 0x00U},
    {0x00U, 0x0CU, 0x0CU, 0x00U, 0x00U, 0x0CU, 0x0CU, 0x00U},
    {0x00U, 0x0CU, 0x0CU, 0x00U, 0x00U, 0x0CU, 0x0CU, 0x06U},
    {0x18U, 0x0CU, 0x06U, 0x03U, 0x06U, 0x0CU, 0x18U, 0x00U},
    {0x00U, 0x00U, 0x3FU, 0x00U, 0x00U, 0x3FU, 0x00U, 0x00U},
    {0x06U, 0x0CU, 0x18U, 0x30U, 0x18U, 0x0CU, 0x06U, 0x00U},
    {0x1EU, 0x33U, 0x30U, 0x18U, 0x0CU, 0x00U, 0x0CU, 0x00U},
    {0x3EU, 0x63U, 0x7BU, 0x7BU, 0x7BU, 0x03U, 0x1EU, 0x00U},
    {0x0CU, 0x1EU, 0x33U, 0x33U, 0x3FU, 0x33U, 0x33U, 0x00U},
    {0x3FU, 0x66U, 0x66U, 0x3EU, 0x66U, 0x66U, 0x3FU, 0x00U},
    {0x3CU, 0x66U, 0x03U, 0x03U, 0x03U, 0x66U, 0x3CU, 0x00U},
    {0x1FU, 0x36U, 0x66U, 0x66U, 0x66U, 0x36U, 0x1FU, 0x00U},
    {0x7FU, 0x46U, 0x16U, 0x1EU, 0x16U, 0x46U, 0x7FU, 0x00U},
    {0x7FU, 0x46U, 0x16U, 0x1EU, 0x16U, 0x06U, 0x0FU, 0x00U},
    {0x3CU, 0x66U, 0x03U, 0x03U, 0x73U, 0x66U, 0x7CU, 0x00U},
    {0x33U, 0x33U, 0x33U, 0x3FU, 0x33U, 0x33U, 0x33U, 0x00U},
    {0x1EU, 0x0CU, 0x0CU, 0x0CU, 0x0CU, 0x0CU, 0x1EU, 0x00U},
    {0x78U, 0x30U, 0x30U, 0x30U, 0x33U, 0x33U, 0x1EU, 0x00U},
    {0x67U, 0x66U, 0x36U, 0x1EU, 0x36U, 0x66U, 0x67U, 0x00U},
    {0x0FU, 0x06U, 0x06U, 0x06U, 0x46U, 0x66U, 0x7FU, 0x00U},
    {0x63U, 0x77U, 0x7FU, 0x7FU, 0x6BU, 0x63U, 0x63U, 0x00U},
    {0x63U, 0x67U, 0x6FU, 0x7BU, 0x73U, 0x63U, 0x63U, 0x00U},
    {0x1CU, 0x36U, 0x63U, 0x63U, 0x63U, 0x36U, 0x1CU, 0x00U},
    {0x3FU, 0x66U, 0x66U, 0x3EU, 0x06U, 0x06U, 0x0FU, 0x00U},
    {0x1EU, 0x33U, 0x33U, 0x33U, 0x3BU, 0x1EU, 0x38U, 0x00U},
    {0x3FU, 0x66U, 0x66U, 0x3EU, 0x36U, 0x66U, 0x67U, 0x00U},
    {0x1EU, 0x33U, 0x07U, 0x0EU, 0x38U, 0x33U, 0x1EU, 0x00U},
    {0x3FU, 0x2DU, 0x0CU, 0x0CU, 0x0CU, 0x0CU, 0x1EU, 0x00U},
    {0x33U, 0x33U, 0x33U, 0x33U, 0x33U, 0x33U, 0x3FU, 0x00U},
    {0x33U, 0x33U, 0x33U, 0x33U, 0x33U, 0x1EU, 0x0CU, 0x00U},
    {0x63U, 0x63U, 0x63U, 0x6BU, 0x7FU, 0x77U, 0x63U, 0x00U},
    {0x63U, 0x63U, 0x36U, 0x1CU, 0x1CU, 0x36U, 0x63U, 0x00U},
    {0x33U, 0x33U, 0x33U, 0x1EU, 0x0CU, 0x0CU, 0x1EU, 0x00U},
    {0x7FU, 0x63U, 0x31U, 0x18U, 0x4CU, 0x66U, 0x7FU, 0x00U},
    {0x1EU, 0x06U, 0x06U, 0x06U, 0x06U, 0x06U, 0x1EU, 0x00U},
    {0x03U, 0x06U, 0x0CU, 0x18U, 0x30U, 0x60U, 0x40U, 0x00U},
    {0x1EU, 0x18U, 0x18U, 0x18U, 0x18U, 0x18U, 0x1EU, 0x00U},
    {0x08U, 0x1CU, 0x36U, 0x63U, 0x00U, 0x00U, 0x00U, 0x00U},
    {0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0xFFU},
    {0x0CU, 0x0CU, 0x18U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U},
    {0x00U, 0x00U, 0x1EU, 0x30U, 0x3EU, 0x33U, 0x6EU, 0x00U},
    {0x07U, 0x06U, 0x06U, 0x3EU, 0x66U, 0x66U, 0x3BU, 0x00U},
    {0x00U, 0x00U, 0x1EU, 0x33U, 0x03U, 0x33U, 0x1EU, 0x00U},
    {0x38U, 0x30U, 0x30U, 0x3EU, 0x33U, 0x33U, 0x6EU, 0x00U},
    {0x00U, 0x00U, 0x1EU, 0x33U, 0x3FU, 0x03U, 0x1EU, 0x00U},
    {0x1CU, 0x36U, 0x06U, 0x0FU, 0x06U, 0x06U, 0x0FU, 0x00U},
    {0x00U, 0x00U, 0x6EU, 0x33U, 0x33U, 0x3EU, 0x30U, 0x1FU},
    {0x07U, 0x06U, 0x36U, 0x6EU, 0x66U, 0x66U, 0x67U, 0x00U},
    {0x0CU, 0x00U, 0x0EU, 0x0CU, 0x0CU, 0x0CU, 0x1EU, 0x00U},
    {0x30U, 0x00U, 0x30U, 0x30U, 0x30U, 0x33U, 0x33U, 0x1EU},
    {0x07U, 0x06U, 0x66U, 0x36U, 0x1EU, 0x36U, 0x67U, 0x00U},
    {0x0EU, 0x0CU, 0x0CU, 0x0CU, 0x0CU, 0x0CU, 0x1EU, 0x00U},
    {0x00U, 0x00U, 0x33U, 0x7FU, 0x7FU, 0x6BU, 0x63U, 0x00U},
    {0x00U, 0x00U, 0x1FU, 0x33U, 0x33U, 0x33U, 0x33U, 0x00U},
    {0x00U, 0x00U, 0x1EU, 0x33U, 0x33U, 0x33U, 0x1EU, 0x00U},
    {0x00U, 0x00U, 0x3BU, 0x66U, 0x66U, 0x3EU, 0x06U, 0x0FU},
    {0x00U, 0x00U, 0x6EU, 0x33U, 0x33U, 0x3EU, 0x30U, 0x78U},
    {0x00U, 0x00U, 0x3BU, 0x6EU, 0x66U, 0x06U, 0x0FU, 0x00U},
    {0x00U, 0x00U, 0x3EU, 0x03U, 0x1EU, 0x30U, 0x1FU, 0x00U},
    {0x08U, 0x0CU, 0x3EU, 0x0CU, 0x0CU, 0x2CU, 0x18U, 0x00U},
    {0x00U, 0x00U, 0x33U, 0x33U, 0x33U, 0x33U, 0x6EU, 0x00U},
    {0x00U, 0x00U, 0x33U, 0x33U, 0x33U, 0x1EU, 0x0CU, 0x00U},
    {0x00U, 0x00U, 0x63U, 0x6BU, 0x7FU, 0x7FU, 0x36U, 0x00U},
    {0x00U, 0x00U, 0x63U, 0x36U, 0x1CU, 0x36U, 0x63U, 0x00U},
    {0x00U, 0x00U, 0x33U, 0x33U, 0x33U, 0x3EU, 0x30U, 0x1FU},
    {0x00U, 0x00U, 0x3FU, 0x19U, 0x0CU, 0x26U, 0x3FU, 0x00U},
    {0x38U, 0x0CU, 0x0CU, 0x07U, 0x0CU, 0x0CU, 0x38U, 0x00U},
    {0x18U, 0x18U, 0x18U, 0x00U, 0x18U, 0x18U, 0x18U, 0x00U},
    {0x07U, 0x0CU, 0x0CU, 0x38U, 0x0CU, 0x0CU, 0x07U, 0x00U},
    {0x6EU, 0x3BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}
};

static bool SSD1306_WaitForI2CIdle(void)
{
    uint32_t guardCount = 0U;

    while(I2C1_IsBusy())
    {
        guardCount++;
        if(guardCount > SSD1306_WAIT_LIMIT)
        {
            return false;
        }
    }

    return true;
}

static bool SSD1306_WriteRaw(const uint8_t *buffer, uint8_t length)
{
    if((buffer == NULL) || (length == 0U))
    {
        return false;
    }

    if(!SSD1306_WaitForI2CIdle())
    {
        return false;
    }

    if(!I2C1_Host_Write(SSD1306_I2C_ADDRESS, (uint8_t *)buffer, length))
    {
        return false;
    }

    if(!SSD1306_WaitForI2CIdle())
    {
        return false;
    }

    return (I2C1_Host_ErrorGet() == I2C_HOST_ERROR_NONE);
}

static bool SSD1306_WriteCommand(uint8_t command)
{
    uint8_t packet[2] = {SSD1306_CONTROL_COMMAND, command};
    return SSD1306_WriteRaw(packet, (uint8_t)sizeof(packet));
}

static bool SSD1306_SetPageAndColumn(uint8_t page)
{
    if(page >= SSD1306_PAGES)
    {
        return false;
    }

    if(!SSD1306_WriteCommand((uint8_t)(0xB0U | page)))
    {
        return false;
    }

    if(!SSD1306_WriteCommand(0x00U))
    {
        return false;
    }

    return SSD1306_WriteCommand(0x10U);
}

static bool SSD1306_WriteDataBlock(const uint8_t *data, uint8_t dataLength)
{
    uint8_t packet[1U + SSD1306_DATA_CHUNK_SIZE];

    if((data == NULL) || (dataLength == 0U) || (dataLength > SSD1306_DATA_CHUNK_SIZE))
    {
        return false;
    }

    packet[0] = SSD1306_CONTROL_DATA;
    for(uint8_t i = 0U; i < dataLength; i++)
    {
        packet[1U + i] = data[i];
    }

    return SSD1306_WriteRaw(packet, (uint8_t)(1U + dataLength));
}

static bool SSD1306_WritePageData(const uint8_t *pageData, uint8_t length)
{
    uint8_t offset = 0U;
    uint8_t chunk;

    if((pageData == NULL) || (length == 0U))
    {
        return false;
    }

    while(offset < length)
    {
        chunk = (uint8_t)(length - offset);
        if(chunk > SSD1306_DATA_CHUNK_SIZE)
        {
            chunk = SSD1306_DATA_CHUNK_SIZE;
        }

        if(!SSD1306_WriteDataBlock(&pageData[offset], chunk))
        {
            return false;
        }

        offset = (uint8_t)(offset + chunk);
    }

    return true;
}

static void SSD1306_GetGlyph8x8(char c, uint8_t glyph[SSD1306_CHAR_WIDTH])
{
    uint8_t ascii = (uint8_t)c;
    const uint8_t *rows;
    uint8_t colBits;

    if((ascii < 0x20U) || (ascii > 0x7EU))
    {
        ascii = (uint8_t)'?';
    }

    rows = ssd1306Font8x8Basic[ascii - 0x20U];

    for(uint8_t col = 0U; col < SSD1306_CHAR_WIDTH; col++)
    {
        colBits = 0U;

        for(uint8_t row = 0U; row < 8U; row++)
        {
            if((rows[row] & (uint8_t)(1U << col)) != 0U)
            {
                colBits |= (uint8_t)(1U << row);
            }
        }

        glyph[col] = colBits;
    }
}

static void SSD1306_ExpandGlyph8x16(const uint8_t glyph8x8[SSD1306_CHAR_WIDTH], uint8_t glyphTop[SSD1306_CHAR_WIDTH], uint8_t glyphBottom[SSD1306_CHAR_WIDTH])
{
    uint16_t expandedColumn;

    for(uint8_t col = 0U; col < SSD1306_CHAR_WIDTH; col++)
    {
        expandedColumn = 0U;

        for(uint8_t row = 0U; row < 8U; row++)
        {
            if((glyph8x8[col] & (uint8_t)(1U << row)) != 0U)
            {
                expandedColumn |= (uint16_t)(1U << (row * 2U));
                expandedColumn |= (uint16_t)(1U << ((row * 2U) + 1U));
            }
        }

        glyphTop[col] = (uint8_t)(expandedColumn & 0x00FFU);
        glyphBottom[col] = (uint8_t)((expandedColumn >> 8U) & 0x00FFU);
    }
}

bool SSD1306_OLED_Initialize(void)
{
    static const uint8_t initCommands[] = {
        0xAEU,
        0xD5U, 0x80U,
        0xA8U, 0x3FU,
        0xD3U, 0x00U,
        0x40U,
        0x8DU, 0x14U,
        0x20U, 0x02U,
        0xA1U,
        0xC8U,
        0xDAU, 0x12U,
        0x81U, 0xCFU,
        0xD9U, 0xF1U,
        0xDBU, 0x40U,
        0xA4U,
        0xA6U,
        0xAFU
    };

    for(uint8_t i = 0U; i < (uint8_t)sizeof(initCommands); i++)
    {
        if(!SSD1306_WriteCommand(initCommands[i]))
        {
            return false;
        }
    }

    return SSD1306_OLED_Clear();
}

bool SSD1306_OLED_Clear(void)
{
    uint8_t clearLine[SSD1306_WIDTH_PIXELS] = {0};

    for(uint8_t page = 0U; page < SSD1306_PAGES; page++)
    {
        if(!SSD1306_SetPageAndColumn(page))
        {
            return false;
        }

        if(!SSD1306_WritePageData(clearLine, SSD1306_WIDTH_PIXELS))
        {
            return false;
        }
    }

    return true;
}

bool SSD1306_OLED_WriteLine(uint8_t lineIndex, const char *text)
{
    uint8_t pageTop;
    uint8_t pageBottom;
    uint8_t topPageBuffer[SSD1306_WIDTH_PIXELS] = {0};
    uint8_t bottomPageBuffer[SSD1306_WIDTH_PIXELS] = {0};
    uint8_t glyph8x8[SSD1306_CHAR_WIDTH];
    uint8_t glyphTop[SSD1306_CHAR_WIDTH];
    uint8_t glyphBottom[SSD1306_CHAR_WIDTH];

    if((lineIndex >= SSD1306_OLED_CHAR_LINES) || (text == NULL))
    {
        return false;
    }

    for(uint8_t col = 0U; col < SSD1306_OLED_CHAR_COLUMNS; col++)
    {
        char currentChar = text[col];
        uint8_t pixelOffset = (uint8_t)(col * SSD1306_CHAR_WIDTH);

        if(currentChar == '\0')
        {
            break;
        }

        SSD1306_GetGlyph8x8(currentChar, glyph8x8);
        SSD1306_ExpandGlyph8x16(glyph8x8, glyphTop, glyphBottom);
        for(uint8_t pixel = 0U; pixel < SSD1306_CHAR_WIDTH; pixel++)
        {
            topPageBuffer[pixelOffset + pixel] = glyphTop[pixel];
            bottomPageBuffer[pixelOffset + pixel] = glyphBottom[pixel];
        }
    }

    pageTop = (uint8_t)(lineIndex * 2U);
    pageBottom = (uint8_t)(pageTop + 1U);

    if(!SSD1306_SetPageAndColumn(pageTop))
    {
        return false;
    }

    if(!SSD1306_WritePageData(topPageBuffer, SSD1306_WIDTH_PIXELS))
    {
        return false;
    }

    if(!SSD1306_SetPageAndColumn(pageBottom))
    {
        return false;
    }

    if(!SSD1306_WritePageData(bottomPageBuffer, SSD1306_WIDTH_PIXELS))
    {
        return false;
    }

    return true;
}

bool SSD1306_OLED_ShowHelloMicrochip(void)
{
    if(!SSD1306_OLED_Initialize())
    {
        return false;
    }

    if(!SSD1306_OLED_WriteLine(0U, "Hello Microchip"))
    {
        return false;
    }

    return true;
}
