/*
 * pop_trob.c — P0-каркас: room_modif + список trob + диспетчер анимаций.
 * Порт SDLPoP seg007 (process_trobs/animate_tile/add_trob/animate_spike).
 * См. pop_trob.h.
 */
#include "pop_trob.h"
#include "pop_level.h"
#include "pop_geom.h"
#include "pop_redraw.h"    /* пометки перерисовки тайлов (вместо прямых вызовов) */
/* pop_bg нужен ТОЛЬКО для пламени факела и пузырька зелья: это не тайловая
 * перерисовка, а покадровый оверлей поверх фона (в оригинале — свои
 * add_backtable/add_foretable внутри draw_tile).  Всё остальное идёт через
 * пометки, поэтому pop_process_trobs — единственная функция модуля, которую
 * нельзя звать из __banked-кода. */
#include "pop_bg.h"

/* Коды тайлов (fg & 0x1F), которые анимируются. */
#define TILE_SPIKE   0x02
#define TILE_GATE    0x04
#define TILE_CLOSER  0x06
#define TILE_DEBRIS  0x0E   /* button_type для «насовсем открыть» (died_on_button) */
#define TILE_OPENER  0x0F
#define TILE_POTION  0x0A
#define TILE_SWORD   0x16   /* меч-предмет на полу */
#define TILE_LEVELDOOR 0x10 /* левая половина двери уровня (на ней modif) */
#define TILE_TORCH   0x13
#define TILE_TORCH_DEBRIS 0x1E

/* ---- Персистентное per-room modifier-состояние -------------------- */
static uint8_t room_modif[POP_NROOMS][POP_ROOMTILES];
static uint8_t room_seen[POP_NROOMS];   /* modif проинициализирован из уровня */

/* ---- Список анимируемых тайлов ------------------------------------ */
typedef struct { uint8_t room, tilepos; int8_t type; } trob_t;
#define TROBS_MAX 30   /* как в оригинале (types.h) */
static trob_t  trobs[TROBS_MAX];
static uint8_t trobs_count;
static uint8_t trob_code[TROBS_MAX];   /* префетч кодов тайлов на кадр (1 map W0) */

/* Последняя НАРИСОВАННАЯ draw-сигнатура тайла (spike: modifier; button:
 * признак «нажата» = таймер связи >1).  Редрой пики/кнопки — ТОЛЬКО при её
 * изменении (порт redraw_11h-on-change из SDLPoP).  Без этого кнопка под
 * стоящим Кидом перерисовывалась КАЖДЫЙ кадр (2×draw_tile ≈ 50% кадра в
 * статике), т.к. её trob висит, пока Кид стоит (это верно, как в SDLPoP —
 * расхождение было именно в безусловном редрое). */
static uint8_t trob_drawn[POP_ROOMTILES];

/* prandom (seg009.c:321, 32-бит LCG) — нужен для стартовой фазы факела и
 * выбора следующего кадра пламени. */
static pop_rnd_t trob_seed;

#define trob_prandom(maxv) pop_prandom(&trob_seed, (maxv))


void pop_trob_reset(void)
{
    uint8_t i;
    for (i = 0; i < POP_NROOMS; i++) room_seen[i] = 0;
    for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
    pop_redraw_reset();
    trobs_count = 0;
}

uint8_t *pop_trob_modif(uint8_t room)
{
    if (room < 1 || room > POP_NROOMS) return 0;
    if (!room_seen[room - 1]) {
        uint8_t i;
        pop_level_room_bg(room, room_modif[room - 1]);
        /* seg009 load_level: у ЗЕЛЬЯ модификатор сдвигается влево на 3 —
         * старшие биты = тип зелья, младшие 3 = фаза пузырька. */
        pop_level_access_begin();
        for (i = 0; i < POP_ROOMTILES; i++)
            if ((pop_level_tile_raw(room, i) & 0x1F) == TILE_POTION)
                room_modif[room - 1][i] = (uint8_t)(room_modif[room - 1][i] << 3);
        pop_level_access_end();
        room_seen[room - 1] = 1;
    }
    return room_modif[room - 1];
}

