#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS

#include "./GDEW0102T4.h"

#include <cstring>

using namespace NicheGraphics::Drivers;

// LUTs from GxEPD2_102.cpp (GDEW0102T4 / UC8175).
static const uint8_t LUT_W_FULL[] = {
    0x60, 0x5A, 0x5A, 0x00, 0x00, 0x01, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};

static const uint8_t LUT_B_FULL[] = {
    0x90, 0x5A, 0x5A, 0x00, 0x00, 0x01, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};

static const uint8_t LUT_W_FAST[] = {
    0x60, 0x01, 0x01, 0x00, 0x00, 0x01, //
    0x80, 0x12, 0x00, 0x00, 0x00, 0x01, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};

static const uint8_t LUT_B_FAST[] = {
    0x90, 0x01, 0x01, 0x00, 0x00, 0x01, //
    0x40, 0x14, 0x00, 0x00, 0x00, 0x01, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
};

GDEW0102T4::GDEW0102T4() : UC8175(width, height, supported) {}

void GDEW0102T4::setFastConfig(FastConfig cfg)
{
    // Clamp out only clearly invalid PLL settings.
    if (cfg.reg30 < 0x05)
        cfg.reg30 = 0x05;
    fastConfig = cfg;
}

GDEW0102T4::FastConfig GDEW0102T4::getFastConfig() const
{
    return fastConfig;
}

void GDEW0102T4::configCommon()
{
    // Init path aligned with GxEPD2_GDEW0102T4 (UC8175 family).
    sendCommand(0xD2);
    sendData(0x3F);

    sendCommand(0x00);
    sendData(0x6F);

    sendCommand(0x01);
    sendData(0x03);
    sendData(0x00);
    sendData(0x2B);
    sendData(0x2B);

    sendCommand(0x06);
    sendData(0x3F);

    sendCommand(0x2A);
    sendData(0x00);
    sendData(0x00);

    sendCommand(0x30); // PLL / drive clock
    sendData(0x13);

    sendCommand(0x50); // Last border/data interval; subtle but can affect artifacts
    sendData(0x57);

    sendCommand(0x60);
    sendData(0x22);

    sendCommand(0x61);
    sendData(width);
    sendData(height);

    sendCommand(0x82); // VCOM DC setting
    sendData(0x12);

    sendCommand(0xE3);
    sendData(0x33);
}

void GDEW0102T4::configFull()
{
    sendCommand(0x23);
    sendData(LUT_W_FULL, sizeof(LUT_W_FULL));
    sendCommand(0x24);
    sendData(LUT_B_FULL, sizeof(LUT_B_FULL));

    powerOn();
}

void GDEW0102T4::configFast()
{
    uint8_t lutW[sizeof(LUT_W_FAST)];
    uint8_t lutB[sizeof(LUT_B_FAST)];
    memcpy(lutW, LUT_W_FAST, sizeof(LUT_W_FAST));
    memcpy(lutB, LUT_B_FAST, sizeof(LUT_B_FAST));

    // Second stage duration bytes are the main "darkness vs ghosting" control for this panel.
    lutW[7] = fastConfig.lutW2;
    lutB[7] = fastConfig.lutB2;

    sendCommand(0x30);
    sendData(fastConfig.reg30);

    sendCommand(0x50);
    sendData(fastConfig.reg50);

    sendCommand(0x82);
    sendData(fastConfig.reg82);

    sendCommand(0x23);
    sendData(lutW, sizeof(lutW));
    sendCommand(0x24);
    sendData(lutB, sizeof(lutB));

    powerOn();
}

void GDEW0102T4::writeOldImage()
{
    // On this panel, FULL refresh is most reliable when "old image" is all white.
    if (updateType == FULL) {
        sendCommand(0x10);
        // Use buffered writes of 0xFF to avoid per-byte SPI transactions.
        const uint16_t chunkSize = 64;
        uint8_t ffBuf[chunkSize];
        memset(ffBuf, 0xFF, sizeof(ffBuf));

        uint32_t remaining = bufferSize;
        while (remaining > 0) {
            uint16_t toSend = remaining > chunkSize ? chunkSize : static_cast<uint16_t>(remaining);
            sendData(ffBuf, toSend);
            remaining -= toSend;
        }
        return;
    }

    // FAST refresh uses differential data (previous frame as old image).
    if (previousBuffer) {
        writeImage(0x10, previousBuffer);
    } else {
        writeImage(0x10, buffer);
    }
}

void GDEW0102T4::finalizeUpdate()
{
    // Keep panel out of deep-sleep between updates for better reliability of repeated FAST refresh.
    powerOff();
}

#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
