/*
 * conio.c — console I/O wrappers around ESTEX kbd/screen syscalls.
 *
 *   $30 WAITKEY  — blocking read, returns scan / ASCII / modifiers
 *   $31 SCANKEY  — non-blocking poll
 *   $32 ECHOKEY  — blocking read + auto-echo to the screen
 *   $52 LOCATE   — set cursor to (D=row, E=col)
 *   $56 CLEAR    — fill a window with (A=char, B=attr)
 *   $5B PUTCHAR  — write single character (CR/LF/scroll handled by ESTEX)
 *
 * Every RST 10h is bracketed with push/pop IX (caller's frame pointer).
 */

#include <conio.h>
#include <stdint.h>
#include <errno.h>


/* Forward extern — definition is further down (after putch/cputs which
 * reference it from asm by linker-symbol name). */
static two_bytes g_text_attr = {0};
static uint8_t   pc_ch       = 0;
extern two_bytes pc_place    = {0};
static uint8_t   pc_raw_mode = 0;


// TODO - проверить - ф-ии 30h-33h (kbhit/getch/getche/getkey)
//   не должны менять IX и им можно не делать push ix / pop ix
//
char kbhit(void) __naked
{
    __asm
        push  ix
        ld    c, #0x33              ; ESTEX CTRLKEY — peeks without consuming
        rst   #0x10
        pop   ix
        ;; A=0 → no key waiting; non-zero → there is one.
        or    a, a
        ret   z
        ld    a, #0x01
        ret
    __endasm;
}

char getch(void) __naked
{
    __asm
        push  ix
        ld    c, #0x30              ; ESTEX WAITKEY (no echo)
        rst   #0x10
        pop   ix
        ;; ESTEX returns ASCII in E (and copy in A)
        ret
    __endasm;
}

char getche(void) __naked
{
    __asm
        push  ix
        ld    c, #0x32              ; ESTEX ECHOKEY (echo to console)
        rst   #0x10
        pop   ix
        ;; ESTEX returns ASCII in E (and copy in A)
        ret
    __endasm;
}

/* getkey — like getch() but exposes BOTH the ASCII value and the
 * positional scan code, so callers can distinguish extended keys
 * (arrows, F1..F12, PgUp/PgDn, Home/End, Ins/Del — all of which carry
 * ASCII == 0 from ESTEX) from plain ASCII keys.
 *
 *   return = (scan << 8) | ascii
 *
 * For plain keys: ascii holds the character, scan holds the positional
 *                 code (bit 7 set when Ctrl/Alt/Shift is held).
 * For extended keys: ascii == 0, scan identifies the key (see KEY_* in
 *                    <conio.h>).
 */
uint16_t getkey(void) __naked
{
    __asm
        push  ix
        ld    c, #0x30              ; ESTEX WAITKEY: A=ASCII, D=scan, E=ASCII
        rst   #0x10
        pop   ix
        ld    e, a                  ; E = ASCII (defensive: A is the canonical copy)
        ret                         ; __sdcccall(1) returns uint16_t in DE
    __endasm;
}

/* ---- putch / cputs: Turbo-C conio convention ---------------------- *
 * Both APPLY the current text attribute (g_text_attr).  When attr is
 * KEEP_EXIST_ATTR (>0xFF), they short-circuit to the FAST stdio path
 * (putchar / puts-like raw PCHARS).
 *
 * No '\n' to CR LF translation here — Turbo-C cputs/putch require the
 * caller to use "\r\n" explicitly.  Stdio puts/putchar do translate.
 */


/* Controls how _raw_putch treats control characters (< 0x20):
 *   0 (default) — BS/TAB/LF/CR are interpreted (no glyph output);
 *                 other chars print as glyphs via WRCHAR.
 *   1           — all characters print as glyphs, no interpretation.
 *
 * Only takes effect on the WRCHAR (attr ≤ 0xFF) path.  When
 * g_text_attr is KEEP_EXIST_ATTR, ESTEX's own PUTCHAR/PCHARS handle
 * cursor and control chars — pc_raw_mode is irrelevant. */

void set_putch_raw_mode(uint8_t mode) { pc_raw_mode = mode; }
uint8_t get_putch_raw_mode(void)      { return pc_raw_mode; }

/* ---- Internal helpers ------------------------------------------- */

