#!/usr/bin/env python3
"""
pop_extract_kid_data.py — извлекает данные анимации Kid из исходников
SDLPoP в C-заголовок roomtest/kid_data.h (Фаза K1).

Извлекает:
  frame_table_kid[]         (seg006.c) -> kframe kid_frames[] {image,dx,dy,flags}
  original_seqtbl[]         (seqtbl.c) -> byte kid_seqtbl[]   (2310 Б, база 0x196E)
  original_seqtbl_offsets[] (seqtbl.c) -> word kid_seq_off[]  (seq_id -> адрес)

seqtbl адресуется базой SEQTBL_BASE=0x196E: элемент = kid_seqtbl[addr-0x196E].
Автогенерация — чтобы не переписывать ~180 кадров + 2КБ байткода руками.
"""
import os
import re

SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "SDLPoP", "src")
OUT_BIN_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                           "..", "poc", "res", "kid")
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                   "..", "roomtest", "kid_data.h")
SEQTBL_BASE = 0x196E


def read(name):
    return open(os.path.join(SRC, name)).read()


def parse_frame_table(txt, name="frame_table_kid"):
    """frame_table_kid[]/frame_tbl_guard[] -> список (image, dx, dy, flags).
    Записи вида { IMG, 0xC0| 4,  DX, DY, 0x40| 4 } — sword/flags как
    OR-выражения."""
    m = re.search(r'const frame_type ' + name + r'\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
    body = m.group(1)
    frames = []
    for row in re.findall(r'\{([^}]*)\}', body):
        parts = [p.strip() for p in row.split(',')]
        image = int(parts[0], 0)
        sword = eval(parts[1], {}, {})          # напр. "0x00| 9"
        dx = int(parts[2], 0)
        dy = int(parts[3], 0)
        flags = eval(parts[4], {}, {})
        frames.append((image & 0xFF, dx & 0xFF, dy & 0xFF, flags & 0xFF, sword & 0xFF))
    return frames


def parse_sword_tbl(txt):
    """sword_tbl[] (seg006:2047) -> список (id, x, y).  id 255 = клинка нет.
    Индекс — sword_frame (cur_frame.sword & 0x3F); один и тот же chtab_0 и у
    Кида, и у стража."""
    m = re.search(r'const sword_table_type sword_tbl\[\]\s*=\s*\{(.*?)\n\};', txt, re.S)
    rows = []
    for row in re.findall(r'\{([^}]*)\}', m.group(1)):
        a, b, c = [int(p.strip(), 0) for p in row.split(',')]
        rows.append((a & 0xFF, b & 0xFF, c & 0xFF))
    return rows


def parse_byte_array(txt, name):
    m = re.search(r'const byte %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S)
    return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))]


def parse_word_array(txt, name):
    m = re.search(r'const word %s\[\]\s*=\s*\{(.*?)\}' % name, txt, re.S)
    return [int(x, 0) for x in re.findall(r'0x[0-9A-Fa-f]+|\d+', m.group(1))]


def emit_c(vals, per_line=12, fmt="0x%02X"):
    out = []
    for i in range(0, len(vals), per_line):
        out.append("    " + ",".join(fmt % v for v in vals[i:i + per_line]) + ",")
    return "\n".join(out)


