/*
 * _bgi.h — внутренности графической подсистемы BGI (НЕ публичный заголовок).
 *
 * Слияние старых libc/bgi/_bgi.h и libc/gfx/_gfx.h.  Графика вынесена из
 * libc в самостоятельную библиотеку libbgi/ (см. docs + memory
 * bgi_two_lib_design).  Из libbgi собираются две driver-библиотеки
 * (каждый .c → отдельный .rel):
 *   bgi256.lib ← common/ + bgi256/   (320×256×256, mode 0x81)
 *   bgi16.lib  ← common/ + bgi16/    (640×256×16,  mode 0x82)
 * common/ — mode-agnostic: .rel компилируется ОДИН раз и кладётся в
 * ОБА архива (один исходник для функций, общих 256- и 16-цветной
 * версиям).  bgi256/ и bgi16/ — mode-specific leaf'ы (реальные
 * реализации, БЕЗ обёрток-делегатов).  Режим выбирается линковкой
 * (--gfx 256 / --gfx 16), одновременно один.
 *
 * Публичные leaf-символы (_bgi_plot_raw, _bgi_hspan_raw, _bgi_read_raw,
 * _bgi_vspan_raw, _bgi_clear_raw, _bgi_mode_set) одинаково объявлены в
 * обоих driver-каталогах, поэтому common-математика не знает режима —
 * нужный .rel тянет линкер из выбранной библиотеки.
 */
#ifndef _BGI_INTERNAL_H
#define _BGI_INTERNAL_H

#include <stdint.h>
#include <graphics.h>
#include <gfx.h>          /* GFX_MODE_*, размеры — для внутренних таблиц */

/* ---- Состояние BGI (_bgi_state.c) -------------------------------- *
 * _bgi_fg/_bgi_bg/_bgi_cx/_bgi_cy/_bgi_maxx/_bgi_maxy/_bgi_maxcolor/
 * _bgi_result объявлены в ПУБЛИЧНОМ graphics.h (нужны static inline
 * аксессорам: setcolor/moveto/getmaxx/…, 2026-07-10);
 * _gfx_visible_page/_gfx_draw_page/_gfx_bank — в публичном gfx.h.
 * Байт _gfx_bank (0x50..0x5F): какой видеобанк мапится при записи —
 * 0x50 normal, 0x54 temp, 0x58 transparent, 0x5C оба. */
extern uint8_t _bgi_prevmode;   /* видеорежим до initgraph */

/* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или
 * 0xC140 (page 1); каждый примитив использует его вместо константы. */
extern uint16_t _gfx_addr_base;
extern uint16_t _gfx_addr_shadow_base;

/* ---- Низкоуровневый видеорежим (libc/video) ---------------------- */
uint8_t _videomode_raw_get(void);
int     _videomode_raw_set(uint8_t mode);

/* ---- W3-скобки (common/_bgi_begin.c, _bgi_end.c) ----------------- *
 * begin: DI + сохранить текущий W3 и замапить _gfx_bank;
 * end: восстановить W3 + EI.  Не реентерабельно (сейв в статике). */

extern uint8_t _gfx_saved_w3;

void _bgi_begin(void) __preserves_regs(b,c,d,e,h,l);
void _bgi_end(void) __preserves_regs(b,c,d,e,h,l);

/* ---- LEAF-операции драйвера (bgi256/ или bgi16/) ----------------- *
 * Каждая self-contained: raw-варианты НЕ ставят свою W3-скобку
 * (вызывающий обязан обернуть в _bgi_begin/_bgi_end); self-bracket
 * (_bgi_read/_bgi_hspan/_bgi_clearall) ставят скобку сами. */

uint8_t  _bgi_mode_set(void);   /* войти в режим + палитра + границы */

/* __preserves_regs(d,e): проверено по asm — эти примитивы читают D/E
 * (y приходит в DE), но НЕ пишут; SDCC не обязан спасать DE вокруг
 * вызова (экономит push/pop в горячих циклах Брезенхэма/блита).
 * При правке asm сверять клоббер-лист заново! */
