/*
 * poc.c — PoC порта Prince of Persia (applications/PoP/docs/PORT_PLAN.md
 * §5): проверяем управление (raw-клавиатура, held-state) + коллизию
 * по краям экрана + анимацию ходьбы + прыжок/присед поверх готового
 * спрайтового движка (sprite.h).
 *
 * ВАЖНО: персонаж — ВРЕМЕННАЯ ЗАГЛУШКА (лицензированный спрайт-пак
 * third_party/16x16-RPG-characters через tools/gen_kid_placeholder.py,
 * тот же источник, что уже использует examples/rpgwalk), НЕ графика
 * оригинальной Prince of Persia — см. §5 и §8.5 плана. У заглушки нет
 * отдельных поз прыжка/приседа — механика (тайминг дуги, состояние,
 * коллизия с полом) проверяется на том же спрайте без смены позы;
 * визуально это упрощение, не финальный вид.
 *
 * Дуга прыжка (jump_height[]) — СВОЯ, приблизительная (не таблица
 * смещений оригинала — см. §6 плана: авторские таблицы кадров решено
 * не переносить, только код/структуры).
 *
 * Нет ещё (следующие итерации): реальный уровень/фон по BLUETYPE,
 * рывок вбок при прыжке с разбега, зацепление за уступ.
 */
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include <kbd_raw.h>
#include <conio.h>
#include <stdio.h>
#include "level.h"
#include "room.h"

/* Комната 1 уровня 1 — РЕАЛЬНАЯ геометрия (foretable/backtable),
 * вырезана tools/extract_room.py из applications/PoP/SDLPoP/data/
 * LEVELS/res2001.bin (level_load_room(), см. level.h/room.c) — не
 * плейсхолдер.  Верхний ряд (row0) — floor-уступ на cols 3-7 (там
 * реально стоит персонаж в оригинале), стены по cols 8-9; ряды 1-2 —
 * ниже уступа (торч/колонны/пол — Фаза 1 просто их отрисовывает по
 * тем же типам, без многоуровневой физики падения). */
static level_t test_level;

#define CHAR_ROW  0                    /* ряд, где стоит персонаж (floor-уступ room1) */
#define ROW_Y(r)  ((r) * 64)           /* room_row_h все по 64 */
#define GROUND_Y  ROW_Y(CHAR_ROW + 1)  /* низ ряда CHAR_ROW = верх пола */
#define KIDY      (GROUND_Y - 16)      /* y спрайта (16 px высотой) */
#define SPEED     2                    /* px/кадр — заглушка, не авторский темп */

/* Настоящая преграда (Wall/Pillar) слева/справа от кандидата x в
 * CHAR_ROW — блокирует движение (грубая проверка по краям хитбокса
 * 16px, без под-тайловой подгонки — для PoC достаточно, см.
 * PORT_PLAN.md §6).  tile_blocks_side(), НЕ tile_is_solid(): Floor —
 * тайл, на котором Кид СТОИТ (тот же CHAR_ROW), tile_is_solid() его
 * тоже считает "твёрдым" (можно стоять сверху) — если проверять им же
 * боковую преграду, Кид не мог сдвинуться с собственного пола (баг,
 * найден 2026-07-16). */
static uint8_t col_blocked(int x)
{
    uint8_t c0, c1;

    if (x < 0 || x + 15 >= ROOM_COLS * ROOM_TILE_W)
        return 1;
    c0 = (uint8_t)(x / ROOM_TILE_W);
    c1 = (uint8_t)((x + 15) / ROOM_TILE_W);
    if (tile_blocks_side(level_tile(&test_level, 1, c0, CHAR_ROW)->type))
        return 1;
    if (tile_blocks_side(level_tile(&test_level, 1, c1, CHAR_ROW)->type))
        return 1;
    return 0;
}

/* Ленты атласа: dir*3+frame, dir 0=вниз/1=влево/2=вправо/3=вверх,
 * 3 кадра маятника на направление (см. tools/gen_kid_placeholder.py). */
#define DIR_DOWN  0
#define DIR_LEFT  1
#define DIR_RIGHT 2

