/*
 * gfx_core.c — Sprinter graphics: common state + setup/teardown API.
 *
 * This module owns the variables that select which graphics page is
 * visible vs drawn into, which W3 bank is mapped during gfx writes,
 * and the row-base CPU address.  All mode-specific primitives (256-
 * color in gfx_raw_256.c / gfx_256.c, 16-color in gfx_raw_16.c /
 * gfx_16.c, text in gfx_text_*.c) read this state via extern.
 *
 * Public API:
 *   gfx_init / gfx_done          — switch video mode, restore previous
 *   gfx_set_visible_page / get   — ESTEX $54 SELPAGE wrapper + cached value
 *   gfx_set_draw_page    / get   — updates _gfx_addr_base for the new page
 *   gfx_set_bank         / get   — sets the W3 page byte (0x50..0x5F)
 *   gfx_wait_vsync               — EI; HALT until next frame interrupt
 *   (palette wrappers live in gfx_palette.c, backed by libc/video/palette.c)
 *
 * Shared state (extern from this file):
 *   _gfx_addr_base   — 0xC000 for page 0, 0xC140 for page 1.  Every
 *                      mode-specific primitive uses this instead of a
 *                      hard-coded 0xC000 so the same code targets the
 *                      currently-selected draw page.
 *   _gfx_bank        — read by _gfx_w3_video_begin in gfx_raw_common.c
 *                      to map the right W3 page.
 */

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

/* From conio's videomode_raw.c — bypasses set_videotextmode's text-only
 * validation so gfx_init can move INTO graphics modes. */
extern uint8_t _videomode_raw_get(void);
extern int     _videomode_raw_set(uint8_t mode);

/* ---- Shared graphics state --------------------------------------- */

/* Cached values of the SELPAGE state.  Page numbers are 0 or 1. */
static uint8_t _gfx_visible_page = 0;
static uint8_t _gfx_draw_page    = 0;

/* The W3 page byte (0x50..0x5F) — see memory/sprinter_vram_transparency.md
 * for the bit-meanings (0x50 normal, 0x54 temp, 0x58 transparent, 0x5C
 * both).  Read by _gfx_w3_video_begin in gfx_raw_common.c. */
uint8_t _gfx_bank = 0x50;

/* CPU address of column 0 in the current draw page.  Each VRAM row is
 * 1024 bytes wide — page 0 occupies bytes 0..319 (CPU 0xC000+), page 1
 * occupies 320..639 (CPU 0xC140+), and the remaining 384 bytes hold
 * mode descriptors / palette data we don't touch.  Updated by
 * gfx_set_draw_page; read by every primitive's raw helper. */
uint16_t _gfx_addr_base = 0xC000;

/* ---- gfx_init / gfx_done ----------------------------------------- */

uint8_t gfx_init(uint8_t mode, uint8_t page)
{
    uint8_t prev = _videomode_raw_get();
    _videomode_raw_set(mode);
    _gfx_bank = 0x50;
    gfx_set_visible_page(page);
    gfx_set_draw_page(page);
    return prev;
}

void gfx_done(uint8_t mode)
{
    _videomode_raw_set(mode);
}

/* ---- Visible page (ESTEX $54 SELPAGE) ---------------------------- *
 *
 * Direct OUT to port 0xC9 bit 0 toggles the "screen mode page" register
 * but doesn't notify DSS's display bookkeeping, leaving the screen in
 * an inconsistent state (one of the swaps shows as black).  Going
 * through the syscall keeps DSS happy.
 */
void gfx_set_visible_page(uint8_t page)
{
    _gfx_visible_page = page & 1;
    __asm
        push  ix
        ld    a, (__gfx_visible_page)
        ld    b, a                       ; B = page 0/1
        ld    c, #0x54                   ; ESTEX SELPAGE
        rst   #0x10
        pop   ix
    __endasm;
}

uint8_t gfx_get_visible_page(void)
{
    return _gfx_visible_page;
}

/* ---- Draw page --------------------------------------------------- */

void gfx_set_draw_page(uint8_t page)
{
    _gfx_draw_page = page & 1;
    /* Direct constants beat (0xC000 + (cond ? 0x140 : 0)) by 3 Z80
     * instructions — SDCC doesn't fold the constant addition. */
    _gfx_addr_base = _gfx_draw_page ? 0xC140 : 0xC000;
}

uint8_t gfx_get_draw_page(void)
{
    return _gfx_draw_page;
}

/* ---- W3 bank (0x50..0x5F) ---------------------------------------- */

void gfx_set_bank(uint8_t bank)
{
    _gfx_bank = bank;
}

uint8_t gfx_get_bank(void)
{
    return _gfx_bank;
}

/* ---- Frame sync -------------------------------------------------- *
 *
 * Block until the next IM2 frame interrupt (50 Hz, programmed by DSS
 * for keyboard / cursor maintenance).  The Z80's HALT instruction
 * sleeps the CPU until the next IRQ, which DSS handles and returns
 * to the instruction after HALT — that's the start of the vertical
 * retrace window, ideal for a tear-free page swap.
 */
void gfx_wait_vsync(void) __naked
{
    __asm
        ei
        halt
        ret
    __endasm;
}

/* Palette wrappers (gfx_pal_load / gfx_pal_set / gfx_pal_get /
 * gfx_pal_get_color / gfx_pal_reset) moved to gfx_palette.c — see also
 * libc/video/palette.c for the shared low-level $A4 / $A6 implementation. */