void     _bgi_plot_raw (int x, int y, uint8_t color) __preserves_regs(d,e);  /* точка, без скобки */
uint8_t  _bgi_read_raw (int x, int y) __preserves_regs(d,e);                 /* чтение, без скобки */
void     _bgi_hspan_raw(int x, int y, int len, uint8_t color) __preserves_regs(d,e); /* H-отрезок, без скобки */
void     _bgi_vspan_raw(int x, int y, int len, uint8_t color) __preserves_regs(d,e); /* V-отрезок, без скобки */
void     _bgi_clear_raw(uint8_t color);                /* заливка экрана, без скобки (клоббер всех) */

/* Копирование h строк по w байт через accel block-copy (LD L,L) —
 * ядро блиттинга (putimage, getimage, gfx_blit, gfx_heal).  Без скобки и
 * клиппинга; w,h: 0 = 256; stride 0 = адрес на месте (сторона экрана,
 * строку выбирает Port_Y); y0 — стартовый Port_Y.  Направление задают
 * указатели (буфер обязан быть вне W3).  Контракт/схема — в
 * bgi256/_bgi_copy_rows_raw.c. */
void     _bgi_copy_rows_raw(const uint8_t *src, uint8_t *dst,
                            uint8_t w, uint8_t h,
                            int dstride, int sstride, uint8_t y0);

/* Специализация для БЛИТА: dst фиксирован (dstride 0), src двигается на
 * sstride (SMC-константа); dst-цепочка выкинута (~24Т/строку).  Ядро
 * gfx_blit_part/putsprite/спрайтового движка.  Без скобки/клиппинга; банк
 * ставит вызывающий (0x5C прозрачный).  bgi256/_bgi_blit_rows_raw.c. */
void     _bgi_blit_rows_raw(const uint8_t *src, uint8_t *dst,
                            uint8_t w, uint8_t h,
                            int sstride, uint8_t y0);

/* КОЛОНОЧНАЯ специализация (column-major src -> экран): каждая колонка —
 * вертикальный accel-burst (LD A,A, Port_Y авто-шаг); dst +1/колонку, src
 * +sstride/колонку.  Даёт бесплатный горизонтальный флип (sstride<0 + src
 * с конца).  w=колонок, h=высота(=размер блока), y0=Port_Y стартовый.
 * bgi256/_bgi_blit_cols_raw.c. */
void     _bgi_blit_cols_raw(const uint8_t *src, uint8_t *dst,
                            uint8_t w, uint8_t h,
                            int sstride, uint8_t y0);

/* Специализация для heal: src == dst == адрес экрана, оба страйда 0
 * (строку выбирает Port_Y) — адресной арифметики в цикле нет, строка
 * 77Т + burst'ы.  y0 — стартовый Port_Y; w,h: 0 = 256.  Без скобки и
 * клиппинга; банк ставит вызывающий (gfx_heal форсит 0x50). */
void     _bgi_heal_rows_raw(uint8_t *scr, int y0, uint8_t w, uint8_t h);

/* Self-bracket версии (begin + raw + end) — для разовых вызовов из
 * математики, которой не нужен пакетный режим (floodfill, cleardevice).
 * Разовое чтение пикселя — публичный getpixel (та же композиция;
 * бывший дубликат _bgi_read удалён 2026-07-10). */
void     _bgi_hspan   (int x, int y, int len, uint8_t color);
void     _bgi_clearall(uint8_t color);


/* Скролл-ядра video->video (банк 0x50: копия = скролл + heal цели; без
 * скобки/клиппинга; DI/EI бандами по 16 ВНУТРИ; перекрытия нет — стороны
 * в разных страницах).  Контракты — в шапках bgi256/_bgi_scroll_*_raw.c.
 *
 * _bgi_scroll_rows_raw — ядро gfx_scroll_h: сдвиг ТОЛЬКО по X (разница
 * адресов src/dst), построчно, БЕЗ страйдов — один OUT Port_Y на строку
 * (~54Т/строку, на 5-7% быстрее cols).  w — байт/строка (0=256), h —
 * строк 1..256 (0=256, конвенция accel), y0 — стартовый Port_Y (++ на строку). */
void    _bgi_scroll_rows_raw(uint16_t src, uint16_t dst,
                             uint8_t w, uint8_t h, uint8_t y0);

