/*
 * _irq_table — жизненный цикл общей IM2-таблицы (счётчик ссылок).
 *
 * Первый ref: проверка W2-адреса БУФЕРА (данные обязаны быть в W2),
 * запись базовой W1-страницы приложения, W2-размещение кадрового
 * трамплина (прямо или копией — см. ниже), построение таблицы (заливка
 * H + jp <W2-трамплин> по смещению H), I := H, IM 2, atexit-уборка.
 * Последний unref: I := старое, IM 1 (штатный режим DSS — его
 * обработчик на 0x0038; см. docs/samples/sprinterIntLib.asm).
 *
 * All-modes (docs/im2_isr_design.md): вектор-таблица и данные цепи
 * должны быть в W2 (это BSS — W2 в tiny/big/huge; в small для мелких
 * программ может уехать в W1 → EINVAL, safe).  Код трамплина в small/
 * huge лежит в W1 и в момент прерывания может быть перемаплен — если
 * &_irq_tramp < 0x8000, копируем его байты в _irq_tramp_w2buf (BSS=W2)
 * и направляем вектор туда.  Трамплин написан copy-safe (см. его шапку).
 */

#include <string.h>
#include <errno.h>
#include <sprinter_exit.h>
#include "_irq.h"

/* Прочитать регистр I. */
static uint8_t irq_get_i(void) __naked
{
    __asm
        ld    a, i
        ret
    __endasm;
}

/* Прочитать порт-регистр окна W1 (0xA2) — текущая физическая страница.
 * В main-контексте (install) это статическая базовая страница
 * приложения (small/huge); в tiny/big значение не используется по
 * назначению, но remap безвреден. */
static uint8_t irq_read_w1_page(void) __naked
{
    __asm
        in    a, (#_IRQ_W1_PORT)
        ret
    __endasm;
}

/* I := A, режим IM 2. */
static void irq_set_i_im2(uint8_t i_val) __naked
{
    (void)i_val;
    __asm
        ;; __sdcccall(1): uint8_t-аргумент уже в A.
        ld    i, a
        im    2
        ret
    __endasm;
}

/* I := A, штатный IM 1 (DSS). */
static void irq_set_i_im1(uint8_t i_val) __naked
{
    (void)i_val;
    __asm
        ld    i, a
        im    1
        ret
    __endasm;
}

/* Уборка на exit: снять оба хендлера (CTC глушится обязательно —
 * иначе после выхода его прерывания задушат шелл). */
static void irq_atexit(void)
{
    irq_ctc_remove();
    irq_remove();
}

uint8_t *_irq_table(void)
{
    return (uint8_t *)(((uint16_t)((uint16_t)_irq_vec_buf + 255u))
                       & 0xFF00u);
}

int _irq_table_ref(void)
{
    if (_irq_refs) {
        _irq_refs++;
        return 0;
    }

    /* Вектор-таблица и данные цепи обязаны жить в W2 (0x8000-0xBFFF):
     * во время прерывания W1/W3 могут быть перемаплены.  Это BSS — W2
     * во всех режимах, КРОМЕ small для мелких программ (данные ещё в
     * W1) → EINVAL, safe.  Код трамплина в W1 обрабатывается копией. */
    uint16_t buf = (uint16_t)_irq_vec_buf;
    if (buf < 0x8000 || buf + _IRQ_VEC_BUF_SIZE > 0xC000) {
        errno = EINVAL;
        return -1;
    }

    /* Базовая W1-страница приложения — для W1-remap в трамплине. */
    _irq_app_w1_page = irq_read_w1_page();

    /* W2-адрес кадрового трамплина: если оригинал уже в W2 (tiny/big) —
     * прямо; иначе (small/huge, код в W1) — копия в W2-буфер. */
    uint16_t tramp = (uint16_t)&_irq_tramp;
    if (tramp < 0x8000) {
        uint16_t n = (uint16_t)_irq_tramp_end - tramp;
        memcpy(_irq_tramp_w2buf, (const void *)tramp, n);
        tramp = (uint16_t)_irq_tramp_w2buf;
    }

    uint8_t *table = _irq_table();
    uint8_t  h     = (uint8_t)((uint16_t)table >> 8);

    IRQ_DISABLE();

    /* Старое I — только чтобы вернуть регистр при unref: DSS работает
     * в IM 1 (I=0x3F — наследие Spectrum ROM, НЕ таблица). */
    _irq_old_i = irq_get_i();

    memset(table, h, 257);
    uint8_t *stub = table + h;         /* h < 0xC0 — с [0xFF] не пересекается */
    stub[0] = 0xC3;                    /* jp <W2-трамплин> */
    stub[1] = (uint8_t)(tramp & 0xFF);
    stub[2] = (uint8_t)(tramp >> 8);

    irq_set_i_im2(h);
    IRQ_ENABLE();

    _irq_refs = 1;

    static uint8_t atexit_armed;
    if (!atexit_armed) {
        atexit_armed = 1;
        atexit(irq_atexit);
    }
    return 0;
}

void _irq_table_unref(void)
{
    if (!_irq_refs) return;
    if (--_irq_refs) return;
    IRQ_DISABLE();
    irq_set_i_im1(_irq_old_i);
    IRQ_ENABLE();
}