def main():
    seg006 = read("seg006.c")
    seqc = read("seqtbl.c")

    frames = parse_frame_table(seg006)
    gframes = parse_frame_table(seg006, "frame_tbl_guard")
    seqtbl = parse_byte_array(seqc, "original_seqtbl")
    offs = parse_word_array(seqc, "original_seqtbl_offsets")
    swtbl = parse_sword_tbl(seg006)

    with open(OUT, "w") as f:
        f.write("/* kid_data.h — данные анимации Kid (frame_table + seqtbl +\n"
                " * offsets), извлечено toolchain/pop_extract_kid_data.py из\n"
                " * SDLPoP.  НЕ править вручную.  seqtbl адресуется базой\n"
                " * SEQTBL_BASE: kid_seqtbl[addr - SEQTBL_BASE]. */\n")
        f.write("#ifndef KID_DATA_H\n#define KID_DATA_H\n#include <stdint.h>\n\n")
        f.write("#define SEQTBL_BASE 0x%04Xu\n\n" % SEQTBL_BASE)

        # frame table и seqtbl В .h НЕ ПОПАДАЮТ — они уезжают в kid_data.bin
        # (EMM-страница): 3.5 КБ в _CODE окна W1/W2 нам дороже, чем один
        # gfx_w0_map за тик.  См. applications/PoP/docs/layout_plan_v2.md §5.1.
        f.write("/* {image, dx, dy, flags, sword}; chtab = sword>>6 (0=kid).\n"
                " * САМА таблица кадров и seqtbl лежат в kid_data.bin (EMM-\n"
                " * страница); здесь только тип и размеры. */\n")
        f.write("typedef struct { uint8_t image; int8_t dx, dy; "
                "uint8_t flags, sword; } kframe;\n")
        f.write("#define KID_NFRAMES %d\n" % len(frames))
        f.write("#define KID_SEQTBL_LEN %d\n\n" % len(seqtbl))

        # раскладка kid_data.bin (смещения ОТ НАЧАЛА ДАННЫХ страницы)
        f.write("/* Раскладка kid_data.bin: кадры (KID_NFRAMES*5), затем seqtbl. */\n")
        f.write("#define KID_BIN_FRAMES_OFF 0\n")
        f.write("#define KID_BIN_SEQTBL_OFF %d\n" % (len(frames) * 5))
        f.write("/* Таблица кадров СТРАЖА (frame_tbl_guard, seg006:372): индекс\n"
                " * frame + add_frame - 149 (add_frame=70 для кадров 102..106). */\n")
        f.write("#define KID_NGFRAMES %d\n" % len(gframes))
        f.write("#define KID_BIN_GFRAMES_OFF %d\n\n" % (len(frames) * 5 + len(seqtbl)))

        # seq offsets
        f.write("#define KID_NSEQ %d\n" % len(offs))
        f.write("static const uint16_t kid_seq_off[KID_NSEQ] = {\n")
        f.write(emit_c(offs, 8, "0x%04X") + "\n};\n\n")

        # sword_tbl — макро-инициализаторами, а не готовыми массивами: так
        # 159 байт таблицы лягут в ОДИН TU (pop_kid.c), а не в каждый, кто
        # включил заголовок.
        f.write("/* sword_tbl (seg006:2047): клинок отдельным спрайтом chtab_0.\n"
                " * Индекс — sword_frame (кадр.sword & 0x3F), id 255 = клинка нет.\n"
                " * Смещения x/y — ЭКРАННЫЕ (применяются после calc_screen_x_coord),\n"
                " * x — «вперёд» по направлению персонажа. */\n")
        f.write("#define KID_NSWORD %d\n" % len(swtbl))
        for i, (nm, fmt) in enumerate((("ID", "0x%02X"), ("DX", "%d"), ("DY", "%d"))):
            # dx/dy — знаковые: печатаем со знаком, иначе int8_t = overflow
            vals = [r[i] if i == 0 else (r[i] - 256 if r[i] > 127 else r[i])
                    for r in swtbl]
            f.write("#define KID_SWORD_%s_INIT { \\\n" % nm)
            f.write(emit_c(vals, 12, fmt).replace("\n", " \\\n") + " \\\n}\n")
        f.write("\n#endif\n")

    # kid_data.bin: кадры (по 5 байт) + seqtbl подряд
    os.makedirs(OUT_BIN_DIR, exist_ok=True)
    binpath = os.path.join(OUT_BIN_DIR, "kid_data.bin")
    with open(binpath, "wb") as b:
        for (img, dx, dy, fl, sw) in frames:
            b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
        b.write(bytes(seqtbl))
        for (img, dx, dy, fl, sw) in gframes:
            b.write(bytes((img & 0xFF, dx & 0xFF, dy & 0xFF, fl & 0xFF, sw & 0xFF)))
    print(f"kid_data.bin: {len(frames)*5 + len(seqtbl) + len(gframes)*5} Б (кадры Kid + seqtbl + кадры стража) -> {binpath}")

    print(f"kid_data.h: {len(frames)} кадров, seqtbl {len(seqtbl)} Б, "
          f"{len(offs)} seq-оффсетов -> {OUT}")


if __name__ == "__main__":
    main()