/* _bgi_scroll_cols_raw — ядро gfx_scroll_v: сдвиг по X И/ИЛИ Y за один
 * проход БЕЗ буфера.  Колонка = вертикальный accel-burst (LD A,A): читаем
 * с Port_Y=y0, пишем с Port_Y=y1 (сдвиг dy = y1−y0; dx — в адресах).  STOP
 * между read и write делает промежуточный OUT Port_Y безопасным.  w —
 * число КОЛОНОК 1..255 (0=выход), h — высота колонки = размер accel-блока
 * (0=256), y0/y1 — Port_Y чтения/записи. */
void    _bgi_scroll_cols_raw(uint16_t src, uint16_t dst,
                             uint8_t w, uint8_t h, uint8_t y0, uint8_t y1);




/* Ядро блиттинга gfx_blit_part/gfx_blit/gfx_heal (Фаза B) — публичные,
 * прототипы в <gfx.h>; спрайтовые обёртки putsprite/movesprite — в
 * <graphics.h>.  Реализации: common/gfx_blit_part.c и соседи. */

/* Bracket-free общие ядра (полная ширина) (common/_gfx_blit_full.c, _gfx_heal_full.c):
 * клип по экрану + нарезка полос ≤256 + accel-leaf, БЕЗ _bgi_begin/
 * _bgi_end и БЕЗ смены банка.  Вызывающий обязан замапить W3 (_bgi_begin)
 * и выставить нужный банк ДО вызова.  Общее ядро для разовых обёрток
 * (gfx_blit_part/gfx_heal) и batch-пасса спрайтового движка v2, который
 * держит одну скобку + один банк на весь кадр (docs/sprite-api-design.md
 * §9.1).  blit: банк 0x5C для прозрачности; heal: банк 0x50. */
void _gfx_blit_full(int x, int y, const void *img,
                    int sx, int sy, int w, int h);
void _gfx_heal_full(int x, int y, int w, int h);

/* Лин-ядра для СПРАЙТОВ (кадр ≤ 64×64, sprite.h): как *_full, но без
 * split'а >256 и с 8-битными w/h (движок/putsprite — горячий путь;
 * программа только со спрайтами не тянет общие *_full со split'ом).
 * Сигнатура БЕЗ img/sx/sy (2026-07-14): src — ГОТОВЫЙ адрес пикселей
 * кадра, stride — шаг строки источника; считает их вызывающий один раз
 * (кэш в sprite_t / putsprite из заголовка) — ни чтения заголовка, ни
 * умножения в ядре.  common/_gfx_blit_sprite.c, _gfx_heal_sprite.c. */
void _gfx_blit_sprite(int x, int y, const uint8_t *src,
                      uint16_t stride, uint8_t w, uint8_t h);
void _gfx_heal_sprite(int x, int y, uint8_t w, uint8_t h);

/* NOCLIP-ядра: клип-кода нет вообще (полный выигрыш от codegen, не только
 * скип проверок).  Зовутся при gfx_sprite_clip(0) — приложение
 * гарантирует пределы.  common/_gfx_blit_sprite_noclip.c, _gfx_heal_… */
void _gfx_blit_sprite_noclip(int x, int y, const uint8_t *src,
                             uint16_t stride, uint8_t w, uint8_t h);
void _gfx_heal_sprite_noclip(int x, int y, uint8_t w, uint8_t h);

/* Диспетчеризация clip/noclip через УКАЗАТЕЛИ (не ветка в горячем цикле):
 * gfx_sprite_clip() перенаправляет их на clip- или noclip-ядро ОДИН раз,
 * а sprite_update/putsprite/movesprite зовут через указатель — один
 * call-site, без if в цикле → тесный codegen (полный выигрыш noclip,
 * см. docs/sprite-api-design.md).  Дефолт (clip-ядра) — статическая
 * инициализация; указатели в РАЗДЕЛЬНЫХ data-модулях (_gfx_blit_fn.c,
 * _gfx_heal_fn.c): putsprite-only программа не тянет heal-ядро. */
typedef void (*_gfx_blit_fp)(int, int, const uint8_t *, uint16_t,
                             uint8_t, uint8_t);
