/*
 * mouse.c — Sprinter mouse driver wrappers (RST 30h).
 *
 * All calls use the same pattern as ESTEX (push/pop IX around the RST)
 * since the driver doesn't promise to preserve registers either.
 */

#include <mouse.h>

/* Scratch for READ_STATE — RST 30h clobbers HL/DE/A so we can't keep the
 * state pointer in HL across the call.  Explicit `= 0` so SDCC reserves
 * real BSS storage — see memory/sdcc_static_storage_gotcha.md. */
static uint16_t mb_x = 0, mb_y = 0;
static uint8_t  mb_buttons = 0;

int mouse_init(void) __naked
{
    __asm
        push  ix
        ld    c, #0x00              ; INITIALIZATION
        rst   #0x30                 ; MOUSE
        pop   ix
        jr    c, _mi_err
        ld    de, #0
        ret
    _mi_err:
        ld    de, #-1
        ret
    __endasm;
}

void mouse_show(void) __naked
{
    __asm
        push  ix
        ld    c, #0x01              ; SHOW MOUSE CURSOR
        rst   #0x30                 ; MOUSE
        pop   ix
        ret
    __endasm;
}

void mouse_hide(void) __naked
{
    __asm
        push  ix
        ld    c, #0x02              ; HIDE MOUSE CURSOR
        rst   #0x30                 ; MOUSE
        pop   ix
        ret
    __endasm;
}

void mouse_refresh(void) __naked
{
    __asm
        push  ix
        ld    c, #0x83              ; MOUSE REFRESH
        rst   #0x30                 ; MOUSE
        pop   ix
        ret
    __endasm;
}

void mouse_read(mouse_state_t *st) __naked
{
    (void)st;
    __asm
        ;; HL = state ptr on entry.
        push  ix
        push  hl                    ; stash ptr across RST
        ld    c, #0x03              ; READ MOUSE STATE 
        rst   #0x30                 ; MOUSE
        ;; Returns: A=buttons, HL=x, DE=y (CF=err but we ignore here)
        ld    (_mb_x), hl
        ld    (_mb_y), de
        ld    (_mb_buttons), a
        pop   hl                    ; restore state ptr
        pop   ix

        ;; Copy scratch → *st.  Struct layout: x(2), y(2), buttons(1).
        ld    de, (_mb_x)
        ld    (hl), e
        inc   hl
        ld    (hl), d
        inc   hl
        ld    de, (_mb_y)
        ld    (hl), e
        inc   hl
        ld    (hl), d
        inc   hl
        ld    a, (_mb_buttons)
        ld    (hl), a
        ret
    __endasm;
}

void mouse_goto(int x, int y) __naked
{
    (void)x; (void)y;
    __asm
        ;; HL = x, DE = y.
        push  ix
        ld    c, #0x04              ; GOTO MOUSE CURSOR
        rst   #0x30                 ; MOUSE
        pop   ix
        ret
    __endasm;
}

void mouse_bounds_x(int xmin, int xmax) __naked
{
    (void)xmin; (void)xmax;
    __asm
        ;; HL = xmin, DE = xmax.
        push  ix
        ld    c, #0x08              ; HORZ BOUNDS
        rst   #0x30                 ; MOUSE
        pop   ix
        ret
    __endasm;
}

void mouse_bounds_y(int ymin, int ymax) __naked
{
    (void)ymin; (void)ymax;
    __asm
        ;; HL = ymin, DE = ymax.
        push  ix
        ld    c, #0x07              ; VERT BOUNDS
        rst   #0x30                 ; MOUSE
        pop   ix
        ret
    __endasm;
}

/* ---- $09 LOAD CURSOR + $0B RETURN CURSOR ---------------------- */
/* Scratch for the IX-passing convention — RST 30h takes the bitmap
 * pointer in IX, so we have to set it up explicitly.
 *
 * Initialised to 0 so SDCC reserves real BSS storage — uninitialised
 * `static uint8_t` declarations can coalesce to a single address and
 * stomp on each other.  See memory/sdcc_static_storage_gotcha.md. */
static uint16_t mc_image  = 0;
static uint8_t  mc_width  = 0;
static uint8_t  mc_height = 0;
static uint8_t  mc_hot_x  = 0;
static uint8_t  mc_hot_y  = 0;
/* Saved struct pointer for mouse_get_cursor.  SDCC __sdcccall(1) passes
 * `c` in HL, then stashes it in DE around the inline asm.  Our asm
 * clobbers DE (driver returns hot_y/hot_x in D/E) so the post-asm
 * `c->width = ...` writes would otherwise land at a garbage address.
 * We park the pointer in BSS instead so SDCC re-fetches it from
 * memory after the asm. */
static mouse_cursor_t *mc_dest = 0;

