/*
 * w0page — проверки схемы «спрайт-данные в странице W0»
 * (docs/sprite-api-design.md §3.1/§9в, обсуждение 2026-07-13).
 *
 * Идея: атлас грузится в EMM-страницу; на время sprite_update страница
 * подключается в W0 (0x0000-0x3FFF).  Опасность: ВСЕ прерывания приходят
 * на 0x38 текущей страницы W0 (IM1 напрямую; наш IM2-трамплин чейнит
 * jp 0x0038).  Защита: в первых 0x100 байтах страницы — стаб:
 * 0x38: JP w0_isr_stub (W2), 0x66: RETN.  Стаб свапит W0 на страницу
 * ядра DSS, отдаёт прерывание настоящему 0x38 (подложив возврат) и
 * ВОССТАНАВЛИВАЕТ спрайт-страницу.
 *
 * Стадии (Enter между; PASS/FAIL печатается ПОСЛЕ восстановления W0):
 *   P1  ESTEX READ в страницу, замапленную в W3 (приём mdview2).
 *   P2  подготовка страницы: стаб + картинка 16×16 в 0x100 + сентинел.
 *   P3  IM1: страница в W0, busy-цикл ~2-3 c с EI — выжить, сентинел
 *       через W0 верен на каждой итерации (restore работает).
 *   P4  IM2 (irq_install): страница в W0, дождаться 100 кадровых
 *       прерываний — счётчик растёт => полный тракт tramp→chain→стаб→
 *       DSS→restore живёт; сентинел верен.
 *   P5  графика: gfx_blit картинки ИЗ АДРЕСА 0x0100 (accel-burst
 *       src в W0) банком 0x50; self-check getpixel по ОЗУ-копии.
 *
 * Сборка: tiny (irq_install требует код/данные в W2) + --gfx 256.
 * Запуск через MAME-мост: press_key посимвольно + Enter'ы.
 */

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <graphics.h>
#include <gfx.h>
#include <irq.h>
#include <sprinter.h>
#include <sprinter_mem.h>

#define PAT_LEN   1024          /* P1: объём проверки чтения           */
#define SENT_OFF  0x3F00u       /* сентинел в конце страницы           */
#define SENT_VAL  0xA5
#define IMG_OFF   0x0100u       /* данные атласа — после резерва стаба */
#define IMG_W     16
#define IMG_H     16

static uint8_t w2buf[PAT_LEN];  /* эталон в W2 */
static uint8_t blk, phys;       /* EMM-блок и физ. страница */
static uint8_t dss_page;        /* страница ядра DSS (снята со старта) */

/* Для стаба (читается из прерывания — только W2-данные). */
static uint8_t w0_cur;

/* ISR-стаб (живёт в W2 = _CODE tiny): вызывается JP'ом с 0x38
 * спрайт-страницы.  Свапит W0 на ядро DSS, отдаёт прерывание честному
 * 0x38 (адрес возврата подложен на стек — DSS завершает EI/RETI и
 * возвращается в хвост), затем возвращает спрайт-страницу.  Вложенное
 * прерывание в хвосте (EI уже сделан DSS) безопасно: W0 в этот момент
 * = DSS, штатный путь, стаб не реентерится. */
