/*
 * conio.h — direct console I/O backed by ESTEX functions.
 *
 * Two-set output API following the Turbo-C convention:
 *
 *   stdio (fast, no attr — system default colour):
 *     putchar(c)   — one char + '\n' → CR LF translation
 *     puts(s)      — string + trailing newline
 *     printf(...)  — uses putchar internally
 *
 *   conio (slower, applies g_text_attr — set via textcolor / textbackground /
 *   textattr / set_text_attr):
 *     putch(c)     — one char, NO '\n' translation
 *     cputs(s)     — string, NO trailing newline, NO '\n' translation
 *                    (write "\r\n" yourself for line breaks)
 *     cprintf(...) — printf with attribute, via vsprintf+cputs internally
 *
 *   The conio set short-circuits to the fast path when
 *   g_text_attr == KEEP_EXIST_ATTR — useful for "I usually want a
 *   specific colour but right now don't care".
 *
 *   Other helpers (unchanged):
 *     kbhit / getch / getche  — keyboard
 *     clrscr / clrscr_attr    — clear screen
 *     gotoxy / wherex / wherey
 *     wrchar / rdchar         — direct VRAM cell access
 *     get_videotextmode / set_videotextmode (text modes only — see gfx.h
 *                              for graphics mode constants)
 *
 *   Coordinates are 0-based to match ESTEX directly.
 */

#ifndef CONIO_H
#define CONIO_H

#include <stdint.h>

char kbhit (void);
char getch (void);
char getche(void);

/* Extended-key reader. Returns (scan << 8) | ascii.  Plain ASCII keys
 * have ascii in the low byte; extended keys (arrows / F1..F12 /
 * PgUp/PgDn / Home / End / Ins / Del) carry ascii == 0 and the
 * KEY_* code in the high byte. */
uint16_t getkey(void);

/* Scan codes returned in the high byte of getkey() when the low byte
 * (ASCII) is 0.  Empirically verified in MAME — the ProgrammerManual.txt
 * "positional code" column is misleading; BIOS returns IBM-style codes
 * for the F-keys and a "5N + numpad-position" pattern for the cursor /
 * editing keys.
 *
 * Verified 2026-06-04 by reading raw getkey() output. */
#define KEY_F1     0x3B
#define KEY_F2     0x3C
#define KEY_F3     0x3D
#define KEY_F4     0x3E
#define KEY_F5     0x3F
#define KEY_F6     0x40
#define KEY_F7     0x41
#define KEY_F8     0x42
#define KEY_F9     0x43
#define KEY_F10    0x44
#define KEY_F11    0x45   /* not verified */
#define KEY_F12    0x46   /* not verified */
#define KEY_END    0x51
#define KEY_DOWN   0x52
#define KEY_PGDN   0x53
#define KEY_LEFT   0x54
#define KEY_RIGHT  0x56
#define KEY_HOME   0x57
#define KEY_UP     0x58
#define KEY_PGUP   0x59
#define KEY_INS    0x50   /* numpad 0; not verified */
#define KEY_DEL    0x55   /* numpad 5/.; not verified */

/* kbd_mod_state — live-состояние модификаторов ПРЯМО СЕЙЧАС (ESTEX
 * CTRLKEY $33h), а не событие из буфера: держится столько, сколько
 * клавиша реально зажата (в отличие от kbhit/getch/getkey).  Покрывает
 * ТОЛЬКО Shift/Ctrl/Alt/Rus-Lat/Lock-клавиши — обычные клавиши
 * (стрелки и т.п.) у ESTEX live-state не имеют, см. <kbd_raw.h>.
 *
 * Возврат: (mode << 8) | shift — расшифровка через KBD_MOD_* ниже. */
uint16_t kbd_mod_state(void);

#define KBD_MOD_RUSLAT    0x8000
#define KBD_MOD_NUMLOCK   0x0800
#define KBD_MOD_SCRLOCK   0x0400
#define KBD_MOD_INSERT    0x0200
#define KBD_MOD_CAPSLOCK  0x0100
#define KBD_MOD_LSHIFT    0x0080
#define KBD_MOD_RSHIFT    0x0040
#define KBD_MOD_CTRL      0x0020
#define KBD_MOD_ALT       0x0010
#define KBD_MOD_LCTRL     0x0008
#define KBD_MOD_LALT      0x0004
#define KBD_MOD_RCTRL     0x0002
#define KBD_MOD_RALT      0x0001

