#!/usr/bin/env python3
"""Summarize original keyboard action tables from Hwanse2.exe."""
from __future__ import annotations

import argparse
import json
import struct
from pathlib import Path

from probe_exe_scene_tables import read_sections, va_to_offset


TABLES = [
    ("primary", 0x00526D58),
    ("alternate", 0x00526E58),
]
GROUPS = 16
SLOTS = 8

DIK_NAMES = {
    0x01: "ESCAPE",
    0x0E: "BACK",
    0x1C: "RETURN",
    0x2C: "Z",
    0x2D: "X",
    0x2E: "C",
    0x39: "SPACE",
    0x48: "NUMPAD_UP",
    0x4B: "NUMPAD_LEFT",
    0x4D: "NUMPAD_RIGHT",
    0x50: "NUMPAD_DOWN",
    0x52: "NUMPAD_INSERT",
    0x9C: "NUMPAD_ENTER",
    0xC8: "UP",
    0xC9: "PRIOR",
    0xCB: "LEFT",
    0xCD: "RIGHT",
    0xD0: "DOWN",
    0xD1: "NEXT",
}

ACTION_HINTS = {
    0: "left",
    1: "right",
    2: "up",
    3: "down",
}


def key_entry(value: int) -> dict:
    entry = {"offset": value, "hex": f"0x{value:04x}"}
    if value < 0x100:
        entry["kind"] = "dik"
        entry["name"] = DIK_NAMES.get(value, f"DIK_0x{value:02x}")
    else:
        entry["kind"] = "buffer-offset"
        entry["name"] = f"state+0x{value:03x}"
    return entry


def read_table(exe: bytes, sections: list[dict], name: str, va: int) -> dict:
    offset = va_to_offset(sections, va)
    if offset is None:
        raise ValueError(f"VA 0x{va:08x} is outside known sections")

    actions = []
    for action in range(GROUPS):
        slots = []
        base = offset + action * SLOTS * 2
        for slot in range(SLOTS):
            value = struct.unpack_from("<H", exe, base + slot * 2)[0]
            if value == 0:
                break
            slots.append(key_entry(value))
        actions.append(
            {
                "index": action,
                "bit": 1 << action,
                "bitHex": f"0x{1 << action:04x}",
                "hint": ACTION_HINTS.get(action),
                "keys": slots,
            }
        )

    return {
        "name": name,
        "va": va,
        "vaHex": f"0x{va:08x}",
        "groups": GROUPS,
        "slotsPerGroup": SLOTS,
        "actions": actions,
    }


def build_summary(exe_path: Path) -> dict:
    exe = exe_path.read_bytes()
    sections = read_sections(exe)
    return {
        "exe": str(exe_path),
        "keyboardStateVa": 0x0055B868,
        "keyboardStateVaHex": "0x0055b868",
        "tables": [
            read_table(exe, sections, name, va)
            for name, va in TABLES
        ],
        "notes": [
            "Each table has 16 action groups; the original routine ORs bit N when any nonzero key entry in group N is pressed.",
            "Values below 0x100 are DirectInput keyboard DIK offsets into the 256-byte state buffer.",
            "Values at or above 0x100 are retained as raw offsets into the same combined input state area until their producer is fully named.",
        ],
    }


def markdown(summary: dict) -> str:
    lines = [
        "# Original Input Keymap",
        "",
        f"Executable: `{summary['exe']}`",
        f"Keyboard state buffer: `{summary['keyboardStateVaHex']}`",
        "",
        "The action packer at `0x0042f6d2` scans one of these tables. Each row is one",
        "action bit; if any listed key/state offset is pressed, that bit is ORed into",
        "the returned 16-bit mask.",
        "",
    ]
    for table in summary["tables"]:
        lines.extend(
            [
                f"## {table['name'].title()} Table",
                "",
                f"Table VA: `{table['vaHex']}`",
                "",
                "| action | bit | hint | keys |",
                "| ---: | --- | --- | --- |",
            ]
        )
        for action in table["actions"]:
            keys = ", ".join(
                f"`{key['name']}` ({key['hex']})"
                for key in action["keys"]
            ) or "-"
            lines.append(
                f"| {action['index']} | `{action['bitHex']}` | "
                f"{action.get('hint') or '-'} | {keys} |"
            )
        lines.append("")
    lines.extend(["## Notes", ""])
    for note in summary["notes"]:
        lines.append(f"- {note}")
    return "\n".join(lines).rstrip() + "\n"


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--exe", type=Path, default=Path("Hwanse2.exe"))
    parser.add_argument("--json-out", type=Path, default=Path("out/input_keymap.json"))
    parser.add_argument("--out", type=Path, default=Path("out/input_keymap.md"))
    args = parser.parse_args()

    summary = build_summary(args.exe)
    args.json_out.parent.mkdir(parents=True, exist_ok=True)
    args.json_out.write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    args.out.write_text(markdown(summary), encoding="utf-8")
    print(f"wrote original input keymap -> {args.out}")


if __name__ == "__main__":
    main()