/* Read current cursor into pc_row / pc_col via ESTEX CURSOR ($53). */
static void _get_cursor(void) __naked
{
    __asm
        ; push  ix
        ld    c, #0x8e              ; BIOS GetCursor
        rst   #0x08
        ld    (_pc_place), de
        ; pop   ix
        ret
    __endasm;
}

/* Move cursor to (pc_col, pc_row) via ESTEX LOCATE ($52). */
static void _set_cursor(void) __naked
{
    __asm
        ; push  ix
        ld    de, (_pc_place)
        ld    c, #0x84              ; BIOS SetCursor
        rst   #0x08
        ; pop   ix
        ret
    __endasm;
}

/* ESTEX PUTCHAR ($5B) — fast no-attr path; ESTEX handles CR/LF/scroll
 * and cursor itself.  Used when g_text_attr = KEEP_EXIST_ATTR. */
static char _bios_putchar(char ch) __naked
{
    (void)ch;
    __asm
        ;; c in A.  push af stashes it across the RST (which clobbers A).
        ;; push  ix                 ; PUTCHAR не меняет IX
        push  af
        ld    c, #0x5B              ; ESTEX PUTCHAR
        rst   #0x10
        pop   af
        ;; pop   ix
        ret
    __endasm;
}

/* Raw putch: low-level WRCHAR-based output at (pc_col, pc_row) using
 * the given attribute byte (caller has already verified that the high
 * byte of g_text_attr is zero — this function takes only the low byte).
 * Updates pc_col / pc_row per pc_raw_mode:
 *
 *   pc_raw_mode == 0: BS/TAB/LF/CR are INTERPRETED:
 *     0x08 BS  → pc_col-- (if not already 0)
 *     0x09 TAB → pc_col rounded up to next multiple of 8 (capped 80)
 *     0x0A LF  → pc_row++ (capped at 32; no glyph)
 *     0x0D CR  → pc_col = 0
 *     other    → WRCHAR + pc_col++
 *
 *   pc_raw_mode == 1: ALL characters print as glyphs via WRCHAR
 *     + pc_col++ (including 0x08, 0x09, 0x0A, 0x0D — they render as
 *     their CP437 glyphs).
 *
 * WRCHAR itself is suppressed when pc_col ≥ 80 or pc_row ≥ 32 (off-
 * screen) — coordinates [0..79] × [0..31] only.
 *
 * Does NOT call CURSOR / LOCATE — caller is expected to fetch cursor
 * once before a sequence of _raw_putch calls and write it back once
 * after, so we pay the BIOS overhead per OPERATION instead of per CHAR. */
/* Mode-0 worker: interprets BS/TAB/LF/CR, outputs other chars as glyphs. */
static void _putch_wrchar(char ch, uint8_t attr) __naked
{
    (void)ch; (void)attr;
    __asm
        ;; __sdcccall(1): ch in A, attr in L.
        ;; Dispatch on control chars while A still holds ch (cp does not
        ;; modify A).  B/C only get loaded on the output path so the
        ;; ctrl-char paths are cheaper.

        ;; ld    c, a
        ;; ld    a, (_pc_ch)                ; A = ch  (`ld a,(nn)` does not touch flags)
        ;; jr    nz, _rp0_pri

        ;; ld    a, c
        cp    #0x08
        jr    z, _rp0_bs
        cp    #0x09
        jr    z, _rp0_tab
        cp    #0x0A
        jr    z, _rp0_lf
        cp    #0x0D
        jr    z, _rp0_cr
        ;; Anything else (printable or unrecognised ctrl) → glyph.

    _rp0_pri:

        ld    c, a                  ; C = ch (save before A is clobbered)
        ld    a, (_pc_place + 1)
        cp    #32
        ret   nc                    ; off-screen bottom — silently skip
        ld    d, a                  ; D = row (ESTEX WRCHAR convention)
        ld    a, (_pc_place)
        cp    #80
        ret   nc                    ; off-screen right — silently skip        
        ld    e, a                  ; E = col
        inc   a
        ld    (_pc_place), a        ; pc_col++

    _rp0_wr:    
        ld    b, l                  ; B = attr
        ld    a, c                  ; A = ch
        push  ix
        ld    c, #0x58              ; ESTEX WRCHAR
        rst   #0x10
        pop   ix
        ret

    _rp0_bs:
        ld    a, (_pc_place)
        or    a, a
        ret   z                     ; already at col 0 — no change
        dec   a
        ld    (_pc_place), a
        ld    e, a                  ; E = col
        ld    a, (_pc_place + 1)
        ld    d, a                  ; D = row (ESTEX WRCHAR convention)
        ld    c, #0x20
        jr    _rp0_wr

    _rp0_tab:
        ld    a, (_pc_place)
        or    #0x07                 ; floor to mult of 8
        inc   a                     ; → next mult of 8
        cp    #81                   ; сравнить A с 81 (0x51)
        jr    c, _rp0_tab_skip      ; если A < 81 (т.е. A ≤ 80), пропустить загрузку
        ld    a, #80                ; иначе A > 80 → установить A = 80
    _rp0_tab_skip:
        ld    (_pc_place), a
        ret

    _rp0_lf:
        ld    a, (_pc_place + 1)
        cp    #32
        ret   nc                    ; already at bottom edge
        inc   a
        ld    (_pc_place + 1), a
        ret

    _rp0_cr:
        xor   a, a
        ld    (_pc_place), a
        ret
    __endasm;
}

