#include <array>
#include <cstdint>

#include "st7789.h"

St7789::St7789(pinetime::interfaces::SpiInterface & spi_if,
               pinetime::interfaces::GpioInterface & rst,
               pinetime::interfaces::GpioInterface & dc,
               pinetime::interfaces::GpioInterface & bl)
    : spi(spi_if)
    , reset(rst)
    , data_command(dc)
    , backlight(bl)
{
    this->reset.set();
    this->data_command.set();
    this->backlight.set();

    // hard reset
    this->reset.clear();
    // FIXME: add proper delay
    // delay_ms(100);
    for(volatile uint32_t cnt = 0; cnt < 100; ++cnt);
    this->reset.set();
    // FIXME: add proper delay
    // delay_ms(100);
    for(volatile uint32_t cnt = 0; cnt < 100; ++cnt);
}

void St7789::init()
{
    send_cmd(0x36);
    send_data(0x00);

    send_cmd(0x3A);
    send_data(0x05);

    send_cmd(0xB2);
    send_data(0x0C);
    send_data(0x0C);
    send_data(0x00);
    send_data(0x33);
    send_data(0x33);

    send_cmd(0xB7);  //Gate Control
    send_data(0x35);

    send_cmd(0xBB);  //VCOM Setting
    send_data(0x19);

    send_cmd(0xC0); //LCM Control
    send_data(0x2C);

    send_cmd(0xC2);  //VDV and VRH Command Enable
    send_data(0x01);
    send_cmd(0xC3);  //VRH Set
    send_data(0x12);
    send_cmd(0xC4);  //VDV Set
    send_data(0x20);

    send_cmd(0xC6);  //Frame Rate Control in Normal Mode
    send_data(0x0F);
    
    send_cmd(0xD0);  // Power Control 1
    send_data(0xA4);
    send_data(0xA1);

    send_cmd(0xE0);  //Positive Voltage Gamma Control
    send_data(0xD0);
    send_data(0x04);
    send_data(0x0D);
    send_data(0x11);
    send_data(0x13);
    send_data(0x2B);
    send_data(0x3F);
    send_data(0x54);
    send_data(0x4C);
    send_data(0x18);
    send_data(0x0D);
    send_data(0x0B);
    send_data(0x1F);
    send_data(0x23);

    send_cmd(0xE1);  //Negative Voltage Gamma Control
    send_data(0xD0);
    send_data(0x04);
    send_data(0x0C);
    send_data(0x11);
    send_data(0x13);
    send_data(0x2C);
    send_data(0x3F);
    send_data(0x44);
    send_data(0x51);
    send_data(0x2F);
    send_data(0x1F);
    send_data(0x1F);
    send_data(0x20);
    send_data(0x23);

    send_cmd(0x21);  //Display Inversion On
    send_cmd(0x11);  //Sleep Out
    send_cmd(0x29);  //Display On
}

void St7789::send_cmd(uint8_t cmd)
{
    this->data_command.clear();
    this->spi.send(&cmd, 1);
}

void St7789::send_data(uint8_t data)
{
    this->data_command.set();
    this->spi.send(&data, 1);
}

void St7789::clear(uint16_t Color)
{
    unsigned int odd = 0;
    std::array<uint8_t, 240> line;
    for(auto it = line.begin(); it != line.end(); ++it) {
        if(odd % 2 == 0) {
            *it = static_cast<uint8_t>((Color >> 8) & 0xff);
        } else {
            *it = static_cast<uint8_t>(Color & 0xff);
        }
        odd++;
    }

    set_windows(0, 0, 240, 240);
    this->data_command.set();
    for(unsigned int i = 0; i < 240 * 2; i++) {
        this->spi.send(line.data(), line.size());
    }
}

void St7789::set_windows(uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end)
{
    //set the X coordinates
    send_cmd(0x2A);
    send_data((x_start >> 8) & 0xFF);
    send_data(x_start & 0xFF);
    send_data(((x_end  - 1) >> 8) & 0xFF);
    send_data((x_end  - 1) & 0xFF);

    //set the Y coordinates
    send_cmd(0x2B);
    send_data((y_start >> 8) & 0xFF);
    send_data(y_start & 0xFF);
    send_data(((y_end  - 1) >> 8) & 0xFF);
    send_data((y_end  - 1) & 0xFF);

    send_cmd(0X2C);
}