typedef void (*_gfx_heal_fp)(int, int, uint8_t, uint8_t);
extern _gfx_blit_fp _gfx_blit_fn;
extern _gfx_heal_fp _gfx_heal_fn;

/* ---- Y-сортировка блит-прохода (sprite.h: gfx_sprite_ysort) ------- *
 * _spr_ysort ведёт ПЕРСИСТЕНТНУЮ таблицу 4-байтных записей
 * {key16 = layer:8|clamp_y:8, ptr16} (вставки, стабильно; таблица и
 * состояние сцены — приватные данные его модуля) и ВОЗВРАЩАЕТ её
 * адрес.  Указатель спрайта — в записи по смещению +2.  Лимит спрайтов
 * на кадр — _GFX_YSORT_MAX; больше — sprite_update рисует БЕЗ
 * сортировки (фолбэк).
 * Подключение — указателем _spr_ysort_fn (NULL = выкл, дефолт; ставит
 * gfx_sprite_ysort, обнуляя _spr_ysort_last — форс rebuild) —
 * программа без вызова сеттера не линкует ни код сортировщика, ни
 * таблицу (funcptr-DCE, как _spr_tick_fn).
 * common/_spr_ysort.c, _spr_ysort_fn.c, gfx_sprite_ysort.c. */
#define _GFX_YSORT_MAX 32u
uint8_t *_spr_ysort(void *arr, uint16_t count);
extern uint8_t *(*_spr_ysort_fn)(void *arr, uint16_t count);
extern void *_spr_ysort_last;    /* arr прошлого кадра (сеттер сбрасывает) */
extern uint8_t _spr_ysort_kmask[2]; /* маски ключа: [0] y, [1] layer (0/FF) */

/* ---- Атласы в страницах W0 (sprite.h, §9в дизайна) ---------------- *
 * _gfx_w0_dss — страница ядра DSS (снимается при первом map/load);
 * _gfx_w0_cur — какая спрайт-страница должна стоять в W0 (читает
 * ISR-стаб _gfx_w0_isr для restore после прерывания).
 * common/_gfx_w0_state.c, _gfx_w0_isr.c. */
extern uint8_t _gfx_w0_dss;
extern uint8_t _gfx_w0_cur;
void _gfx_w0_isr(void);

/* Раскладка .atl-файла/страницы (вариант II, §3.1). */
#define _ATL_DIR_OFF   0x68u    /* каталог: count × 8 Б (макс 19)     */
#define _ATL_DATA_OFF  0x100u   /* ленты; = резерв ISR-стаба          */

/* ---- Палитровые хелперы (common/_gfx_pal_buf.c) ------------------- *
 * Общий буфер чанковых операций (sync/fload/fsave): 64 записи × 4 Б;
 * в W2 (BIOS $A4 требует буфер в #4000-#BFFF). */
#define _GFX_PAL_CHUNK 64u
extern uint8_t _gfx_pal_buf[_GFX_PAL_CHUNK * 4];

/* ---- Авто-анимация спрайтов (§9г) --------------------------------- *
 * Тикер подключается указателем (DCE: программа без sprite_anim/
 * sprite_moveto не тянет его код): дефолт NULL (_BSS), ставят
 * sprite_anim()/sprite_moveto().  sprite_update зовёт в начале кадра.
 * Типы из <sprite.h> здесь недоступны (подключается после) — void*. */
typedef void (*_spr_tick_fp)(void *arr, uint8_t count);
extern _spr_tick_fp _spr_tick_fn;             /* common/_spr_tick_fn.c */
void _sprite_tick(void *arr, uint8_t count);  /* common/_sprite_tick.c */

/* ---- Fill-burst'ы акселератора 256 (register-ABI, asm-only) ------ *
 * Вызываются ТОЛЬКО из asm raw-примитивов; аргументы в регистрах, без
 * глобального скретча в _DATA:
 *   HL = CPU-адрес (base + x),  C = color,
 *   B  = длина (0 = 256),       E = y (строка для Port_Y).
 * Клоббер A/F; сохраняют BC/DE/HL/IX/IY.  Pre: W3 замаплен, IRQ гасятся
 * внутри вокруг одного выстрела.  Прототип void(void) — только чтобы
 * линкер видел символ (реальный ABI регистровый, см. bgi256/_gfx_*fill256.c). */