/* Mode-1 worker: every byte goes through WRCHAR as a glyph. */
static void _putch_wrchar_raw(char ch, uint8_t attr) __naked
{
    (void)ch; (void)attr;
    __asm
        ;; __sdcccall(1): ch in A, attr in L.
        ld    c, a                  ; C = ch (save)
        
        ld    a, (_pc_place + 1)
        cp    #32
        ret   nc                    ; off-screen bottom — silently skip
        ld    d, a                  ; D = row
        ld    a, (_pc_place)
        cp    #80
        ret   nc                    ; off-screen right — silently skip        
        ld    e, a                  ; E = col
        inc   a
        ld    (_pc_place), a        ; pc_col++

        ld    b, l                  ; B = attr
        ld    a, c                  ; A = ch
        push  ix
        ld    c, #0x58              ; ESTEX WRCHAR
        rst   #0x10
        pop   ix
        ret
    __endasm;
}


/* ---- Public putch / cputs --------------------------------------- *
 *
 * KEEP_EXIST_ATTR (high byte != 0) → fast PUTCHAR/PCHARS through
 * ESTEX, which manages its own cursor.
 *
 * Otherwise → fetch cursor ONCE via CURSOR ($53), run one or many
 * _raw_putch calls, write cursor back ONCE via LOCATE ($52).  This
 * folds the per-char CURSOR/LOCATE pair from the old design into a
 * single pair per operation. */


char putch(char ch) __naked
{
    (void)ch;
    __asm
        ;; A = ch on entry; char return → A.
        ld    (_pc_ch), a               ; stash c (for both return and re-load)

        ld    a, (_g_text_attr)          ; A = low byte = attr
        ld    l, a                       ; L = attr

        ld    a, (_pc_raw_mode)
        or    a, a                       ; Z = (mode == 0)
        ld    a, (_pc_ch)                ; A = ch  (`ld a,(nn)` does not touch flags)
        jr    nz, _putch_use_raw
        call  __putch_wrchar
        jr    _putch_after_raw
    _putch_use_raw:
        call  __putch_wrchar_raw
    _putch_after_raw:
        call  __set_cursor
        ld    a, (_pc_ch)                ; return value
        ret

    __endasm;
}


char cputs(const char *s) __naked
{
    (void)s;
    __asm
        ;; HL = s on entry; char return → A.

        ;; NULL-check: cputs(NULL) → return 0 immediately.
        ld    a, h
        or    a, l
        ret   z

        push  ix

        call  __get_cursor

        ;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0
        ld    a, (_g_text_attr + 1)
        or    a, a
        jr    nz, _cputs_fast

        ld    a, (_pc_raw_mode)
        or    a, a
        jr    nz, _cputs_bios

        push  hl
        pop   de
        
    _cputs_wrloop:
        ld    a, (_g_text_attr)
        ld    l, a
        ld    a, (de)                ; загрузить байт
        or    a                      ; установить флаг Z, если A == 0
        jr    z, _cputs_ex           ; завершить подпрограмму (конец строки)
        push  de
        call  __putch_wrchar         ; вывести символ (A передаётся как аргумент)
        pop   de
        inc   de                     ; перейти к следующему байту
        jr    _cputs_wrloop          ; повторить

    _cputs_bios:
        ld    a, (_g_text_attr)
        ld    b, #0xFF
        ld    d, #0x0
        ld    e, a
        ld    c, #0x8B                  ; BIOS LP_PRINT_LN5
        rst   #0x08
        jr    _cputs_ex
    _cputs_fast:        
        ld    c, #0x5C                  ; ESTEX PCHARS
        rst   #0x10
    _cputs_ex:
        call  __set_cursor
        pop   ix                        ; restore callers IX
        xor   a, a                      ; return 0
        ret
    __endasm;
}

