#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS

#include "./BaseUIEInkDisplay.h"

#include "configuration.h"
#include "main.h"

using namespace NicheGraphics;

BaseUIEInkDisplay::BaseUIEInkDisplay(Drivers::EInk *driver, uint8_t rotation) : driver(driver), rotation(rotation & 0x3)
{
    this->geometry = GEOMETRY_RAWMODE;

    // BaseUI draws in UI orientation. Physical panel dimensions are swapped for 90°/270°.
    const bool swap = (this->rotation == 1) || (this->rotation == 3);
    this->displayWidth = swap ? driver->height : driver->width;
    this->displayHeight = swap ? driver->width : driver->height;

    // OLEDDisplay indexes buffer[x + (y/8) * displayWidth]
    this->displayBufferSize = displayWidth * ((displayHeight + 7) / 8);

    // Panel-native row-major buffer
    panelRowBytes = ((driver->width - 1) / 8) + 1;
    panelBufferSize = panelRowBytes * driver->height;
    panelBuffer = new uint8_t[panelBufferSize];
    memset(panelBuffer, 0xFF, panelBufferSize); // All white
}

BaseUIEInkDisplay::~BaseUIEInkDisplay()
{
    delete[] panelBuffer;
}

bool BaseUIEInkDisplay::connect()
{
    LOG_INFO("Init BaseUI E-Ink (%u x %u, rot %u)", driver->width, driver->height, rotation);
    return true;
}

void BaseUIEInkDisplay::addFrameFlag(frameFlagTypes flag)
{
    frameFlags = (frameFlagTypes)(frameFlags | flag);
}

void BaseUIEInkDisplay::setDisplayResilience(uint8_t fastPerFull, float stressMultiplier)
{
    this->fastPerFull = (fastPerFull == 0) ? 1 : fastPerFull;
    this->stressMultiplier = stressMultiplier;
}

void BaseUIEInkDisplay::joinAsyncRefresh()
{
    if (driver->busy())
        driver->await();
}

// OLEDDisplayUi tick path. Honours rate-limit unless flags demand otherwise.
void BaseUIEInkDisplay::display()
{
    const bool demandFast = frameFlags & DEMAND_FAST;
    const bool cosmetic = frameFlags & COSMETIC;
    const bool unlimitedFast = frameFlags & UNLIMITED_FAST;

    if (!demandFast && !cosmetic && !unlimitedFast) {
        if (!forceDisplay(lastDrawMsec == 0 ? 0 : 1000))
            return;
        return;
    }

    forceDisplay(0);
}

// Keyframe path. Returns true if a frame was pushed (sets lastDrawMsec).
bool BaseUIEInkDisplay::forceDisplay(uint32_t msecLimit)
{
    const uint32_t now = millis();
    if (lastDrawMsec != 0 && (now - lastDrawMsec) < msecLimit)
        return false;

    const bool blocking = frameFlags & BLOCKING;
    Drivers::EInk::UpdateTypes type = decide();

    // Don't pile frames on top of a running update - wait it out.
    if (driver->busy())
        driver->await();

    const bool pushed = commit(type, blocking);
    if (pushed)
        lastDrawMsec = now;

    // Reset flags for next frame
    frameFlags = BACKGROUND;
    return pushed;
}

bool BaseUIEInkDisplay::commit(Drivers::EInk::UpdateTypes type, bool blocking)
{
    uint32_t hash = repack();

    // Skip if frame unchanged. Exception: caller explicitly wants a refresh (COSMETIC or FULL).
    if (hash == lastHash && type != Drivers::EInk::UpdateTypes::FULL && lastDrawMsec != 0)
        return false;
    lastHash = hash;

    // Fall back to FULL on panels that don't advertise FAST support.
    if (type == Drivers::EInk::UpdateTypes::FAST && !driver->supports(Drivers::EInk::UpdateTypes::FAST))
        type = Drivers::EInk::UpdateTypes::FULL;

    driver->update(panelBuffer, type);

    if (blocking)
        driver->await();
    return true;
}

Drivers::EInk::UpdateTypes BaseUIEInkDisplay::decide()
{
    typedef Drivers::EInk::UpdateTypes UT;

    const bool unlimitedFast = frameFlags & UNLIMITED_FAST;

    // Explicit flag wins outright
    if (frameFlags & COSMETIC) {
        fullRefreshDebt = max(fullRefreshDebt - 1.0f, 0.0f);
        return UT::FULL;
    }
    if (frameFlags & DEMAND_FAST) {
        if (!unlimitedFast) {
            fullRefreshDebt += (fullRefreshDebt < 1.0f) ? (1.0f / fastPerFull) : (stressMultiplier * (1.0f / fastPerFull));
        }
        return UT::FAST;
    }

    const bool explicitFast = frameFlags & RESPONSIVE;

    if (explicitFast || unlimitedFast) {
        if (!unlimitedFast) {
            fullRefreshDebt += (fullRefreshDebt < 1.0f) ? (1.0f / fastPerFull) : (stressMultiplier * (1.0f / fastPerFull));
        }
        return UT::FAST;
    }

    // BACKGROUND / unspecified: let debt decide
    if (fullRefreshDebt >= 1.0f) {
        fullRefreshDebt = max(fullRefreshDebt - 1.0f, 0.0f);
        return UT::FULL;
    }
    fullRefreshDebt += 1.0f / fastPerFull;
    return UT::FAST;
}

uint32_t BaseUIEInkDisplay::repack()
{
    memset(panelBuffer, 0xFF, panelBufferSize); // start all-white

    const uint16_t pw = driver->width;
    const uint16_t ph = driver->height;

    // OLEDDisplay buffer: byte = buffer[x + (y/8) * displayWidth]; bit = 1 << (y & 7); 1 = black
    // Niche buffer:       byte = (y * panelRowBytes) + (x/8);      bit = 1 << (7 - x%8); 1 = white
    for (uint16_t oy = 0; oy < displayHeight; oy++) {
        for (uint16_t ox = 0; ox < displayWidth; ox++) {
            const uint8_t b = buffer[ox + (oy / 8) * displayWidth];
            const bool isBlack = b & (1 << (oy & 7));

            uint16_t px, py;
            switch (rotation) {
            case 1: // 90° CW: OLED (ox,oy) → panel (pw-1-oy, ox)
                px = pw - 1 - oy;
                py = ox;
                break;
            case 2: // 180°
                px = pw - 1 - ox;
                py = ph - 1 - oy;
                break;
            case 3: // 270° CW
                px = oy;
                py = ph - 1 - ox;
                break;
            case 0:
            default:
                px = ox;
                py = oy;
                break;
            }

            if (px >= pw || py >= ph)
                continue;

            const uint32_t byteNum = (py * panelRowBytes) + (px / 8);
            const uint8_t bitNum = 7 - (px % 8);
            if (isBlack)
                panelBuffer[byteNum] &= ~(1 << bitNum);
            else
                panelBuffer[byteNum] |= (1 << bitNum);
        }
    }

    // FNV-1a
    uint32_t h = 2166136261u;
    for (uint32_t i = 0; i < panelBufferSize; i++) {
        h ^= panelBuffer[i];
        h *= 16777619u;
    }
    return h;
}

#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