/* Горизонтальный Fill-burst (bgi256/_gfx_hfill256.c). */
void _gfx_hfill256_segment(void);

/* Вертикальный Fill-burst (bgi256/_gfx_vfill256.c). */
void _gfx_vfill256_segment(void);

/* Заливка прямоугольника цветом: x, y, w, h, color.
 * _gfx_rectfill256 — диспетчер: выбирает ориентацию с меньшим числом
 * выстрелов (~160-245Т на проверку) и tail-jump'ит в leaf.  Если форма
 * известна заранее или |w-h| < ~4, выгоднее звать leaf напрямую:
 *   _gfx_recthfill256 — h горизонтальных строк, ТРЕБУЕТ 1 <= w <= 256;
 *   _gfx_rectvfill256 — w вертикальных колонок, ТРЕБУЕТ 1 <= h <= 256.
 * Контракты/расчёт break-even — в шапках bgi256/_gfx_rect*fill256.c. */
void _gfx_rectfill256(int x, int y, int w, int h, uint8_t color);
void _gfx_recthfill256(int x, int y, int w, int h, uint8_t color);
void _gfx_rectvfill256(int x, int y, int w, int h, uint8_t color);

/* ---- Скретч 16-цветных примитивов (bgi16/_gfx_g16_state.c) ------- */
extern uint8_t  _gfx_g16_y;
extern uint8_t  _gfx_g16_byte;   /* nibble | (nibble<<4) */
extern uint8_t  _gfx_g16_nibble; /* цвет в нужной половине байта */
extern uint8_t  _gfx_g16_mask;   /* маска, сохраняющая ДРУГУЮ половину */
extern uint8_t  _gfx_g16_len;    /* размер accel-блока (0 = 256) */
extern uint16_t _gfx_g16_addr;

/* Горизонтальный Fill-burst байтами _gfx_g16_byte (bgi16/_gfx_hfill16.c). */
void _gfx_hfill16_chunk(void);

/* RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y) (bgi16/_gfx_rmw16.c). */
void _gfx_rmw16_pixel(void);

/* ---- Шрифт (common/_gfx_font_state.c, _gfx_font_ensure.c) -------- *
 * Формат: 256 глифов × 8 рядов, INTERLEAVED — offset = row*256 + char;
 * биты MSB-first.  См. memory/sprinter_font_format. */

#define _GFX_FONT_BYTES 2048

extern uint8_t        _gfx_font_buf[_GFX_FONT_BYTES];
extern const uint8_t *_gfx_font_ptr;
extern uint8_t        _gfx_font_loaded;

/* Ленивая инициализация: грузит системный шрифт при первом использовании. */
void _gfx_font_ensure(void);

/* 8 байт строк глифа c (грузит шрифт при первом использовании) —
 * mode-agnostic, просто читает _gfx_font_ptr.  common/_bgi_font_rows.c. */
void _bgi_font_rows(unsigned char c, uint8_t *rows8);

/* ---- Тригонометрия для дуг (common/_bgi_trig.c) ------------------ *
 * Угол в градусах; результат sin/cos × 128 (Q7 — чтобы радиус×значение
 * влезало в int без 32-бит арифметики). */
int _bgi_sin128(int deg);
int _bgi_cos128(int deg);

/* Общий рисователь дуги/контура эллипса (common/_bgi_arc_draw.c):
 * полилиния по точкам от stangle до endangle с полуосями xr,yr вокруг
 * (cx,cy). */
void _bgi_ellipse_arc(int cx, int cy, int stangle, int endangle,
                      int xr, int yr, uint8_t color);

/* Целочисленный sqrt (0..~32767) для fillellipse (common/_bgi_isqrt.c). */
int _bgi_isqrt(int n);

/* ---- Состояние линий (common/_bgi_line_state.c) ------------------ */
extern uint8_t  _bgi_line_style;    /* SOLID_LINE..USERBIT_LINE */
extern uint8_t  _bgi_line_thick;    /* 1 (NORM) или 3 (THICK)   */
extern uint16_t _bgi_line_pattern;  /* маска для USERBIT_LINE    */