typedef union {
    uint16_t value;
    struct {
        uint8_t low;
        uint8_t high;
    } byte;
} two_bytes;



char putch (char c);
char cputs (const char *s);
int  cprintf(const char *fmt, ...);
void clrscr(void);
void gotoxy(uint8_t x, uint8_t y);


/* Solid-C compatibility helpers. */
#define home()       gotoxy(0, 0)
#define inp(port)    z80_inp(port)
#define outp(p, v)   z80_outp((p), (v))
#define enable()     __asm__("ei")
#define disable()    __asm__("di")
uint8_t z80_inp(uint8_t port);
void    z80_outp(uint8_t port, uint8_t value);

/* Read a line from the console (no echo control — uses getche).
 * `buf[0]` must be the max length; on return `buf[1]` is the actual
 * length and `buf[2..]` is the NUL-terminated string. */
char *cgets(char *buf);

/* Cursor query via ESTEX $53 CURSOR.  Returns 0-based row/column. */
uint8_t  wherex (void);
uint8_t  wherey (void);
uint16_t wherexy(void); // high byte = Y, low byte = X coords.

void scroll(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);

/* Direct character/attribute screen access (ESTEX $57 / $58).
 *   wrchar — write char + attribute at (x, y); does NOT advance the cursor
 *            and does NOT interpret control characters.  Useful for
 *            coloured text and for painting the last-column-last-row cell
 *            without triggering ESTEX's auto-scroll on PCHARS/PUTCHAR.
 *   rdchar — read both character and attribute back; returned as
 *            (attr<<8 | ch). */
void     wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr);
uint16_t rdchar(uint8_t x, uint8_t y);

/* clrscr_attr — wipe the entire 80x32 screen using the given attribute
 * byte (fill character = space).  Companion to clrscr() which uses the
 * default attr 0x0F (bright white on black). */
void clrscr_attr(uint8_t attr);

/* Text video-mode control (ESTEX $50 / $51).  These constants and helpers
 * cover ONLY text modes; graphics modes live in <gfx.h> as GFX_MODE_*.
 * `set_videotextmode()` validates that the argument is a known text mode
 * (so calling code that includes conio.h alone cannot accidentally switch
 * the screen into graphics — that requires <gfx.h>). */
#define TEXT_MODE_40x32  0x02
#define TEXT_MODE_80x32  0x03

uint8_t gettextmode(void);
int     settextmode(uint8_t mode);  /* 0 OK, -1 + errno on bad mode */

/* ------------------------------------------------------------------ *
 * Text-output attribute (used by the conio set: putch / cputs / cprintf).
 *
 *   textcolor(c)        — set foreground (preserves background+blink)
 *   textbackground(c)   — set background (preserves foreground+blink)
 *   textattr(a)         — replace the whole attribute byte
 *   set_text_attr(a)    — alias to textattr but returns the previous value
 *   get_text_attr()     — read current attribute
 *
 * Range 0x00..0xFF is a real attribute byte; KEEP_EXIST_ATTR (0xFFFF)
 * means "don't touch attributes — fall through to the fast no-attr path"
 * (so putch becomes equivalent to putchar etc.).
 *
 * Default at startup = 0x0F (bright white on black).
 *
 * NOTE: stdio's putchar / puts / printf IGNORE this — they always use
 * whatever ESTEX has cached for the cursor cell.  Use the conio set
 * (putch / cputs / cprintf) for coloured output.
 * ------------------------------------------------------------------ */
#define KEEP_EXIST_ATTR  0xFFFF

/* Внутреннее состояние (хранилище: libc/conio/_g_text_attr.c и
 * _pc_raw_mode.c) — НЕ трогать напрямую, только через функции ниже.
 * Объявлено здесь, чтобы ТОНКИЕ аксессоры были inline: чистый C99
 * `inline` БЕЗ static — SDCC инлайнит вызов и не эмитит standalone-
 * тело (см. memory/sdcc_inline_codegen_findings).  «Толстые» (RMW-
 * маски textcolor/textbackground, set_text_attr с возвратом прежнего)
 * НЕ inline — замерено: тело 12+ байт при 2+ сайтах вызова крупнее
 * call+общее тело (conio2 +13 Б). */
extern two_bytes g_text_attr;
extern uint8_t   pc_raw_mode;