static int find_trob(uint8_t room, uint8_t tilepos)
{
    uint8_t i;
    for (i = 0; i < trobs_count; i++)
        if (trobs[i].room == room && trobs[i].tilepos == tilepos) return i;
    return -1;
}

void pop_add_trob(uint8_t room, uint8_t tilepos, int8_t type)
{
    int f = find_trob(room, tilepos);
    if (f >= 0) { trobs[f].type = type; return; }
    if (trobs_count >= TROBS_MAX) return;
    trobs[trobs_count].room = room;
    trobs[trobs_count].tilepos = tilepos;
    trobs[trobs_count].type = type;
    trobs_count++;
}

void pop_trob_room_changed(void)
{
    uint8_t i;
    for (i = 0; i < POP_ROOMTILES; i++) trob_drawn[i] = 0;
    pop_redraw_reset();
}

/* anim_tile_modif (seg000:0B12) — при входе в комнату завести анимации её
 * тайлов: факелам — случайная стартовая фаза пламени (start_anim_torch,
 * seg007:0847: modif = prandom(8)), зельям — случайная фаза пузырька
 * (start_anim_potion: младшие 3 бита = prandom(6)+1, старшие = тип). */
void pop_trob_anim_room(uint8_t room)
{
    uint8_t *mod = pop_trob_modif(room);
    uint8_t i, code;
    if (!mod) return;
    pop_level_access_begin();
    /* Освободить список от «декоративных» trob ДРУГИХ комнат: факел, зелье и
     * блеск меча анимируются только в ОТРИСОВАННОЙ комнате, а место в списке
     * занимают.  Оригинал их копит и на 30-й выдаёт «Trobs Overflow»; у нас
     * отладочный обход всех 24 комнат упирается в лимит за пару минут, после
     * чего новые анимации молча не заводятся.  Механику (ворота/пики/кнопки,
     * в т.ч. кросс-комнатные) чистка не трогает. */
    {
        uint8_t k, wr = 0;
        for (k = 0; k < trobs_count; k++) {
            uint8_t c = (uint8_t)(pop_level_tile_raw(trobs[k].room,
                                                     trobs[k].tilepos) & 0x1F);
            if (trobs[k].room != room &&
                (c == TILE_TORCH || c == TILE_TORCH_DEBRIS ||
                 c == TILE_POTION || c == TILE_SWORD))
                continue;
            trobs[wr++] = trobs[k];
        }
        trobs_count = wr;
    }
    for (i = 0; i < POP_ROOMTILES; i++) {
        code = (uint8_t)(pop_level_tile_raw(room, i) & 0x1F);
        if (code == TILE_TORCH || code == TILE_TORCH_DEBRIS) {
            mod[i] = (uint8_t)trob_prandom(8);
            pop_add_trob(room, i, 1);
        } else if (code == TILE_POTION) {
            mod[i] = (uint8_t)((mod[i] & 0xF8) | (trob_prandom(6) + 1));
            pop_add_trob(room, i, 1);
        } else if (code == TILE_SWORD) {
            /* start_anim_sword (seg007:087C): случайная фаза счётчика блеска. */
            mod[i] = (uint8_t)(trob_prandom(0xFF) & 0x1F);
            pop_add_trob(room, i, 1);
        }
    }
    pop_level_access_end();
}

/* animate_spike (seg007:04D3): автомат по modifier.  *m — modifier тайла,
 * *type — тип trob (>=0 активен, <0 = удалить).  Выдвиг: ++modif (1..4),
 * на 5 → 0x8F (полностью вышли, безвредны), на 9 → 0 + конец.  Убирание
 * (флаг 0x80): --modif; когда 0x7F-часть обнулилась → modif=6 (начать
 * визуальный уход 6..8).  0xFF = disabled. */
/* get_torch_frame (seg007:06CD): случайный кадр 0..8, иначе следующий. */
static uint8_t get_torch_frame(uint8_t curr)
{
    uint8_t next = (uint8_t)trob_prandom(255);
    if (next != curr && next < 9) return next;
    next = (uint8_t)(curr + 1);
    if (next >= 9) next = 0;
    return next;
}

