#include "st7789.h"

#include "font8x8_basic.h"

#include "mcc_generated_files/spi_host/spi1.h"
#include "mcc_generated_files/system/pins.h"
#include <xc.h>

#define ST7789_CMD_SWRESET      0x01U
#define ST7789_CMD_SLPOUT       0x11U
#define ST7789_CMD_NORON        0x13U
#define ST7789_CMD_INVOFF       0x20U
#define ST7789_CMD_INVON        0x21U
#define ST7789_CMD_DISPON       0x29U
#define ST7789_CMD_CASET        0x2AU
#define ST7789_CMD_RASET        0x2BU
#define ST7789_CMD_RAMWR        0x2CU
#define ST7789_CMD_MADCTL       0x36U
#define ST7789_CMD_COLMOD       0x3AU

#define ST7789_DELAY_1MS_ITERATIONS  100000UL

static void ST7789_DelayMs(uint16_t delayMs)
{
    volatile uint32_t i;
    uint16_t ms;

    for(ms = 0U; ms < delayMs; ms++)
    {
        for(i = 0UL; i < ST7789_DELAY_1MS_ITERATIONS; i++)
        {
            __builtin_nop();
        }
    }
}

static inline void ST7789_Select(void)
{
    TFT_CS_SetLow();
}

static inline void ST7789_Deselect(void)
{
    TFT_CS_SetHigh();
}

static inline void ST7789_WriteByte(uint8_t value)
{
    (void)SPI1_ByteExchange(value);
}

static void ST7789_WriteCommand(uint8_t command)
{
    ST7789_Select();
    TFT_DC_SetLow();
    ST7789_WriteByte(command);
    ST7789_Deselect();
}

static void ST7789_WriteData(const uint8_t *data, uint16_t size)
{
    uint16_t index;

    ST7789_Select();
    TFT_DC_SetHigh();

    for(index = 0U; index < size; index++)
    {
        ST7789_WriteByte(data[index]);
    }

    ST7789_Deselect();
}

static void ST7789_WriteCommandWithData(uint8_t command, const uint8_t *data, uint16_t size)
{
    ST7789_WriteCommand(command);
    if((data != NULL) && (size > 0U))
    {
        ST7789_WriteData(data, size);
    }
}

static void ST7789_SetAddressWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
    uint8_t data[4];

    data[0] = (uint8_t)(x0 >> 8);
    data[1] = (uint8_t)x0;
    data[2] = (uint8_t)(x1 >> 8);
    data[3] = (uint8_t)x1;
    ST7789_WriteCommandWithData(ST7789_CMD_CASET, data, sizeof(data));

    data[0] = (uint8_t)(y0 >> 8);
    data[1] = (uint8_t)y0;
    data[2] = (uint8_t)(y1 >> 8);
    data[3] = (uint8_t)y1;
    ST7789_WriteCommandWithData(ST7789_CMD_RASET, data, sizeof(data));
}

static void ST7789_DrawChar8x8(uint16_t x,
                               uint16_t y,
                               char character,
                               uint16_t color,
                               uint16_t backgroundColor,
                               uint8_t scale)
{
    const uint8_t *glyph;
    uint8_t row;
    uint8_t col;
    uint16_t pixelColor;
    uint8_t ascii = (uint8_t)character;

    if(scale == 0U)
    {
        return;
    }

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

    glyph = ST7789_FONT8X8_BASIC[ascii];

    for(row = 0U; row < 8U; row++)
    {
        for(col = 0U; col < 8U; col++)
        {
            if((glyph[row] & (1U << col)) != 0U)
            {
                pixelColor = color;
            }
            else
            {
                pixelColor = backgroundColor;
            }

            ST7789_FillRect((uint16_t)(x + ((uint16_t)col * (uint16_t)scale)),
                            (uint16_t)(y + ((uint16_t)row * (uint16_t)scale)),
                            scale,
                            scale,
                            pixelColor);
        }
    }
}

