#!/usr/bin/env python3
"""Find packed CNS filename groups embedded in Hwanse2.exe."""
from __future__ import annotations

import argparse
import re
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]


def find_cns_groups(exe: Path, max_gap: int) -> list[list[tuple[int, str]]]:
    data = exe.read_bytes()
    pattern = re.compile(rb"[a-z0-9_]{1,8}\.cns\x00")
    matches = [
        (match.start(), match.group()[:-1].decode("ascii"))
        for match in pattern.finditer(data)
    ]

    groups: list[list[tuple[int, str]]] = []
    current: list[tuple[int, str]] = []
    previous_end: int | None = None
    for offset, name in matches:
        if previous_end is None or offset - previous_end <= max_gap:
            current.append((offset, name))
        else:
            if len(current) >= 2:
                groups.append(current)
            current = [(offset, name)]
        previous_end = offset + len(name) + 1

    if len(current) >= 2:
        groups.append(current)
    return groups


def group_summary(group: list[tuple[int, str]]) -> str:
    tilesets = [name for _, name in group if re.fullmatch(r"map_[a-z][0-9]\.cns", name)]
    maps = [name for _, name in group if re.fullmatch(r"map[0-9]_[0-9]{2}[a-z]\.cns", name)]
    battles = [name for _, name in group if name.startswith("btl_")]
    sprites = [name for _, name in group if name.startswith(("cara_", "face_"))]
    parts = []
    if tilesets:
        parts.append("tilesets=" + ",".join(tilesets))
    if maps:
        parts.append("maps=" + ",".join(maps))
    if battles:
        parts.append("btl=" + ",".join(battles))
    if sprites:
        parts.append("sprites=" + ",".join(sprites[:8]))
    return "; ".join(parts)


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--exe", type=Path, default=ROOT / "Hwanse2.exe")
    parser.add_argument("--max-gap", type=int, default=16)
    parser.add_argument("--contains", help="only print groups containing this substring")
    args = parser.parse_args()

    groups = find_cns_groups(args.exe, args.max_gap)
    printed = 0
    for group in groups:
        names = [name for _, name in group]
        if args.contains and not any(args.contains in name for name in names):
            continue

        printed += 1
        print(f"{printed}. offset={group[0][0]:#x} count={len(group)}")
        summary = group_summary(group)
        if summary:
            print(f"   {summary}")
        for offset, name in group:
            print(f"   {offset:#x} {name}")

    if printed == 0:
        raise SystemExit("no matching CNS filename groups found")


if __name__ == "__main__":
    main()