/* bubble_next_frame (seg007:0759): фазы пузырька 1..7 по кругу. */
static uint8_t bubble_next_frame(uint8_t curr)
{
    uint8_t next = (uint8_t)(curr + 1);
    return (next >= 8) ? 1 : next;
}

static void animate_spike(uint8_t *m, int8_t *type)
{
    uint8_t mod = *m;
    if (*type >= 0) {
        if (mod == 0xFF) return;              /* disabled — не двигать */
        if (mod & 0x80) {
            --mod;
            if (mod & 0x7F) { *m = mod; return; }
            mod = 6;
        } else {
            ++mod;
            if (mod == 5) mod = 0x8F;
            else if (mod == 9) { mod = 0; *type = -1; }
        }
    }
    *m = mod;
}

/* ---- Кнопки/ворота (seg007) --------------------------------------- *
 * Таблицы связей LINKLOC/LINKMAP — в pop_level (мутабельная копия в W2).
 * doorlink1 (d1) = LINKLOC, doorlink2 (d2) = LINKMAP. */
static uint8_t get_doorlink_timer(uint8_t i) { return (uint8_t)(pop_doorlink2(i) & 0x1F); }
static void    set_doorlink_timer(uint8_t i, uint8_t v)
{
    pop_doorlink2_set(i, (uint8_t)((pop_doorlink2(i) & 0xE0) | (v & 0x1F)));
}
static uint8_t get_doorlink_tile(uint8_t i) { return (uint8_t)(pop_doorlink1(i) & 0x1F); }
static uint8_t get_doorlink_next(uint8_t i) { return (uint8_t)(!(pop_doorlink1(i) & 0x80)); }
static uint8_t get_doorlink_room(uint8_t i)
{
    return (uint8_t)(((pop_doorlink1(i) & 0x60) >> 5) + ((pop_doorlink2(i) & 0xE0) >> 3));
}

/* trigger_gate (seg007:092C): изменить openness ворот по типу кнопки.
 * Возвращает тип анимации для add_trob (или -1 = ничего). */
static int8_t trigger_gate(uint8_t room, uint8_t tilepos, uint8_t button_type)
{
    uint8_t *mod = pop_trob_modif(room);
    uint8_t modifier = mod[tilepos];
    if (button_type == TILE_OPENER) {
        if (modifier == 0xFF) return -1;               /* уже насовсем открыт */
        if (modifier >= 188) { mod[tilepos] = 238; return -1; }  /* открыт → держать */
        mod[tilepos] = (uint8_t)((modifier + 3) & 0xFC);
        return 1;                                       /* обычное открытие */
    } else if (button_type == TILE_DEBRIS) {           /* насовсем (died_on_button) */
        if (modifier < 188) return 2;
        mod[tilepos] = 0xFF;
        return -1;
    } else {                                            /* closer */
        if (modifier != 0) return 3;                    /* быстро закрыть */
        return -1;
    }
}

/* trigger_1 (seg007:0999): цель = ворота → trigger_gate; иначе -1 (leveldoor
 * и прочее — отложено). */
static int8_t trigger_1(uint8_t target_type, uint8_t room, uint8_t tilepos, uint8_t button_type)
{
    if (target_type == TILE_GATE)
        return trigger_gate(room, tilepos, button_type);
    if (target_type == TILE_LEVELDOOR) {
        /* trigger_1 (seg007:0999): дверь уровня открывается ОДИН раз — если
         * створка уже тронулась (modif != 0), кнопка ничего не делает. */
        uint8_t *m = pop_trob_modif(room);
        if (m && m[tilepos] == 0) return 1;
        return -1;
    }
    return -1;
}

/* do_trigger_list (seg007:09E5): обойти цепочку LINKLOC от index, для каждой
 * цели добавить trob (если trigger_1 >= 0). */
static void do_trigger_list(uint8_t index, uint8_t button_type)
{
    for (;;) {
        uint8_t room = get_doorlink_room(index);
        uint8_t tilepos = get_doorlink_tile(index);
        uint8_t target_type = pop_level_tile(room, tilepos);
        int8_t result = trigger_1(target_type, room, tilepos, button_type);
        if (result >= 0) pop_add_trob(room, tilepos, result);
        if (get_doorlink_next(index) == 0) break;
        index++;
    }
}

