#!/usr/bin/env python3
"""Diagnostic script to trace z-string decoding in Zork 1.

This script loads Zork 1 and traces the z-string decoding pipeline:
1. Dumps all abbreviations from the abbreviation table
2. Monkey-patches ZStringFactory.get() to log every string decode
3. Runs the game with piped input to capture string decoding during gameplay
"""

import inspect
import sys
from pathlib import Path


def main():
    # Add src to path so we can import mudlib
    project_root = Path(__file__).parent.parent
    sys.path.insert(0, str(project_root / "src"))

    from mudlib.zmachine import ZMachine
    from mudlib.zmachine.trivialzui import create_zui
    from mudlib.zmachine.zstring import ZStringFactory

    print("=" * 80)
    print("Z-STRING DECODING DIAGNOSTIC FOR ZORK 1")
    print("=" * 80)
    print()

    story_path = project_root / "content" / "stories" / "zork1.z3"

    if not story_path.exists():
        print(f"ERROR: Story file not found at {story_path}")
        sys.exit(1)

    with open(story_path, "rb") as f:
        story_bytes = f.read()

    print(f"Loaded {len(story_bytes)} bytes from {story_path.name}")
    print()

    # Create ZUI and ZMachine
    ui = create_zui()
    zmachine = ZMachine(story_bytes, ui, debugmode=False)

    # Access the string factory
    string_factory = zmachine._stringfactory

    # PART 1: Dump all abbreviations
    print("=" * 80)
    print("ABBREVIATION TABLE DUMP")
    print("=" * 80)
    print()

    abbrevs = string_factory.zchr._abbrevs
    if not abbrevs:
        print("No abbreviations found (version 1 game or failed to load)")
    else:
        print(f"Total abbreviations: {len(abbrevs)}")
        print()

        for table in range(3):
            print(f"--- Table {table} ---")
            for index in range(32):
                key = (table, index)
                if key in abbrevs:
                    zscii_codes = abbrevs[key]
                    text = string_factory.zscii.get(zscii_codes)
                    # Show ZSCII codes (truncate if too long)
                    if len(zscii_codes) > 20:
                        zscii_display = str(zscii_codes[:20]) + "..."
                    else:
                        zscii_display = str(zscii_codes)
                    # Show text (truncate if too long)
                    if len(text) > 60:
                        text_display = repr(text[:60]) + "..."
                    else:
                        text_display = repr(text)
                    print(f"  [{table},{index:2d}] ZSCII={zscii_display}")
                    print(f"           Text={text_display}")
            print()

    # PART 2: Monkey-patch ZStringFactory.get() to trace all string decodes
    print("=" * 80)
    print("STRING DECODING TRACE")
    print("=" * 80)
    print()
    print("Format: [opcode args] -> addr")
    print("        zchars -> zscii -> text")
    print()

    def traced_get(self, addr):
        # Get caller information using inspect
        stack = inspect.stack()
        caller_name = "unknown"
        caller_info = ""

        # Walk up the stack to find the opcode function
        for frame_info in stack[1:]:  # Skip our own frame
            func_name = frame_info.function
            if func_name.startswith("op_"):
                caller_name = func_name
                # Try to extract arguments from the frame
                frame_locals = frame_info.frame.f_locals
                # For op_print_addr, show the addr argument
                if func_name == "op_print_addr" and "addr" in frame_locals:
                    caller_info = f" addr={frame_locals['addr']:#06x}"
                # For op_print_paddr, show packed address and conversion
                elif func_name == "op_print_paddr" and "string_paddr" in frame_locals:
                    paddr = frame_locals["string_paddr"]
                    # Show both the packed address and what it converts to
                    # For z3, byte_addr = paddr * 2
                    byte_addr = paddr * 2
                    caller_info = f" paddr={paddr:#06x} (byte={byte_addr:#06x})"
                # For op_print_obj, show the object number
                elif func_name == "op_print_obj" and "obj" in frame_locals:
                    caller_info = f" obj={frame_locals['obj']}"
                # For op_print, note it's inline (z-string follows opcode)
                elif func_name == "op_print":
                    caller_info = " (inline z-string)"
                break

        # Get z-chars from ZStringTranslator
        zchars = self.zstr.get(addr)

        # Convert z-chars to ZSCII codes
        zscii_codes = self.zchr.get(zchars)

        # Convert ZSCII to Unicode text
        text = self.zscii.get(zscii_codes)

        # Log to stderr so it doesn't interfere with game output
        # Truncate long lists/strings for readability
        if len(zchars) > 20:
            zchars_display = str(zchars[:20]) + f"... (len={len(zchars)})"
        else:
            zchars_display = str(zchars)

        if len(zscii_codes) > 20:
            zscii_display = str(zscii_codes[:20]) + f"... (len={len(zscii_codes)})"
        else:
            zscii_display = str(zscii_codes)

        text_display = repr(text[:80]) + "..." if len(text) > 80 else repr(text)

        print(f"[{caller_name}{caller_info}] -> {addr:#06x}", file=sys.stderr)
        print(f"         zchars={zchars_display}", file=sys.stderr)
        print(f"         zscii={zscii_display}", file=sys.stderr)
        print(f"         text={text_display}", file=sys.stderr)
        print(file=sys.stderr)

        return text

    # Apply the monkey patch
    ZStringFactory.get = traced_get  # type: ignore[assignment]

    # PART 3: Run the game with piped input
    print("=" * 80)
    print("GAME OUTPUT (running interpreter with tracing enabled)")
    print("=" * 80)
    print()
    print(
        "Note: String decode traces are written to stderr and "
        "interleaved with game output."
    )
    print("To separate them, run: python3 scripts/debug_zstrings.py 2>trace.log")
    print()
    print(
        "To see just the trace: "
        "python3 scripts/debug_zstrings.py 2>&1 >/dev/null | grep zchars"
    )
    print()
    print("Press Ctrl+C to stop the game.")
    print()

    try:
        zmachine.run()
    except KeyboardInterrupt:
        print("\n[Interrupted by user]")
    except Exception as e:
        print(f"\n[Game ended: {type(e).__name__}: {e}]")

    print()
    print("=" * 80)
    print("DIAGNOSTIC COMPLETE")
    print("=" * 80)


if __name__ == "__main__":
    main()
