/*
 * gfxbanks — Фаза 0 спрайтового дизайна: верификация подрежимов
 * видеостраниц #50..#5F (docs/sprite-api-design.md, «Фаза 0»).
 *
 * Семантика по доку (Иван Мак §4.3):
 *   bit3 (0x58/0x5C) — запись байта 0xFF НЕ производится (прозрачность);
 *   bit2 (0x54/0x5C) — запись только в видео-ОЗУ, ОЗУ-копия не трогается;
 *   чтение из #50..#5F всегда возвращает ОЗУ-копию (getpixel = ОЗУ).
 *
 * ВНИМАНИЕ: вердикт MAME НЕ финален — тест прогоняется и в MAME, и на
 * железе.  Известное расхождение: в MAME 0.283 (наш v306) FF-скип bit3
 * гасит ТОЛЬКО запись в VRAM, а теневое ОЗУ при bit2=0 всё равно
 * получает FF (ram_w: `if (BIT(~page,2)) ram[...]=data;
 * if (!(BIT(page,3) && data==0xff)) vram_w(...)`); в master-драйвере
 * уже полное подавление (`return`) — по доку.  Через 0x5C расхождение
 * не проявляется (bit2 гасит ОЗУ-ветку) — поэтому putsprite планово
 * работает через 0x5C.  На банке 0x58 зона SKIP это расхождение
 * ловит: chkC = FAIL на 0.283, PASS по доку/master.
 *
 * Сценарий (4 стадии, скриншот после каждой; палитра[255] = синий;
 * паузы — по RTC getdatetime, т.к. delayms в --memory small неработосп.
 * (irq_install → EINVAL), а скорость CPU в паузах не участвует):
 *   S1  фон: вертикальные полосы RED/LIGHTGRAY через 0x50 (VRAM+ОЗУ).
 *   S2  «спрайты» (после Фазы A putimage(COPY) идёт через акселератор,
 *       поэтому CPU-путь в ZL — честный цикл putpixel):
 *        ZL (CPU-путь):  банк 0x5C, putpixel-ями круг GREEN с полями
 *            0xFF — ожидание: круг поверх полос, поля прозрачны;
 *        ZR (accel-путь): банк 0x5C, bar 0xFF 64×64 (accel-fill:
 *            ожидание no-op) + putimage того же круга (accel-COPY:
 *            прозрачность полей на burst-пути);
 *        ZB (bit3-skip):  банк 0x58, putimage 0xFF (accel-copy, верх) +
 *            bar 0xFF (accel-fill, низ) — ожидание по доку: no-op
 *            экрана и ОЗУ.
 *       Проверки по ОЗУ (getpixel): A = ОЗУ под ZL/ZR не тронуто (bit2);
 *       C = ОЗУ под ZB не тронуто (полное подавление FF-записи).
 *       Hex-пробы ОЗУ печатаются рядом (ожидание — цвета полос 04/07).
 *   S3  КЛЮЧЕВОЙ ШАГ — 0xFF реально в VRAM: банк 0x54 (скипа нет),
 *       квадрат 0xFF 32×32 поверх круга (CPU putpixel-ями в ZL,
 *       accel-copy putimage'ем в ZR).
 *       Экран:  СИНИЙ квадрат  → FF отображается как цвет 255
 *                                (семантика дока полная, стирание = heal);
 *               ПОЛОСЫ         → видеокарта подставляет ОЗУ-копию
 *                                (дешёвое стирание FF-заливкой работает);
 *               круг/зелёный   → 0x54 не эмулируется.
 *       Проверка B = ОЗУ всё ещё полосы (bit2 у 0x54).
 *   S4  heal: чтение+запись через 0x50 ЛЕВЫХ половин всех зон —
 *       левые половины возвращаются к фону ИЗ ОЗУ-КОПИИ, правые
 *       сохраняют артефакты S2/S3.  NB: если ОЗУ под зоной испорчено
 *       (chkC FAIL на 0.283), heal честно перенесёт порчу в VRAM —
 *       зона SKIP посинеет на этой стадии.
 *
 * Запуск:
 *   python3 toolchain/mame_interactive.py tests/gfxbanks/gfxbanks.exe \
 *       --snap 13,17,21,25 --timeout 27
 */