void pop_trigger_button(uint8_t room, uint8_t tilepos,
                        uint8_t button_type, uint8_t modifier)
{
    uint8_t link_timer = get_doorlink_timer(modifier);
    if (link_timer != 0x1F) {                          /* не заклинено */
        set_doorlink_timer(modifier, 5);
        if (link_timer < 2)
            pop_add_trob(room, tilepos, 1);            /* кнопка нажимается (+ TODO sound) */
        do_trigger_list(modifier, button_type);
    }
}

/* animate_button (seg007:0D3A): отсчитать таймер связи; <2 → кнопка
 * отжимается (trob кончился).  idx = модификатор кнопки (индекс LINKLOC). */
static void animate_button(uint8_t idx, int8_t *type)
{
    if (*type >= 0) {
        uint8_t timer = get_doorlink_timer(idx);
        timer = (uint8_t)(timer ? timer - 1 : 0);
        set_doorlink_timer(idx, timer);
        if (timer < 2) *type = -1;
    }
}

/* animate_leveldoor (seg007:05F1): створка двери уровня.  type 0..2 —
 * открытие (modif 0 -> 43, по пикселю за кадр), type >= 3 — быстрое
 * закрытие со скоростями {0,5,17,99} (после выхода из уровня).  Готово ->
 * type = -1. */
static const uint8_t leveldoor_close_speeds[5] = {0, 5, 17, 99, 0};

static void animate_leveldoor(uint8_t *m, int8_t *type)
{
    if (*type < 0) return;
    if (*type >= 3) {
        uint8_t sp;
        if (*type < 7) (*type)++;
        sp = leveldoor_close_speeds[*type - 3];
        if (sp >= *m) { *m = 0; *type = -1; }
        else           *m = (uint8_t)(*m - sp);
    } else {
        if (*m < 43) (*m)++;
        if (*m >= 43) { *m = 43; *type = -1; }
    }
}

/* animate_door (seg007:0522): openness по типу анимации.  *m — modif ворот,
 * *type — 0 закрытие / 1 открытие / 2 насовсем / 3..8 быстрое закрытие. */
static const uint8_t gate_close_speeds[9] = {0, 0, 0, 20, 40, 60, 80, 100, 120};
static const int8_t  door_delta[3] = {-1, 4, 4};

static void animate_door(uint8_t *m, int8_t *type)
{
    int8_t anim_type = *type;
    uint8_t mod = *m;
    if (anim_type >= 0) {
        if (anim_type >= 3) {                          /* быстрое закрытие */
            if (anim_type < 8) { anim_type++; *type = anim_type; }
            {
                int new_mod = (int)mod - gate_close_speeds[anim_type];
                if (new_mod < 0) { mod = 0; *type = -1; }
                else mod = (uint8_t)new_mod;
            }
        } else if (mod != 0xFF) {
            mod = (uint8_t)(mod + door_delta[anim_type]);
            if (anim_type == 0) {                      /* закрытие */
                if (mod == 0) *type = -1;              /* gate_stop */
            } else {                                    /* открытие */
                if (mod >= 188) {
                    if (anim_type < 2) { mod = 238; *type = 0; }  /* открыт → потом закрывать */
                    else { mod = 0xFF; *type = -1; }              /* насовсем */
                }
            }
        } else {
            *type = -1;
        }
    }
    *m = mod;
}

uint8_t pop_trob_room_active(uint8_t room)
{
    uint8_t i;
    for (i = 0; i < trobs_count; i++)
        if (trobs[i].room == room) return 1;
    return 0;
}


/* Темп анимации пламени в ЛОГИЧЕСКИХ кадрах (один логический кадр = 3
 * vsync и соответствует игровому тику оригинала):
 *   1 — кадр пламени каждый тик, как в оригинале; кадры при этом ровные по
 *       тактам, что нужно для счёта бюджета;
 *   2 — вдвое реже (так было до 2026-07-30; пламя шло на половинной
 *       скорости, а бюджет кадра «прыгал» на 6.2 % от того, попал кадр на
 *       анимацию или нет). */