/* Дуга прыжка: своя, приблизительная (не авторская таблица, см. шапку
 * файла) — высота над полом (px) по кадрам 0..19, УЖЕ ПОЛНЫЙ горб
 * (подъём 2→22 к элементу 10, спуск обратно к 2 к элементу 19) — БЕЗ
 * зеркалирования в коде, массив читается один раз целиком.  БАГ,
 * найденный пользователем 2026-07-15: раньше код ЕЩЁ РАЗ зеркалил
 * этот уже полный горб на 40 кадров — получалось два полных прыжка
 * подряд от одного триггера (не проблема клавиатуры/декодера, чистая
 * рассинхронизация данных и комментария). 20 кадров @ 50 Гц ~= 0.4 с. */
static const uint8_t jump_height[20] = {
    2, 4, 7, 10, 13, 16, 18, 20, 21, 22,
    22, 21, 20, 18, 16, 13, 10, 7, 4, 2
};
#define JUMP_FRAMES  20

static atlas_t  at;
static sprite_t kid;
static uint8_t  facing = DIR_DOWN;   /* текущее направление анимации */
static uint8_t  jumping = 0;         /* 0 = на земле */
static uint8_t  jump_t  = 0;         /* кадр дуги, 0..JUMP_FRAMES-1 */
static uint8_t  crouching = 0;
/* Прыжок — level-triggered НАМЕРЕННО (не edge-detect): если UP всё ещё
 * зажат к моменту приземления — следующий прыжок стартует СРАЗУ (цепочка
 * прыжков, пока держишь); отпустил раньше — второй прыжок не начнётся
 * сам, только по следующему нажатию.  Раньше здесь были up_prev/
 * jump_cooldown — попытка "починить" ровно ЭТО поведение, приняв его
 * за баг; убрано 2026-07-15 после уточнения желаемого поведения. */

static void draw_room(void)
{
    setfillstyle(SOLID_FILL, BLACK);
    bar(0, 0, 319, 255);
    room_draw(&test_level, 1);
    setcolor(LIGHTGRAY);
    outtextxy(60, 4, "PoP PoC: hold LEFT/RIGHT to walk, ESC to quit");
    outtextxy(4, 14, "(placeholder tiles -- not original PoP art)");
}

/* HUD-плашка состояния (нет отдельной позы прыжка/приседа — статус
 * текстом, рисуется банком 0x50, heal спрайтового движка её не
 * трогает — как fps-плашка в examples/rpgwalk). */
static void show_state(uint8_t jump, uint8_t crouch)
{
    setfillstyle(SOLID_FILL, BLACK);
    bar(0, 24, 60, 32);
    setcolor(YELLOW);
    if (jump)
        outtextxy(0, 24, "JUMP");
    else if (crouch)
        outtextxy(0, 24, "CROUCH");
}

static void set_facing(uint8_t dir)
{
    if (facing == dir)
        return;
    facing = dir;
    sprite_anim(&kid, (uint8_t)(dir * 3), (uint8_t)(dir * 3 + 2),
                6, ANIM_PINGPONG);
}