#include <graphics.h>
#include <gfx.h>
#include <time.h>

#define ZY   56         /* верх зон */
#define ZH   64         /* зоны 64×64 */
#define ZLX  16         /* 0x5C, CPU-путь   */
#define ZRX  120        /* 0x5C, accel-путь */
#define ZBX  224        /* 0x58, bit3-skip  */

#define STRIPE_Y0 48
#define STRIPE_Y1 135

/* Точки-пробы внутри области, куда S2 писал НЕ-FF байты (круг r=28 и
 * квадрат +16..+47 содержат все три), — общие для проверок и hex-дампа. */
static const uint8_t probe[3][2] = { {32, 32}, {20, 28}, {40, 36} };

/* Полосы шириной 8: цвет по абсолютному x */
static uint8_t stripe_color(int x)
{
    return ((x >> 3) & 1) ? LIGHTGRAY : RED;
}

/* Спрайт 64×64: круг GREEN r=28, вокруг — прозрачный 0xFF */
static uint8_t spr[4 + 64 * 64];
/* Квадрат 32×32 сплошного 0xFF */
static uint8_t ffsq[4 + 32 * 32];

static void build_images(void)
{
    uint8_t *p = spr;
    int x, y;

    *p++ = 64; *p++ = 0; *p++ = 64; *p++ = 0;
    for (y = 0; y < 64; y++)
        for (x = 0; x < 64; x++) {
            int dx = x - 32, dy = y - 32;
            *p++ = (dx * dx + dy * dy <= 28 * 28) ? GREEN : 0xFF;
        }

    p = ffsq;
    *p++ = 32; *p++ = 0; *p++ = 32; *p++ = 0;
    for (x = 0; x < 32 * 32; x++)
        *p++ = 0xFF;
}

/* ОЗУ-копия зоны — нетронутые полосы? */
static uint8_t ram_is_stripes(int zx)
{
    uint8_t i;

    for (i = 0; i < 3; i++) {
        int x = zx + probe[i][0];
        if (getpixel(x, ZY + probe[i][1]) != stripe_color(x))
            return 0;
    }
    return 1;
}

static char hexd(uint8_t v)
{
    v &= 15;
    return v < 10 ? '0' + v : 'A' + v - 10;
}

/* Hex-дамп трёх ОЗУ-проб зоны: "ZX 04 04 07" в колонке x=216 */
static void show_probes(const char *tag, int zx, int ty)
{
    char buf[12];
    uint8_t i, v;

    buf[0] = tag[0];
    buf[1] = tag[1];
    for (i = 0; i < 3; i++) {
        v = (uint8_t)getpixel(zx + probe[i][0], ZY + probe[i][1]);
        buf[2 + i * 3] = ' ';
        buf[3 + i * 3] = hexd(v >> 4);
        buf[4 + i * 3] = hexd(v);
    }
    buf[11] = 0;
    outtextxy(216, ty, buf);
}

/* Пауза n секунд по RTC (гранулярность 1 с, не зависит от CPU/режима) */
static void pause_sec(uint8_t n)
{
    datetime_t dt;
    uint8_t last;

    getdatetime(&dt);
    last = dt.second;
    while (n) {
        getdatetime(&dt);
        if (dt.second != last) {
            last = dt.second;
            n--;
        }
    }
}

/* CPU-путь: вывести rect w×h из img (getimage-формат) в (x0,y0)
 * по-пиксельно через putpixel (LD (HL),A — НЕ акселератор). */
static void blit_cpu(int x0, int y0, const void *img, int w, int h)
{
    const uint8_t *p = (const uint8_t *)img + 4;
    int x, y;

    for (y = 0; y < h; y++)
        for (x = 0; x < w; x++)
            putpixel(x0 + x, y0 + y, *p++);
}