#define TORCH_ANIM_DIV 1

#if TORCH_ANIM_DIV > 1
static uint8_t torch_tick;
#define TORCH_ANIM_NOW ((uint8_t)(torch_tick % TORCH_ANIM_DIV) == 0)
#else
#define TORCH_ANIM_NOW 1
#endif

void pop_process_trobs(uint8_t cur_room)
{
    uint8_t i, w = 0;
#if TORCH_ANIM_DIV > 1
    torch_tick++;
#endif

    /* Перерисовка «покоя» законченных пик/кнопок (обе страницы).
     * row/col ведём бегущими счётчиками, а НЕ через i/10, i%10: иначе SDCC
     * выносит оба деления (__divuchar/__moduchar) в вершину цикла как общее
     * подвыражение аргументов redraw — 30*2=60 делений/кадр даже когда нет
     * активных пик/кнопок (тот же случай, что был в pop_loose_tick). */
    if (trobs_count == 0) return;

    /* Префетч кодов тайлов всех trob'ов за ОДИН map/unmap окна 0, а не
     * pop_level_tile() на каждый trob (тот свапал W0 каждый вызов = N свапов
     * за кадр даже без анимации).  Дальше цикл (с редроями) идёт с
     * размапленным W0 — как раньше. */
    {
        uint8_t k;
        pop_level_access_begin();
        for (k = 0; k < trobs_count; k++)
            trob_code[k] = pop_level_tile_raw(trobs[k].room, trobs[k].tilepos);
        pop_level_access_end();
    }

    for (i = 0; i < trobs_count; i++) {
        uint8_t room = trobs[i].room;
        uint8_t tp   = trobs[i].tilepos;
        uint8_t code = trob_code[i];
        uint8_t *mod = pop_trob_modif(room);
        int8_t  type = trobs[i].type;

        switch (code) {
        case TILE_SPIKE:
            animate_spike(&mod[tp], &type);
            break;
        case TILE_OPENER:
        case TILE_CLOSER:
            animate_button(mod[tp], &type);   /* mod[tp] = индекс LINKLOC кнопки */
            break;
        case TILE_GATE:
            animate_door(&mod[tp], &type);
            break;
        case TILE_LEVELDOOR:
            animate_leveldoor(&mod[tp], &type);
            break;
        case TILE_TORCH:
        case TILE_TORCH_DEBRIS:
            /* animate_torch (seg007:03C1): анимация вечная — type остаётся
             * 1, пока комната отрисована.  Темп — TORCH_ANIM_DIV. */
            if (room == cur_room && TORCH_ANIM_NOW)
                mod[tp] = get_torch_frame(mod[tp]);
            break;
        case TILE_POTION:
            /* animate_potion (seg007:03E9): крутим младшие 3 бита (фаза
             * пузырька), старшие 5 — тип зелья. */
            if (room == cur_room)
                mod[tp] = (uint8_t)((mod[tp] & 0xF8) | bubble_next_frame(mod[tp] & 7));
            break;
        case TILE_SWORD:
            /* animate_sword (seg007:0425): счётчик вниз; на 0 — новый период
             * 0x28..0x67.  Блеск рисуется РОВНО на modif==1 (draw_tile берёт
             * кадр (modif==1)+10), т.е. одиночная вспышка раз в 40..103 тика. */
            if (room == cur_room) {
                if (--mod[tp] == 0)
                    mod[tp] = (uint8_t)((trob_prandom(255) & 0x3F) + 0x28);
            }
            break;
        default:
            type = -1;
            break;
        }
        trobs[i].type = type;

        if (room == cur_room &&
            (code == TILE_TORCH || code == TILE_TORCH_DEBRIS || code == TILE_POTION)) {
            /* Пламя/пузырёк анимируются КАЖДЫЙ кадр и в фон не запечены —
             * рисуем поверх (сами делают heal своей области). */
            uint8_t trow = tp / 10, tcol = tp % 10;
            if (code == TILE_POTION) pop_potion_draw(trow, tcol, mod[tp]);
            else                     pop_torch_draw(trow, tcol, mod[tp]);
        }
        if (room == cur_room && code == TILE_SPIKE) {
            /* ПИКИ — редрой КАЖДЫЙ кадр, пока trob жив: animate_spike
             * (seg007:0353) зовёт redraw_21h БЕЗУСЛОВНО, а не по смене кадра.
             * Это не «лишняя работа оригинала»: выдвинутая пика живёт только в
             * видео-ОЗУ (банк SPRITE, в фон не запечена), а kid_heal каждый кадр
             * возвращает под спрайтом Кида ПЕЧЁНЫЙ фон — то есть стирает те
             * части остриёв, что попали под его прямоугольник.  При редрое
             * по смене кадра пика держится 15 кадров (modif 0x8F..0x81) с
             * неизменной сигнатурой, и стёртое уже не возвращается: рядом с
             * Кидом остриё пропадало, а нижний кусок мерцал (стёрт на одной
             * странице дабл-буфера из двух). */
            /* Последний кадр (пика убралась, trob умирает) — пометить ОБЕ
             * страницы: живой trob метит каждый кадр по одной. */
            pop_set_redraw(tp, POP_RD_SPIKE, (uint8_t)(type < 0 ? 2 : 1));
        }
        if (room == cur_room && code == TILE_GATE) {
            /* draw_trob (seg007:01E6), которым заканчивается animate_door:
             * пока trob жив — решётка едет каждый кадр, помечаем ТЕКУЩУЮ
             * страницу; последний кадр (type<0, ворота встали) — ОБЕ, иначе
             * на второй странице дабл-буфера застынет предыдущее положение
             * и решётка дрожит через кадр.  Тайлпос — САМИ ворота, соседа
             * справа выбирает pop_gate_redraw. */
            pop_set_redraw(tp, POP_RD_GATE, (uint8_t)(type < 0 ? 2 : 1));
        }
        if (room == cur_room && code == TILE_LEVELDOOR) {
            /* Створка едет каждый кадр — перерисовываем ПРАВУЮ половину (там
             * её рисует draw_tile_anim_right) С ЗАПЕЧКОЙ в ОЗУ-копию: иначе
             * kid_heal вернул бы из фона закрытую створку.  Куски двери
             * непрозрачны, поэтому wipe не нужен. */
            /* Створку рисует ПРАВАЯ половина двери; последний кадр анимации
             * помечаем на обе страницы, иначе дверь дрожит через кадр. */
            if (tp % 10 + 1 < 10)
                pop_set_redraw((uint8_t)(tp + 1), POP_RD_LEVELDOOR,
                               (uint8_t)(type < 0 ? 2 : 1));
        }
        if (room == cur_room && code == TILE_SWORD) {
            /* МЕЧ — редрой только на смене видимого кадра (блеск = modif==1):
             * спрайт запечён в фон (draw_tile), поэтому перерисовываем тайл
             * той же схемой, что и кнопку — текущая страница сразу, вторая
             * следующим кадром через rest-цикл. */
            uint8_t sig = (uint8_t)(mod[tp] == 1);
            if (sig != trob_drawn[tp]) {
                pop_set_redraw(tp, POP_RD_FLOOR, 2);   /* обе страницы */
                trob_drawn[tp] = sig;
            }
        }
        if (room == cur_room && (code == TILE_OPENER || code == TILE_CLOSER)) {
            /* КНОПКА — редрой только при смене видимого состояния (нажата =
             * таймер связи >1): пока Кид её держит, картинка стабильна.
             * Рисуем ТЕКУЩУЮ страницу сразу + <rest>=1 (вторая страница
             * дабл-буфера следующим кадром через rest-цикл).  tp/10,tp%10
             * считаем только в момент редроя (редко), а не каждый кадр. */
            uint8_t sig = (uint8_t)(get_doorlink_timer(mod[tp]) > 1);
            if (sig != trob_drawn[tp]) {
                pop_set_redraw(tp, POP_RD_FLOOR, 2);   /* обе страницы */
                trob_drawn[tp] = sig;
            }
        }
    }

    /* compact: удалить завершённые (type < 0). */
    for (i = 0; i < trobs_count; i++)
        if (trobs[i].type >= 0) trobs[w++] = trobs[i];
    trobs_count = w;
}