void clrscr(void) __naked
{
    __asm
        ld    a, #0x0F
        jp    _clrscr_attr
    __endasm;
}

void clrscr_attr(uint8_t attr) __naked
{
    (void)attr;
    __asm
        push  ix
        ;; SDCC __sdcccall(1): uint8_t 1st arg is in A.
        ld    b, a                  ; B = attribute (mode-fill colour)
        ld    de, #0x0000           ; top-left
        ld    hl, #0x2050           ; H=32 rows, L=80 cols
        ld    a, #0x20              ; space fill
        ld    c, #0x56              ; ESTEX CLEAR
        rst   #0x10
        pop   ix
        ret
    __endasm;
}

void gotoxy(uint8_t x, uint8_t y) __naked
{
    (void)x; (void)y;
    __asm
        ;; __sdcccall(1) 2 uint8 args: x in A, y in L.
        ;; ESTEX LOCATE ($52) wants: D = row, E = col.
        ;; push     ix
        ld       d, l                  ; D = row (y)
        ld       e, a                  ; E = col (x)
        ld       (_pc_place), de
        ld       c, #0x84              ; BIOS SetCursor
        rst      #0x08
        ;; ld       c, #0x52
        ;; rst      #0x10
        ;; pop      ix
        ret
    __endasm;
}

uint8_t wherex(void) __naked
{
    __asm
        ;; ESTEX CURSOR ($53): D = row, E = col.  Return col in DE.
        ;; push  ix
        ld       c, #0x8e              ; BIOS GetCursor
        rst      #0x08
        ;; ld       c, #0x53
        ;; rst      #0x10
        ;; pop      ix
        ld      a, e
        ld      (_pc_place), de
        ret
    __endasm;
}

uint8_t wherey(void) __naked
{
    __asm
        ;; push  ix
        ld       c, #0x8e              ; BIOS GetCursor
        rst      #0x08
        ;; ld       c, #0x53
        ;; rst      #0x10
        ;; pop      ix
        ld       a, d
        ld       (_pc_place), de
        ret
    __endasm;
}

uint16_t wherexy(void) __naked
{
    __asm
        ;; ESTEX CURSOR ($53): D = row, E = col.  Return col in DE.
        ;; push     ix
        ld       c, #0x8e              ; BIOS GetCursor
        rst      #0x08
        ;; ld       c, #0x53
        ;; rst      #0x10
        ;; pop      ix
        ld       (_pc_place), de
        ret
    __endasm;
}

/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
 *
 * SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr
 * packed and pushed on the stack as a single 16-bit value (caller does
 * `ld hl, #(attr<<8)|ch; push hl`).  Layout after CALL:
 *   [SP+0..1] = return address
 *   [SP+2]    = ch   (low half of pushed pair)
 *   [SP+3]    = attr (high half)
 * Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy).
 */
void scroll(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t direction, uint8_t clear) __naked
{
    (void)x; (void)y; (void)w; (void)h; (void)direction; (void)clear;
    __asm
        pop   iy                    ; return address
        ld    d, l                  ; D = row (y)
        ld    e, a                  ; E = col (x)
        pop   hl                    ; H = heigth(h), L = width(w)
        pop   bc                    ; C = direction, B = clear
        ld    a, b                  ; A = clear(B)
        ld    b, c                  ; B = direction(C)
        push  ix
        ld    c, #0x55              ; ESTEX SCROLL
        rst   #0x10
        pop   ix
        jp    (iy)
    __endasm;
}




/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr)
 *
 * SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr
 * packed and pushed on the stack as a single 16-bit value (caller does
 * `ld hl, #(attr<<8)|ch; push hl`).  Layout after CALL:
 *   [SP+0..1] = return address
 *   [SP+2]    = ch   (low half of pushed pair)
 *   [SP+3]    = attr (high half)
 * Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy).
 */
void wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr) __naked
{
    (void)x; (void)y; (void)ch; (void)attr;
    __asm
        pop   iy                    ; return address
        pop   bc                    ; C = ch, B = attr
        push  ix
        ld    d, l                  ; D = row (y)
        ld    e, a                  ; E = col (x)
        ld    a, c                  ; A = ch
        ld    c, #0x58              ; ESTEX WRCHAR
        rst   #0x10
        pop   ix
        jp    (iy)
    __endasm;
}