static void w0_isr_stub(void) __naked
{
    __asm
        push  af
        ld    a, (_dss_page)
        out   (#0x82), a
        pop   af
        push  hl
        ld    hl, #w0_isr_ret
        ex    (sp), hl              ; вершина стека = возврат, HL цел
        jp    0x0038                ; честный обработчик DSS (EI/RETI)
    w0_isr_ret:
        push  af
        ld    a, (_w0_cur)
        out   (#0x82), a
        pop   af
        ret
    __endasm;
}

/* W0 := A под DI (щель между out и прерыванием закрывается стабом,
 * но сам свап делаем атомарно). */
static void w0_set(uint8_t page) __naked
{
    (void)page;
    __asm
        di
        out   (#0x82), a
        ei
        ret
    __endasm;
}

static void w0_map(void)   { w0_cur = phys; w0_set(phys); }
static void w0_unmap(void) { w0_set(dss_page); }

/* ---- P4: счётчик кадровых прерываний -------------------------------- */
static volatile uint8_t irq_ticks;
static void tick_isr(void) { irq_ticks++; }

/* ---- P5: тестовая картинка ------------------------------------------ *
 * Рамка RED, тело LIGHTCYAN, центр 2×2 YELLOW. */
static void build_image_via_w3(void)
{
    uint8_t *p = (uint8_t *)0xC000 + IMG_OFF;
    int x, y;

    p[0] = IMG_W; p[1] = 0; p[2] = IMG_H; p[3] = 0;
    p += 4;
    for (y = 0; y < IMG_H; y++)
        for (x = 0; x < IMG_W; x++) {
            uint8_t c = LIGHTCYAN;
            if (x == 0 || y == 0 || x == IMG_W - 1 || y == IMG_H - 1)
                c = RED;
            if ((x == 7 || x == 8) && (y == 7 || y == 8))
                c = YELLOW;
            *p++ = c;
        }
}

int main(void)
{
    uint8_t saved_w3;
    int fd, i, n;
    uint8_t ok;

    puts("W0PAGE: sprite data in W0 window probes");
    dss_page = _io_page_w0;             /* IN A,(0x82): страница ядра  */
    printf("dss page = %u\n", dss_page);

    blk = mem_alloc_pages(1);
    if (!blk) { puts("alloc FAIL"); return 1; }
    phys = mem_get_page(blk, 0);
    printf("emm page = %u\n", phys);

    /* ---- P1: ESTEX READ в W3-замапленную страницу ------------------- */
    for (i = 0; i < PAT_LEN; i++) w2buf[i] = (uint8_t)(i * 7 + 3);
    fd = creat("w0page.dat", 0);
    if (fd < 0) { puts("creat FAIL"); return 1; }
    write(fd, w2buf, PAT_LEN);
    close(fd);

    fd = open("w0page.dat", O_RDONLY);
    saved_w3 = _io_page_w3;
    sprinter_page_w3(phys);
    n = read(fd, (void *)0xC000, PAT_LEN);   /* буфер — в W3! */
    close(fd);

    ok = (n == PAT_LEN);
    if (ok)
        for (i = 0; i < PAT_LEN; i++)
            if (*((uint8_t *)0xC000 + i) != w2buf[i]) { ok = 0; break; }

    /* ---- P2: стаб + картинка + сентинел (страница ещё в W3) --------- */
    {
        uint8_t *pg = (uint8_t *)0xC000;
        uint16_t stub = (uint16_t)&w0_isr_stub;

        pg[0x38] = 0xC3;                        /* JP w0_isr_stub */
        pg[0x39] = (uint8_t)(stub & 0xFF);
        pg[0x3A] = (uint8_t)(stub >> 8);
        pg[0x66] = 0xED; pg[0x67] = 0x45;       /* RETN (NMI)     */
        pg[SENT_OFF] = SENT_VAL;
        build_image_via_w3();
    }
    sprinter_page_w3(saved_w3);
    printf("P1 read->W3:  %s\n", ok ? "PASS" : "FAIL");
    puts("P2 page prepared (stub+img)");
    (void)getchar();

    /* ---- P3: IM1, страница в W0, busy-цикл с EI --------------------- *
     * Внутри окна НИКАКИХ ESTEX/BIOS.  ~50-100 прерываний IM1 пройдут
     * через стаб; сентинел через W0 обязан оставаться верным. */
    ok = 1;
    w0_map();
    {
        uint8_t  outer;
        uint16_t inner;
        for (outer = 0; outer < 60; outer++)
            for (inner = 0; inner < 40000u; inner++)
                if (*(volatile uint8_t *)SENT_OFF != SENT_VAL) ok = 0;
    }
    w0_unmap();
    printf("P3 IM1 in W0: %s\n", ok ? "PASS" : "FAIL");
    (void)getchar();

    /* ---- P4: IM2, страница в W0, счёт кадровых прерываний ----------- *
     * Бюджет ожидания: 100 кадров = 2 с; guard-циклов с запасом ~10 с
     * (65000×150Т ≈ 0.5 с — прошлый одиночный цикл истекал раньше
     * срока и давал ложный FAIL). */
    ok = 0;
    {
        uint8_t got = 0, sent_ok = 1, outer;

        if (irq_install(tick_isr) == 0) {
            uint8_t  start;
            uint16_t guard;

            w0_map();
            start = irq_ticks;
            for (outer = 0; outer < 20 && !ok && sent_ok; outer++)
                for (guard = 0; guard < 60000u; guard++) {
                    got = (uint8_t)(irq_ticks - start);
                    if (got >= 100) { ok = 1; break; }
                    if (*(volatile uint8_t *)SENT_OFF != SENT_VAL) {
                        sent_ok = 0;
                        break;
                    }
                }
            w0_unmap();
            irq_remove();
        } else
            puts("irq_install failed");
        printf("P4 IM2 in W0: %s (ticks=%u sent=%s)\n",
               ok ? "PASS" : "FAIL", got, sent_ok ? "ok" : "BAD");
    }
    puts("Enter -> gfx");
    (void)getchar();

    /* ---- P5: accel-блит из W0-адреса -------------------------------- */
    initgraph();
    cleardevice();
    setfillstyle(SOLID_FILL, GREEN);
    bar(140, 90, 175, 125);             /* фон вокруг картинки */

    w0_map();
    gfx_blit(150, 100, (const void *)IMG_OFF);   /* src = 0x0100 (W0)! */
    w0_unmap();

    ok = (getpixel(150, 100) == RED)          /* рамка   */
      && (getpixel(157, 107) == YELLOW)       /* центр   */
      && (getpixel(152, 103) == LIGHTCYAN)    /* тело    */
      && (getpixel(148, 100) == GREEN);       /* фон цел */

    setcolor(WHITE);
    outtextxy(4, 4,   "W0PAGE gfx: blit from 0x0100 (W0)");
    outtextxy(4, 200, ok ? "P5 blit from W0: PASS"
                         : "P5 blit from W0: FAIL");
    outtextxy(4, 210, "Enter to exit");
    (void)getchar();

    closegraph();
    printf("P5 gfx blit:  %s\n", ok ? "PASS" : "FAIL");
    puts("done");
    return 0;
}
