/*! @file LCD.c

  @brief   sets and writes framebuffer
  @author  Hendrik Schutter
  @version V1.0
  @date    03.11.2020

 This code displays graphics on the 320x240 LCD on an ESP-WROVER_KIT board.

 Some info about the ILI9341/ST7789V: It has an C/D line, which is connected to a GPIO here. It expects this
 line to be low for a command and high for data. We use a pre-transmit callback here to control that
 line: every transaction has as the user-definable argument the needed state of the D/C line and just
 before the transaction is sent, the callback will set this line to the correct state.

*/

#include "LCD.h"
#include "Driver.h"

esp_err_t iLCD_allocateFramebuffer(uint16_t ***pData);
void vLCD_writeChar(uint16_t u16xPos, uint16_t u16yPos, char cCharacter,uint16_t u16ColorFont,uint16_t u16ColorBackground);

uint16_t **pu16Framebuffer; //framebuffer

//ASCII Font 6x8
static const uint8_t font6_8[][6] =
{
    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },   //sp0
    { 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 },   // !1
    { 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 },   // "2
    { 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 },   // #3
    { 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 },   // $4
    { 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 },   // %5
    { 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 },   // &6
    { 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 },   // '7
    { 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 },   // (8
    { 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 },   // )9
    { 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 },   // *10
    { 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 },   // +11
    { 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 },   // ,12
    { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 },   // -13
    { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 },   // .14
    { 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 },   // /15
    { 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E },   // 016
    { 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 },   // 117
    { 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 },   // 218
    { 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 },   // 319
    { 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 },   // 420
    { 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 },   // 521
    { 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 },   // 622
    { 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 },   // 723
    { 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 },   // 824
    { 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E },   // 925
    { 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 },   // :26
    { 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 },   // ;27
    { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 },   // <28
    { 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 },   // =29
    { 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 },   // >30
    { 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 },   // ?31
    { 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E },   // @32
    { 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C },   // A33
    { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 },   // B34
    { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 },   // C35
    { 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C },   // D36
    { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 },   // E37
    { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 },   // F38
    { 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A },   // G39
    { 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F },   // H40
    { 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 },   // I41
    { 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 },   // J42
    { 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 },   // K43
    { 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 },   // L44
    { 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F },   // M45
    { 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F },   // N46
    { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E },   // O47
    { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 },   // P48
    { 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E },   // Q49
    { 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 },   // R50
    { 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 },   // S51
    { 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 },   // T52
    { 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F },   // U53
    { 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F },   // V54
    { 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F },   // W55
    { 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 },   // X56
    { 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 },   // Y57
    { 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 },   // Z58
    { 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 },   // [59
    { 0x00, 0x02, 0x04, 0x08, 0x10, 0x20 },   // \60
    { 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 },   // ]61
    { 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 },   // ^62
    { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 },   // _63
    { 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 },   // '64
    { 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 },   // a65
    { 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 },   // b66
    { 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 },   // c67
    { 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F },   // d68
    { 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 },   // e69
    { 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 },   // f70
    { 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C },   // g71
    { 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 },   // h72
    { 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 },   // i73
    { 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 },   // j74
    { 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 },   // k75
    { 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 },   // l76
    { 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 },   // m77
    { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 },   // n78
    { 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 },   // o79
    { 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 },   // p80
    { 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC },   // q81
    { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 },   // r82
    { 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 },   // s83
    { 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 },   // t84
    { 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C },   // u85
    { 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C },   // v86
    { 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C },   // w87
    { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 },   // x88
    { 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C },   // y89
    { 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 },   // z90
    { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14 }    // horiz lines91
};

/**
 * @fn esp_err_t iLCD_init(void)
 * @brief init
 * @param void
 * @return int
 * @author Hendrik Schutter
 * @date 3.11.2020
 */
esp_err_t iLCD_init(void) {
    esp_err_t ret;

    //Initialize the Driver
    vDriver_init();

    //Initialize the framebuffer
    ret= iLCD_allocateFramebuffer(&pu16Framebuffer);
    ESP_ERROR_CHECK(ret);

    return ret;
}

/**
 * @fn esp_err_t iLCD_allocateFramebuffer(uint16_t ***pData)
 * @brief allocate framebuffer
 * @param pointer of framebuffer two dimensional array
 * @return esp error code
 * @author Hendrik Schutter
 * @date 3.11.2020
 */