void mouse_load_cursor(const mouse_cursor_t *c)
{
    /* Copy fields out of the C struct into our scratch globals so the
     * asm side has well-known names. */
    mc_image  = (uint16_t)(uintptr_t)c->image;
    mc_width  = c->width;
    mc_height = c->height;
    mc_hot_x  = c->hot_x;
    mc_hot_y  = c->hot_y;
    __asm
        push  ix
        ld    ix, (_mc_image)
        ld    a, (_mc_height)
        ld    h, a
        ld    a, (_mc_width)
        ld    l, a
        ld    a, (_mc_hot_y)
        ld    d, a
        ld    a, (_mc_hot_x)
        ld    e, a
        ld    b, #0
        ld    c, #0x09              ; LOAD MOUSE CURSOR
        rst   #0x30                 ; MOUSE
        pop   ix
    __endasm;
}

void mouse_get_cursor(mouse_cursor_t *c)
{
    mc_dest  = c;                                 /* park ptr in BSS */
    mc_image = (uint16_t)(uintptr_t)c->image;
    __asm
        push  ix
        ld    ix, (_mc_image)         ; IX = bitmap buffer from caller
        ld    c, #0x0B                ; RETURN CURSOR
        rst   #0x30                   ; mouse driver, NOT ESTEX
        ;; Returns: H=height, L=width, D=hot_y, E=hot_x.
        ld    a, h
        ld    (_mc_height), a
        ld    a, l
        ld    (_mc_width), a
        ld    a, d
        ld    (_mc_hot_y), a
        ld    a, e
        ld    (_mc_hot_x), a
        pop   ix
    __endasm;
    /* Re-fetch the struct pointer from BSS — `c` (kept in DE by SDCC
     * around the inline asm) was clobbered by the RST 30h above. */
    mouse_cursor_t *p = mc_dest;
    p->width  = mc_width;
    p->height = mc_height;
    p->hot_x  = mc_hot_x;
    p->hot_y  = mc_hot_y;
}

/* ---- $0E / $0F SENSITIVITY ------------------------------------ */
/* GET returns H=vert, L=horz in HL.  We expose the two halves as
 * separate getters so the simple "uint8_t" return ABI works cleanly. */

static int ms_query(void) __naked
{
    __asm
        push  ix
        ld    c, #0x0E              ; GET SENSITIVITY
        rst   #0x30                 ; MOUSE
        ld    d, h
        ld    e, l
        pop   ix
        ret
    __endasm;
}

uint8_t mouse_get_sensitivity_x(void)
{
    return (uint8_t)(ms_query() & 0xFF);     /* E = horz */
}

uint8_t mouse_get_sensitivity_y(void)
{
    return (uint8_t)(ms_query() >> 8);       /* D = vert */
}

void mouse_set_sensitivity(uint8_t horz, uint8_t vert) __naked
{
    (void)horz; (void)vert;
    /* Pack into HL: H=vert, L=horz. */
    __asm
        push  ix
        ld    h, l
        ld    l, a
        ld    c, #0x0F              ; SET SENSITIVITY
        rst   #0x30                 ; MOUSE
        pop   ix
    __endasm;
}

/* ---- $81 CHANGE VIDEO MODE ------------------------------------ */
/* SDCC __sdcccall(1): single uint8_t arg arrives in A. */
void mouse_video_mode_changed(uint8_t mode) __naked
{
    (void)mode;
    __asm
        push  ix
        ;; A already holds the mode byte (from SDCC ABI).
        ld    c, #0x81              ; CHANGE VIDEO MODE
        rst   #0x30                 ; MOUSE
        pop   ix
        ret
    __endasm;
}

/* CURSOR_TEXT_MODES ($0A):
 *   B = 0
 *   H = AND symbol mask    L = XOR symbol mask
 *   D = AND attribute mask E = XOR attribute mask
 *
 * SDCC __sdcccall(1) gives us:
 *   sym_and  in L (low byte of HL arg)
 *   sym_xor  in E (low byte of DE arg)
 *   attr_and at [SP+2]
 *   attr_xor at [SP+3]
 */
void mouse_text_cursor(uint8_t sym_and, uint8_t sym_xor,
                       uint8_t attr_and, uint8_t attr_xor) __naked
{
    (void)sym_and; (void)sym_xor; (void)attr_and; (void)attr_xor;
    __asm
        ;; SDCC __sdcccall(1) for 4×uint8_t args:
        ;;   arg1 sym_and  → A
        ;;   arg2 sym_xor  → L
        ;;   arg3 attr_and → stack low byte (packed into HL.L on caller, push HL)
        ;;   arg4 attr_xor → stack high byte (HL.H pushed by caller)
        pop   iy                    ; return address
        pop   bc                    ; C = attr_and (low), B = attr_xor (high)

        push  ix
        ;; Target: H=sym_and, L=sym_xor, D=attr_and, E=attr_xor, B=0
        ld    h, a                  ; H = sym_and (from A)
                                    ; L already holds sym_xor
        ld    d, c                  ; D = attr_and
        ld    e, b                  ; E = attr_xor
        ld    b, #0
        ld    c, #0x0A              ; CURSOR TEXT MODE
        rst   #0x30                 ; MOUSE
        pop   ix
        jp    (iy)
    __endasm;
}
