/*
 * gfx_raw_256.c — 256-colour (mode 0x81) raw primitives.
 *
 * "Raw" = W3-naive: caller must have W3 mapped to the video bank and
 * interrupts disabled (typically via _gfx_w3_video_begin/end from
 * gfx_raw_common.c).  Composite primitives in gfx_256.c wrap one
 * begin/end around many raw calls so the W3 dance is amortised across
 * the whole drawing operation.
 *
 * Addressing (mode 0x81): pixel (x, y) lives at CPU 0xC000 + x with
 * Port_Y (0x89) = y.  _gfx_addr_base is 0xC000 for draw page 0 and
 * 0xC140 for draw page 1 — see gfx_core.c.
 *
 * Accelerator opcodes (docs/converted/accel_r.txt):
 *   LD D,D (0x52)  enter "set block size" mode; the NEXT byte is the
 *                  length operand and MUST follow LD A immediate (0x3E).
 *   LD C,C (0x49)  horizontal Fill mode (LD (HL),A fills n bytes)
 *   LD E,E (0x5B)  vertical   Fill mode (auto-increments Port_Y)
 *   LD B,B (0x40)  disable accelerator
 *
 * The block-length byte uses SMC — we patch the immediate at runtime,
 * then run the accel sequence.  HOME is RAM after DSS loads us, so
 * SMC inside our own .EXE is safe.
 */

#include <gfx.h>
#include <stdint.h>

extern uint16_t _gfx_addr_base;

/* ---- Scratch shared across asm helpers --------------------------- *
 * Single-threaded — no reentrancy.  GFX runs with interrupts off
 * between _gfx_w3_video_begin and _gfx_w3_video_end. */
static uint8_t  acc_color;
static uint8_t  acc_y;
static uint8_t  acc_len;       /* 0 means 256 — the accel convention */
static uint16_t acc_addr;

/* Putpixel scratch — separate from acc_* so a putpixel inside a
 * Bresenham loop doesn't trample on outer accel state. */
static uint8_t  _gfx_pp_y;
static uint16_t _gfx_pp_addr;
static uint8_t  _gfx_pp_color;

/* ---- inner accel bursts ------------------------------------------ *
 *
 * Pre: W3 mapped to the video bank, DI active.  Caller wraps a sequence
 * of bursts in a single begin/end pair. */

/* Horizontal fill: acc_len bytes at acc_addr on row acc_y. */
static void hfill_chunk(void) __naked
{
    __asm
        ;; Patch the LD A,#n immediate (operand byte) with acc_len.
        ld    a, (_acc_len)
        ld    (_hfill_len_imm), a

        ld    a, (_acc_y)
        out   (#0x89), a        ; Port_Y = y

        ;; Pre-load colour into C and dest into HL before arming accel.
        ld    a, (_acc_color)
        ld    c, a
        ld    hl, (_acc_addr)

        ;; --- ACCEL SEQUENCE ---
        ld    d, d              ; 0x52 — set block size mode
        ld    a, #0             ; 0x3E nn — block size (nn patched above)
    _hfill_len_imm = . - 1
        ld    c, c              ; 0x49 — horizontal Fill
        ld    a, c              ; 0x79 — A = colour (NOT another ld a,#n)
        ld    (hl), a           ; fires accel; fills acc_len bytes
        ld    b, b              ; 0x40 — disable
        ret
    __endasm;
}

/* Vertical fill: acc_len pixels at column acc_addr, top row acc_y.
 * The accel auto-increments Port_Y as it paints down the column. */
static void vfill_chunk(void) __naked
{
    __asm
        ld    a, (_acc_len)
        ld    (_vfill_len_imm), a

        ld    a, (_acc_y)
        out   (#0x89), a        ; starting Y

        ld    a, (_acc_color)
        ld    c, a
        ld    hl, (_acc_addr)

        ld    d, d              ; 0x52 — set block size
        ld    a, #0             ; immediate length (patched)
    _vfill_len_imm = . - 1
        ld    e, e              ; 0x5B — vertical Fill
        ld    a, c              ; A = colour
        ld    (hl), a           ; fires accel
        ld    b, b              ; 0x40 — disable
        ret
    __endasm;
}

/* ---- Raw primitives (W3-naive, composable) ----------------------- *
 *
 * These do NO DI/W3 setup — caller wraps a sequence with one
 * _gfx_w3_video_begin / _gfx_w3_video_end pair. */

void _gfx_putpixel256_raw(int x, int y, uint8_t color)
{
    if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return;
    _gfx_pp_y     = (uint8_t)y;
    _gfx_pp_addr  = (uint16_t)(_gfx_addr_base + (unsigned)x);
    _gfx_pp_color = color;
    __asm
        ld    a, (__gfx_pp_y)
        out   (#0x89), a
        ld    hl, (__gfx_pp_addr)
        ld    a, (__gfx_pp_color)
        ld    (hl), a
    __endasm;
}

void _gfx_hline256_raw(int x, int y, int len, uint8_t color)
{
    if ((unsigned)y >= GFX_HEIGHT) return;
    if (x < 0)               { len += x; x = 0; }
    if (x >= GFX_WIDTH)      return;
    if (x + len > GFX_WIDTH) len = GFX_WIDTH - x;
    if (len <= 0)            return;

    acc_color = color;
    acc_y     = (uint8_t)y;
    acc_addr  = (uint16_t)(_gfx_addr_base + (unsigned)x);

    while (len > 0) {
        int chunk = len > 256 ? 256 : len;
        acc_len = (chunk == 256) ? 0 : (uint8_t)chunk;
        hfill_chunk();
        len -= chunk;
        acc_addr += chunk;
    }
}

void _gfx_vline256_raw(int x, int y, int len, uint8_t color)
{
    if ((unsigned)x >= GFX_WIDTH) return;
    if (y < 0)                { len += y; y = 0; }
    if (y >= GFX_HEIGHT)      return;
    if (y + len > GFX_HEIGHT) len = GFX_HEIGHT - y;
    if (len <= 0)             return;

    /* GFX_HEIGHT = 256 so a full column is a single accel burst. */
    acc_color = color;
    acc_y     = (uint8_t)y;
    acc_addr  = (uint16_t)(_gfx_addr_base + (unsigned)x);
    acc_len   = (len == 256) ? 0 : (uint8_t)len;
    vfill_chunk();
}

/* Clear the whole 320×256 area with `color`.  Row-major hfill:
 * 256 rows × 2 bursts each (256-byte + 64-byte) = 512 bursts. */
void _gfx_clear256_raw(uint8_t color)
{
    acc_color = color;
    for (int y = 0; y < GFX_HEIGHT; y++) {
        acc_y    = (uint8_t)y;
        acc_addr = _gfx_addr_base;            /* 256-byte burst */
        acc_len  = 0;
        hfill_chunk();
        acc_addr = (uint16_t)(_gfx_addr_base + 256);  /* 64-byte burst */
        acc_len  = 64;
        hfill_chunk();
    }
}