void textcolor(uint8_t fg);
void textbackground(uint8_t bg);
inline void textattr(uint8_t attr) { g_text_attr.value = (uint16_t)attr; }

int16_t set_text_attr(int16_t attr);   /* возвращает прежнее значение */
inline int16_t get_text_attr(void) { return g_text_attr.value; }

/* Control how putch / cputs / cprintf (the WRCHAR path) treat
 * control characters (< 0x20):
 *
 *   mode = 0 (default) — interpret BS/TAB/LF/CR:
 *     0x08 BS  → pc_col-- (clamped at 0)
 *     0x09 TAB → pc_col rounded up to next multiple of 8 (clamped 80)
 *     0x0A LF  → pc_row++ (clamped at 32; no glyph emitted)
 *     0x0D CR  → pc_col = 0
 *
 *   mode = 1 — print EVERY character as a CP437 glyph (no
 *              interpretation; useful e.g. for drawing box-drawing
 *              characters that overlap the 0x00..0x1F range).
 *
 * Only affects the WRCHAR path (attr ≤ 0xFF).  When attr is
 * KEEP_EXIST_ATTR, ESTEX's own PUTCHAR/PCHARS rule the cursor and
 * pc_raw_mode is irrelevant. */
inline void    set_putch_raw_mode(uint8_t mode) { pc_raw_mode = mode; }
inline uint8_t get_putch_raw_mode(void) { return pc_raw_mode; }

/* Sprinter text-mode 03h attribute byte (verified via attr_probe):
 *   bits 0..3 = foreground (0..15)
 *   bits 4..6 = background (0..7)
 *   bit  7    = blink (toggles fg between fg-colour and bg-colour)
 * Colour order is standard CGA / Borland-conio.h.  Constants 0..7 are
 * usable for both fg and bg; 8..15 are foreground-only. */
enum {
    COLOR_BLACK = 0, 
    COLOR_BLUE,  
    COLOR_GREEN,   
    COLOR_CYAN,
    COLOR_RED,       
    COLOR_MAGENTA, 
    COLOR_BROWN, 
    COLOR_LIGHTGRAY,
    COLOR_DARKGRAY,  
    COLOR_LIGHTBLUE, 
    COLOR_LIGHTGREEN, 
    COLOR_LIGHTCYAN,
    COLOR_LIGHTRED,  
    COLOR_LIGHTMAGENTA, 
    COLOR_YELLOW, 
    COLOR_WHITE
};
#define COLOR_BLINK    0x80u
#define COLOR(fg, bg)  ((uint8_t)((((bg) & 0x07) << 4) | ((fg) & 0x0F)))

/* Text-mode palette.  The 16 logical CGA colours seen by COLOR(fg, bg)
 * actually live in four 256-entry hardware palette planes indexed by the
 * full 8-bit attribute byte:
 *
 *   TEXT_PAL_PAPER        — background colour, non-blink phase
 *   TEXT_PAL_INK          — foreground colour, non-blink phase
 *   TEXT_PAL_BLINK_PAPER  — background colour during the blink half-cycle
 *   TEXT_PAL_BLINK_INK    — foreground colour during the blink half-cycle
 *
 * For non-blinking attributes (bit 7 = 0) all four planes display the
 * same colours, so writing to PAPER/INK is enough. For blinking attrs
 * (bit 7 = 1) the renderer alternates between the non-blink and blink
 * planes — that's how flash is implemented in hardware.
 *
 * These wrappers add 4 to the plane index and forward to the low-level
 * <palette.h> API (pal_load / pal_set_color / pal_get / pal_get_color).
 * Use text_pal_reset() to restore the system default CGA palette. */
#define TEXT_PAL_PAPER        0
#define TEXT_PAL_INK          1
#define TEXT_PAL_BLINK_PAPER  2
#define TEXT_PAL_BLINK_INK    3

void text_pal_load     (uint8_t plane, uint8_t start, uint8_t count,
                        const uint8_t *bgr0);
void text_pal_set_color(uint8_t plane, uint8_t attr,
                        uint8_t r, uint8_t g, uint8_t b);
void text_pal_get      (uint8_t plane, uint8_t start, uint8_t count,
                        uint8_t *bgr0);
void text_pal_get_color(uint8_t plane, uint8_t attr,
                        uint8_t *r, uint8_t *g, uint8_t *b);
void text_pal_reset    (void);

#endif
