/*
 * _irq_table — жизненный цикл общей IM2-таблицы (счётчик ссылок).
 *
 * Первый ref: проверка W2-адресов, построение таблицы (заливка H +
 * jp _irq_tramp по смещению H), I := H, IM 2, atexit-уборка.
 * Последний unref: I := старое, IM 1 (штатный режим DSS — его
 * обработчик на 0x0038; см. docs/samples/sprinterIntLib.asm).
 */

#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;
}

/* 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 могут быть перемаплены.  CODE вне W2 =
     * неподходящий memory mode (small/huge). */
    uint16_t tramp = (uint16_t)&_irq_tramp;
    uint16_t buf   = (uint16_t)_irq_vec_buf;
    if (tramp < 0x8000 || tramp >= 0xC000 ||
        buf   < 0x8000 || buf + _IRQ_VEC_BUF_SIZE > 0xC000) {
        errno = EINVAL;
        return -1;
    }

    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();
    _irq_dss_target = 0x0038;

    memset(table, h, 257);
    uint8_t *stub = table + h;         /* h < 0xC0 — с [0xFF] не пересекается */
    stub[0] = 0xC3;                    /* jp _irq_tramp */
    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();
}