esp_err_t iLCD_allocateFramebuffer(uint16_t ***pData)
{
    *pData = NULL;
    esp_err_t ret = ESP_OK;
    //Alocate pixel memory. Each line is an array of IMAGE_W 16-bit pixels; the `*pixels` array itself contains pointers to these lines.
    *pData = calloc(240, sizeof(uint16_t *));
    if (*pData == NULL) {
        printf("Error allocating memory for lines");
        ret = ESP_ERR_NO_MEM;
    }
    for (int i = 0; i < 240; i++) {
        (*pData)[i] = malloc(320 * sizeof(uint16_t));
        if ((*pData)[i] == NULL) {
            printf("Error allocating memory for line %d", i);
            ret = ESP_ERR_NO_MEM;
        }
    }
    return ret;
}

/**
 * @fn esp_err_t iLCD_clearFramebuffer(uint16_t u16Color)
 * @brief clear complete framebuffer with a color
 * @param RGB586 color code
 * @return esp error code
 * @author Hendrik Schutter
 * @date 3.11.2020
 */
esp_err_t iLCD_clearFramebuffer(uint16_t u16Color) {
    for (int y = 0; y < 240; y++) {
        for (int x = 0; x < 320; x++) {
            //The LCD wants the 16-bit value in big-endian, so swap bytes
            u16Color = (u16Color >> 8) | (u16Color << 8);
            pu16Framebuffer[y][x] = u16Color;
        }
    }
    return iDriver_writeFramebuffer(&pu16Framebuffer);
}

/**
 * @fn esp_err_t iLCD_writeString(uint16_t u16xPos, uint16_t u16yPos, char *pcText, uint16_t u16ColorFont, uint16_t u16ColorBackground)
 * @brief write string/text into framebuffer
 * @param uint16_t u16xPos
 * @param uint16_t u16yPos
 * @param char *pcText
 * @param RGB586 color code for font uint16_t u16ColorFont
 * @param RGB586 color code for background uint16_t u16ColorBackground
 * @return esp error code
 * @author Hendrik Schutter
 * @date 4.11.2020
 */
esp_err_t iLCD_writeString(uint16_t u16xPos, uint16_t u16yPos, char *pcText, uint16_t u16ColorFont, uint16_t u16ColorBackground)
{
    while(*pcText != '\0')
    {
        if(u16xPos > LCD_WIDTH-6)
        {
            u16xPos = 0;
            u16yPos++;
        }
        if(u16yPos > LCD_HIGH-8)
        {
            u16xPos = u16yPos = 0;
        }

        vLCD_writeChar(u16xPos,u16yPos,*pcText,u16ColorFont,u16ColorBackground);
        u16xPos+=6;
        pcText++;
    }
    return iDriver_writeFramebuffer(&pu16Framebuffer);
}

/**
 * @fn void vLCD_writeChar(uint16_t u16xPos, uint16_t u16yPos, char cCharacter,uint16_t u16ColorFont,uint16_t u16ColorBackground)
 * @brief write string/text into framebuffer
 * @param uint16_t u16xPos
 * @param uint16_t u16yPos
 * @param char cCharacter
 * @param RGB586 color code for font uint16_t u16ColorFont
 * @param RGB586 color code for background uint16_t u16ColorBackground
 * @return esp error code
 * @author Hendrik Schutter
 * @date 4.11.2020
 */
void vLCD_writeChar(uint16_t u16xPos, uint16_t u16yPos, char cCharacter, uint16_t u16ColorFont,uint16_t u16ColorBackground)
{
    uint8_t u8FontIndex = (cCharacter-32);

    for (uint16_t y = u16yPos; y <= (u16yPos+8); y++) {
        for (uint16_t x = u16xPos; x <= (u16xPos+6); x++) {

            if(font6_8[u8FontIndex] [(x-u16xPos)] & (1<<(y-u16yPos)))
            {
                pu16Framebuffer[y][x] = (u16ColorFont >> 8) | (u16ColorFont << 8);
            } else
            {
                pu16Framebuffer[y][x] = (u16ColorBackground >> 8) | (u16ColorBackground << 8);
            }
        }
    }
}