void ST7789_Initialize(void)
{
    uint8_t data;

    TFT_DC_SetDigitalOutput();
    TFT_RST_SetDigitalOutput();
    TFT_CS_SetDigitalOutput();

    TFT_CS_SetHigh();
    TFT_DC_SetHigh();

    TFT_RST_SetHigh();
    ST7789_DelayMs(10U);
    TFT_RST_SetLow();
    ST7789_DelayMs(10U);
    TFT_RST_SetHigh();
    ST7789_DelayMs(120U);

    (void)SPI1_Host_Open(HOST_CONFIG);

    ST7789_WriteCommand(ST7789_CMD_SWRESET);
    ST7789_DelayMs(150U);

    ST7789_WriteCommand(ST7789_CMD_SLPOUT);
    ST7789_DelayMs(120U);

    data = 0x55U;
    ST7789_WriteCommandWithData(ST7789_CMD_COLMOD, &data, 1U);

    data = 0x00U;
    ST7789_WriteCommandWithData(ST7789_CMD_MADCTL, &data, 1U);

    ST7789_WriteCommand(ST7789_CMD_INVON);
    ST7789_WriteCommand(ST7789_CMD_NORON);
    ST7789_DelayMs(10U);
    ST7789_WriteCommand(ST7789_CMD_DISPON);
    ST7789_DelayMs(100U);
}

void ST7789_FillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color)
{
    uint32_t pixelCount;
    uint16_t xEnd;
    uint16_t yEnd;
    uint8_t colorBytes[2];

    if((x >= ST7789_WIDTH) || (y >= ST7789_HEIGHT) || (width == 0U) || (height == 0U))
    {
        return;
    }

    if((uint32_t)x + (uint32_t)width > ST7789_WIDTH)
    {
        width = (uint16_t)(ST7789_WIDTH - x);
    }

    if((uint32_t)y + (uint32_t)height > ST7789_HEIGHT)
    {
        height = (uint16_t)(ST7789_HEIGHT - y);
    }

    xEnd = (uint16_t)(x + width - 1U);
    yEnd = (uint16_t)(y + height - 1U);
    pixelCount = (uint32_t)width * (uint32_t)height;

    ST7789_SetAddressWindow(x, y, xEnd, yEnd);
    ST7789_WriteCommand(ST7789_CMD_RAMWR);

    colorBytes[0] = (uint8_t)(color >> 8);
    colorBytes[1] = (uint8_t)color;

    ST7789_Select();
    TFT_DC_SetHigh();

    while(pixelCount > 0UL)
    {
        ST7789_WriteByte(colorBytes[0]);
        ST7789_WriteByte(colorBytes[1]);
        pixelCount--;
    }

    ST7789_Deselect();
}

void ST7789_DrawImageRGB565(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint16_t *image)
{
    uint32_t pixelCount;
    uint16_t xEnd;
    uint16_t yEnd;
    uint16_t pixel;

    if((image == NULL) || (width == 0U) || (height == 0U))
    {
        return;
    }

    if((x >= ST7789_WIDTH) || (y >= ST7789_HEIGHT))
    {
        return;
    }

    if(((uint32_t)x + (uint32_t)width > ST7789_WIDTH) || ((uint32_t)y + (uint32_t)height > ST7789_HEIGHT))
    {
        return;
    }

    xEnd = (uint16_t)(x + width - 1U);
    yEnd = (uint16_t)(y + height - 1U);
    pixelCount = (uint32_t)width * (uint32_t)height;

    ST7789_SetAddressWindow(x, y, xEnd, yEnd);
    ST7789_WriteCommand(ST7789_CMD_RAMWR);

    ST7789_Select();
    TFT_DC_SetHigh();

    while(pixelCount > 0UL)
    {
        pixel = *image;
        image++;

        ST7789_WriteByte((uint8_t)(pixel >> 8));
        ST7789_WriteByte((uint8_t)pixel);
        pixelCount--;
    }

    ST7789_Deselect();
}

void ST7789_DrawString8x8(uint16_t x, uint16_t y, const char *text, uint16_t color, uint16_t backgroundColor, uint8_t scale)
{
    uint16_t cursorX = x;
    uint16_t charAdvance;

    if((text == NULL) || (scale == 0U))
    {
        return;
    }

    charAdvance = (uint16_t)(8U * (uint16_t)scale);

    while(*text != '\0')
    {
        ST7789_DrawChar8x8(cursorX, y, *text, color, backgroundColor, scale);

        cursorX = (uint16_t)(cursorX + charAdvance);
        text++;

        if(cursorX >= ST7789_WIDTH)
        {
            break;
        }
    }
}