/* rdchar(int x, int y) → (attr << 8) | ch */
uint16_t rdchar(uint8_t x, uint8_t y) __naked
{
    (void)x; (void)y;
    __asm
        push  ix
        ld    d, l                  ; D = row
        ld    e, a                  ; E = col
        ld    c, #0x57              ; ESTEX RDCHAR
        rst   #0x10
        ;; A = ch, B = attr
        ld    d, b                  ; high byte → attr
        ld    e, a                  ; low byte → ch
        pop   ix
        ret
    __endasm;
}

/* Public text-mode video API — defined here so it's pulled in with the
 * rest of conio.  The raw setters/getters live in videomode_raw.c so
 * pure graphics programs can pick them up without conio's other
 * dependencies. */
extern uint8_t _videomode_raw_get(void);
extern int     _videomode_raw_set(uint8_t mode);

uint8_t gettextmode(void)
{
    return _videomode_raw_get();
}

int settextmode(uint8_t mode)
{
    /* Refuse anything that isn't a known text mode — otherwise a stray
     * GFX_MODE_* value could swap the screen out from under text I/O. */
    if (mode != TEXT_MODE_40x32 && mode != TEXT_MODE_80x32) {
        errno = EINVAL;
        return -1;
    }
    return _videomode_raw_set(mode);
}

/* ---- text attribute state ----------------------------------------
 * g_text_attr is owned by conio.c now (Turbo-C-style: stdio putchar/puts
 * are fast and attribute-free; only conio's putch/cputs/cprintf apply
 * the attribute).  Default = 0x0F (bright white on black).
 *
 *   0x00..0xFF — real attribute (4-bit FG | 3-bit BG | 1-bit blink)
 *   KEEP_EXIST_ATTR (0xFFFF) — putch/cputs fall back to fast no-attr path */

int16_t set_text_attr(int16_t attr)
{
    int16_t prev = g_text_attr.value;
    g_text_attr.value = attr;
    return prev;
}

int16_t get_text_attr(void)
{
    return g_text_attr.value;
}

/* ---- Turbo-C-style palette helpers --------------------------------
 * textcolor / textbackground touch only their nibble; the other nibble
 * (and the blink bit) are preserved.  textattr replaces the whole byte. */

void textcolor(uint8_t fg)
{
    fg = (fg & 0x07);
    g_text_attr.value = (g_text_attr.byte.low & 0xF0) | fg;
    // g_text_attr.byte.high = 0;
}

void textbackground(uint8_t bg)
{
    bg = (bg & 0x07) << 4;
    g_text_attr.value = (g_text_attr.byte.low & 0x0F) | bg;
    // g_text_attr.byte.high = 0;
}

void textattr(uint8_t attr)
{
    g_text_attr.value = (uint16_t)attr;
}

/* ---- Solid-C compatibility ---------------------------------------- */

/* Direct port I/O.  Z80 has 256 IN/OUT ports; we wrap the Z80 IN/OUT
 * opcodes with a stable C API.  Names match Solid-C / MS-DOS Turbo-C. */
uint8_t z80_inp(uint8_t port) __naked
{
    (void)port;
    __asm
        ;; SDCC __sdcccall(1): single uint8_t arg in A; uint8_t return in A.
        ld    c, a
        in    a, (c)
        ret
    __endasm;
}

void z80_outp(uint8_t port, uint8_t value) __naked
{
    (void)port; (void)value;
    __asm
        ;; __sdcccall(1): 2 uint8 args → arg1 in A, arg2 in L.
        ld    c, a                    ; C = port
        out   (c), l                  ; out (port), value
        ret
    __endasm;
}

/* cgets — Solid-C / Turbo-C style line input.
 *   buf[0] = max characters (in)
 *   buf[1] = actual count (out)
 *   buf[2..] = chars + NUL
 * Returns &buf[2]. */
char *cgets(char *buf)
{
    uint8_t maxlen = (uint8_t)buf[0];
    uint8_t n = 0;
    while (n < maxlen) {
        int ch = getche();
        if (ch == '\n' || ch == '\r') {
            putch('\r'); putch('\n');
            break;
        }
        if (ch == 8) {                /* backspace */
            if (n > 0) { n--; }
            continue;
        }
        buf[2 + n] = (char)ch;
        n++;
    }
    buf[1]     = (char)n;
    buf[2 + n] = 0;
    return &buf[2];
}