/* Отрезок текущим стилем/толщиной линии (common/_bgi_styled_line.c). */
void _bgi_styled_line(int x0, int y0, int x1, int y1, uint8_t color);

/* Отрезок (x0,y0)->(x1,y1) БЕЗ стиля: ортогонал → hspan/vspan,
 * диагональ → Брезенхэм over _bgi_plot_raw, своя W3-скобка
 * (common/_bgi_lineseg.c).  Реальная логика, не обёртка. */
void _bgi_lineseg(int x0, int y0, int x1, int y1, uint8_t color);

/* ---- Состояние заливки (common/_bgi_fill_state.c) ---------------- */
extern uint8_t _bgi_fill_pattern;   /* *_FILL */
extern uint8_t _bgi_fill_color;

/* Залить строку [x0..x1] на y ТЕКУЩИМ стилем заливки (паттерн+цвет),
 * с клипом по экрану.  Вызывать между _bgi_begin/_bgi_end. */
void _bgi_fill_span(int x0, int x1, int y);

/* ---- Стиль текста (common/_bgi_text_state.c) --------------------- */
extern uint8_t _bgi_text_dir;    /* HORIZ_DIR / VERT_DIR */
extern uint8_t _bgi_text_size;   /* масштаб 1..10        */

/* Нарисовать строку s в (x,y) текущим стилем текста (масштаб/направление,
 * цвет _bgi_fg, прозрачный фон) — common/_bgi_draw_scaled.c. */
void _bgi_draw_scaled(int x, int y, const char *s);

/* ---- Скан-таблицы min/max X по строкам для fillpoly -------------- *
 * (common/_bgi_poly_state.c).  GFX_HEIGHT — высота экрана (256 в обоих
 * режимах), см. <gfx.h>. */
extern int _bgi_span_min[GFX_HEIGHT];
extern int _bgi_span_max[GFX_HEIGHT];

/* Прогнать ребро (Брезенхэм) обновляя _bgi_span_min/max
 * (common/_bgi_poly_edge.c). */
void _bgi_poly_edge(int x0, int y0, int x1, int y1);

/* ---- Cross-library: внутренние символы libc/cbl ------------------ *
 * libbgi ВСЕГДА линкуется вместе с libc (sprinter.lib), поэтому
 * зависимость от внутренних символов libc допустима.  gfx_wait_vsync
 * держит bit7 порта 0x004E через рефкаунт _cbl_port_ref/unref, чтобы
 * бит 5 порта 0xFE (позиция луча) был доступен.
 * ВНИМАНИЕ: это ВНУТРЕННИЕ символы libc/cbl — при изменении сигнатур
 * СВЕРЯТЬ с libc/cbl/_cbl.h и синхронизировать.  (По тому же паттерну,
 * каким здесь живут _videomode_raw_* из libc/video.) */
extern void _cbl_port_ref(void);
extern void _cbl_port_unref(void);

/* Idle-хук ожидания кадра (common/_gfx_idle_state.c, ставится
 * gfx_set_idle_hook).  NULL = нет; зовётся из лучевого цикла
 * gfx_wait_vsync. */
extern void (*_gfx_idle_hook)(void);

/* ---- FPS-делитель (frame pacing) --------------------------------- *
 * Фоновый счётчик кадров через цепочку кадровых IRQ (libc/irq).  Данные
 * в common/_gfx_fps_state.c; при _gfx_fps_div<=1 путь не активен и
 * gfx_wait_vsync остаётся лучевым поллингом.  Механику irq_chain_add
 * дёргает ТОЛЬКО gfx_set_fps_div.c (DCE). */
extern uint8_t          _gfx_fps_div;    /* 0/1 = старый путь; >=2 — делитель */
extern volatile uint8_t _gfx_frame_tick; /* инкрементит _gfx_frame_isr */
extern uint8_t          _gfx_fps_last;   /* тик прошлого выхода из wait */

/* Кадровый ISR: чистый инкремент _gfx_frame_tick (правила irq.h —
 * никаких ESTEX/gfx/банков). */
void _gfx_frame_isr(void);

#endif