int main(void)
{
    uint8_t page, hidden;
    int x;

    if (level_load_room(&test_level, 1, "room1.dat") != 0 &&
        level_load_room(&test_level, 1, "a:\\room1.dat") != 0) {
        puts("room1.dat not found");
        return 1;
    }
    /* Стартовая позиция.  В данных room1 start_location = tileOffset 0
     * (row0/col0) — а там EMPTY (провал, без пола); в Фазе 1 нет физики
     * падения, поэтому для PoC ставим Кида на floor-уступ (col 3, где он
     * реально стоит в оригинале).  Когда появится многоуровневая физика —
     * брать col из start_location.  start_direction: -1 = влево, 0 =
     * вправо (level.h). */
    x = 3 * ROOM_TILE_W;
    facing = (test_level.start_direction < 0) ? DIR_LEFT : DIR_RIGHT;

    if (atlas_load(&at, "kid.atl") != 0 &&
        atlas_load(&at, "a:\\kid.atl") != 0) {
        puts("kid.atl not found");
        return 1;
    }
    if (tile_atlas_load("") == 0 && tile_atlas_load("a:\\") == 0) {
        puts("tile atlases not found");
        atlas_free(&at);
        return 1;
    }

    initgraph();
    /* room.pal = EGA16 + Kid + Floor + Wall — ОДНА палитра на всё,
     * собрана tools/build_room_palette.py (см. --seed-pal в
     * toolchain/png_strip.py) — Kid и тайлы на экране одновременно,
     * их "свои" цвета обязаны жить в одной таблице. */
    if (gfx_pal_fload(0, "room.pal") < 0)
        gfx_pal_fload(0, "a:\\room.pal");
    gfx_pal_sync();
    gfx_sprite_clip(0);          /* коллизия по краям гарантирует границы */

    /* kid.atl — ОДНА лента (12 кадров вертикально: dir*3+кадр, см. шапку
     * файла и examples/rpgwalk); индекс atlas_sprite_init — это НОМЕР
     * ЛЕНТЫ (персонажа), а не кадра.  Лента одна → всегда 0.  Стартовый
     * кадр направления выставляем sprite_frame (вертикальная лента, fh=16:
     * кадр N на sy=N*16).  Баг Соннета (найден 2026-07-16): здесь стоял
     * facing*3 как индекс ЛЕНТЫ — при старте лицом влево (idx 3) читался
     * мусор за каталогом атласа → мусорные w/h/src → блит спрайта заливал
     * пол-экрана «снегом». */
    atlas_sprite_init(&kid, &at, 0);
    sprite_frame(&kid, 0, (int)(facing * 3) * 16);
    kid.x = x;
    kid.y = KIDY;
    sprite_show(&kid);

    for (page = 0; page < 2; page++) {   /* фон + спрайт на обе страницы */
        gfx_set_draw_page(page);
        draw_room();
        sprite_update(&kid, 1);
    }
    gfx_set_visible_page(0);

    /* kbd_raw требует BSS в W2 (IM2-таблица) — недоступно в --memory small
     * (там BSS может уехать в W1 → EINVAL); poc собирается --memory huge
     * (CODE в W1, DATA/BSS в W2).  closegraph ДО puts — иначе сообщение
     * ушло бы в графический режим (невидимо), а программа молча вышла бы
     * в prompt (баг Соннета, найден 2026-07-16). */
    if (kbd_raw_open() != 0) {
        closegraph();
        puts("kbd_raw_open failed (need memory mode with BSS in W2)");
        atlas_free(&at);
        return 1;
    }

    for (;;) {
        uint8_t moving = 0;
        int y = KIDY;

        if (jumping) {
            /* дуга идёт сама; направлением можно скользить вбок,
             * поза не меняется (нет отдельного кадра прыжка) */
            if (kbd_raw_down(KBD_LEFT)) {
                if (!col_blocked(x - SPEED)) x -= SPEED;
            } else if (kbd_raw_down(KBD_RIGHT)) {
                if (!col_blocked(x + SPEED)) x += SPEED;
            }
            y = KIDY - jump_height[jump_t];
            jump_t++;
            if (jump_t >= JUMP_FRAMES) {
                jumping = 0;
                y = KIDY;
            }
        } else if (crouching) {
            if (!kbd_raw_down(KBD_DOWN))
                crouching = 0;
        } else if (kbd_raw_down(KBD_UP)) {
            jumping = 1;
            jump_t = 0;
        } else if (kbd_raw_down(KBD_DOWN)) {
            crouching = 1;
        } else if (kbd_raw_down(KBD_LEFT)) {
            if (!col_blocked(x - SPEED)) x -= SPEED;
            set_facing(DIR_LEFT);
            moving = 1;
        } else if (kbd_raw_down(KBD_RIGHT)) {
            if (!col_blocked(x + SPEED)) x += SPEED;
            set_facing(DIR_RIGHT);
            moving = 1;
        }
        if (!moving && !jumping && (sprite_anim_status(&kid) & SPR_ANIM_ON))
            sprite_anim_stop(&kid, (int8_t)(facing * 3));

        sprite_move(&kid, x, y);

        if (kbd_raw_down(KBD_ESC))
            break;

        hidden = gfx_get_visible_page() ^ 1;
        gfx_set_draw_page(hidden);
        show_state(jumping, crouching);
        sprite_update(&kid, 1);
        gfx_wait_vsync();
        gfx_set_visible_page(hidden);
    }

    kbd_raw_close();
    closegraph();
    tile_atlas_free();
    atlas_free(&at);
    return 0;
}
