#include "epd.h"
#include "config.h"
#include <qrcode.h>

static uint8_t s_row[EPD_WIDTH / 2];

static void wait_busy() {
    while (digitalRead(PIN_BUSY) == LOW) delay(5);
}
static void cmd(uint8_t c) {
    digitalWrite(PIN_DC, LOW);
    digitalWrite(PIN_CS, LOW);
    SPI.transfer(c);
    digitalWrite(PIN_CS, HIGH);
}
static void dat(uint8_t d) {
    digitalWrite(PIN_DC, HIGH);
    digitalWrite(PIN_CS, LOW);
    SPI.transfer(d);
    digitalWrite(PIN_CS, HIGH);
}

void epd_init() {
    digitalWrite(PIN_RST, HIGH); delay(20);
    digitalWrite(PIN_RST, LOW);  delay(2);
    digitalWrite(PIN_RST, HIGH); delay(20);
    wait_busy(); delay(30);

    cmd(0xAA);
    dat(0x49); dat(0x55); dat(0x20);
    dat(0x08); dat(0x09); dat(0x18);
    cmd(0x01); dat(0x3F);
    cmd(0x00); dat(0x5F); dat(0x69);
    cmd(0x03); dat(0x00); dat(0x54); dat(0x00); dat(0x44);
    cmd(0x05); dat(0x40); dat(0x1F); dat(0x1F); dat(0x2C);
    cmd(0x06); dat(0x6F); dat(0x1F); dat(0x17); dat(0x49);
    cmd(0x08); dat(0x6F); dat(0x1F); dat(0x1F); dat(0x22);
    cmd(0x30); dat(0x03);
    cmd(0x50); dat(0x3F);
    cmd(0x60); dat(0x02); dat(0x00);
    cmd(0x61); dat(0x03); dat(0x20); dat(0x01); dat(0xE0);
    cmd(0x84); dat(0x01);
    cmd(0xE3); dat(0x2F);
    cmd(0x04); wait_busy();
}

void epd_sleep() {
    cmd(0x02); dat(0x00); wait_busy();
    cmd(0x07); dat(0xA5);
}

static void epd_refresh() {
    cmd(0x04); wait_busy();
    cmd(0x12); dat(0x00); wait_busy();
}

void epd_fill(uint8_t color) {
    uint8_t byte = (color << 4) | color;
    cmd(0x10);
    digitalWrite(PIN_DC, HIGH);
    digitalWrite(PIN_CS, LOW);
    for (int i = 0; i < EPD_WIDTH * EPD_HEIGHT / 2; i++) {
        SPI.transfer(byte);
    }
    digitalWrite(PIN_CS, HIGH);
    epd_refresh();
}

void epd_draw_image_from_file(fs::File& f) {
    cmd(0x10);
    uint8_t buf[512];
    while (f.available()) {
        size_t n = f.read(buf, sizeof(buf));
        digitalWrite(PIN_DC, HIGH);
        digitalWrite(PIN_CS, LOW);
        SPI.writeBytes(buf, n);
        digitalWrite(PIN_CS, HIGH);
    }
    epd_refresh();
}

// Inline helper: returns the EPD color nibble for display pixel (px, py)
// given a centered QR code with cellPx pixels per module.
static inline uint8_t qr_nibble(QRCode* qr, int px, int py, int offX, int offY, int cellPx,
                                  uint8_t bg, uint8_t fg) {
    int qx = (px - offX) / cellPx;
    int qy = (py - offY) / cellPx;
    if (qx >= 0 && qx < qr->size && qy >= 0 && qy < qr->size) {
        return qrcode_getModule(qr, qx, qy) ? fg : bg;
    }
    return bg;
}

void epd_draw_qr(QRCode* qr, uint8_t cellPx, uint8_t bg, uint8_t fg) {
    int qrPx = qr->size * cellPx;
    int offX  = (EPD_WIDTH  - qrPx) / 2;
    int offY  = (EPD_HEIGHT - qrPx) / 2;

    cmd(0x10);
    for (int y = 0; y < EPD_HEIGHT; y++) {
        for (int x = 0; x < EPD_WIDTH; x += 2) {
            uint8_t hi = qr_nibble(qr, x,   y, offX, offY, cellPx, bg, fg);
            uint8_t lo = qr_nibble(qr, x+1, y, offX, offY, cellPx, bg, fg);
            s_row[x / 2] = (hi << 4) | lo;
        }
        digitalWrite(PIN_DC, HIGH);
        digitalWrite(PIN_CS, LOW);
        SPI.writeBytes(s_row, sizeof(s_row));
        digitalWrite(PIN_CS, HIGH);
    }
    epd_refresh();
}