/* CPU-путь: залить rect цветом по-пиксельно. */
static void fill_cpu(int x0, int y0, int w, int h, uint8_t color)
{
    int x, y;

    for (y = 0; y < h; y++)
        for (x = 0; x < w; x++)
            putpixel(x0 + x, y0 + y, color);
}

/* heal: прочитать (вернёт ОЗУ-копию) и записать (уйдёт в VRAM+ОЗУ)
 * каждый пиксель прямоугольника банком 0x50.  Прообраз gfx_heal. */
static void heal(int x0, int y0, int w, int h)
{
    int x, y;

    for (y = y0; y < y0 + h; y++)
        for (x = x0; x < x0 + w; x++)
            putpixel(x, y, getpixel(x, y));
}

static void say(int y, const char *s)
{
    outtextxy(4, y, s);
}

int main(void)
{
    uint8_t chkA, chkB, chkC;
    int x;

    build_images();
    initgraph();
    gfx_pal_set(0, 255, 0, 0, 255);     /* цвет 255 = ярко-синий */
    cleardevice();
    setcolor(WHITE);

    /* ---- S1: фон ------------------------------------------------ */
    say(4, "GFXBANKS: page submodes 50/54/58/5C");
    for (x = 0; x < 320; x += 8) {
        setfillstyle(SOLID_FILL, stripe_color(x));
        bar(x, STRIPE_Y0, x + 7, STRIPE_Y1);
    }
    outtextxy(ZLX, 44, "5C CPU");
    outtextxy(ZRX, 44, "5C ACC");
    outtextxy(ZBX, 44, "58 SKIP");
    say(148, "S1 stripes via 50");
    pause_sec(4);

    /* ---- S2: спрайты через 0x5C / no-op через 0x58 --------------- */
    gfx_set_bank(0x5C);
    blit_cpu(ZLX, ZY, spr, 64, 64);          /* CPU: круг + FF-поля  */
    setfillstyle(SOLID_FILL, 255);
    bar(ZRX, ZY, ZRX + 63, ZY + 63);         /* accel-fill FF — ждём no-op */
    putimage(ZRX, ZY, spr, COPY_PUT);        /* accel-COPY: круг + FF-поля */
    gfx_set_bank(0x58);
    putimage(ZBX, ZY, ffsq, COPY_PUT);       /* accel-copy FF, верх зоны */
    setfillstyle(SOLID_FILL, 255);
    bar(ZBX, ZY + 32, ZBX + 31, ZY + 63);    /* accel-fill FF, низ зоны  */
    gfx_set_bank(0x50);

    chkA = ram_is_stripes(ZLX) && ram_is_stripes(ZRX);
    chkC = ram_is_stripes(ZBX);
    say(158, "S2 sprites 5C, ff-skip 58");
    say(168, chkA ? "A bit2 RAM intact: PASS" : "A bit2 RAM intact: FAIL");
    show_probes("ZL", ZLX, 168);
    say(178, chkC ? "C bit3 skip (RAM): PASS" : "C bit3 skip (RAM): FAIL");
    show_probes("ZB", ZBX, 178);
    pause_sec(4);

    /* ---- S3: 0xFF реально в VRAM (банк 0x54, скипа нет) ---------- */
    gfx_set_bank(0x54);
    fill_cpu(ZLX + 16, ZY + 16, 32, 32, 0xFF);      /* CPU-путь        */
    putimage(ZRX + 16, ZY + 16, ffsq, COPY_PUT);    /* accel-copy путь */
    gfx_set_bank(0x50);

    chkB = ram_is_stripes(ZLX) && ram_is_stripes(ZRX);
    say(188, "S3 KEY: ff into vram via 54");
    say(198, chkB ? "B 54 RAM intact:   PASS" : "B 54 RAM intact:   FAIL");
    show_probes("ZR", ZRX, 198);
    say(208, "S3 look: BLUE=doc STRIPES=subst");
    pause_sec(4);

    /* ---- S4: heal левых половин зон ------------------------------ */
    heal(ZLX, ZY, 32, ZH);
    heal(ZRX, ZY, 32, ZH);
    heal(ZBX, ZY, 32, ZH);
    say(218, "S4 healed left halves, done");

    for (;;) { }
}
