#!/usr/bin/env bash
# mameDebug.sh - source-level breakpoint+step driver for a W65816 .bin
# under MAME.
#
# Workflow:
#   1. Build with `-g` and link with `--debug-out FOO.dwarf --map FOO.map`.
#   2. Run this script with the binary + a breakpoint (function name or
#      source line) + optional step commands.
#   3. Each hit prints `PC=0xNNNN FILE=foo.c LINE=N FUNC=bar` resolved
#      via pc2line.py.
#
# Usage:
#   scripts/mameDebug.sh <bin> <map> <dwarf> [--break FUNC] [--break FILE:LINE]
#                       [--steps N]
#
# Without --break, runs until the program halts (BRK or wild jump).
# --break FUNC sets a breakpoint at the function's entry; --break FILE:LINE
# resolves the line to a PC via pc2line.py and breaks there.
# --steps N single-steps N times after the first break, printing the
# source location at each step.

set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

BIN=""
MAP=""
DWARF=""
BREAKS=()
STEPS=0

while [ $# -gt 0 ]; do
    case "$1" in
        --break) BREAKS+=("$2"); shift 2;;
        --steps) STEPS="$2";    shift 2;;
        *)
            if   [ -z "$BIN" ];   then BIN="$1"
            elif [ -z "$MAP" ];   then MAP="$1"
            elif [ -z "$DWARF" ]; then DWARF="$1"
            else echo "unexpected arg: $1" >&2; exit 2
            fi
            shift
            ;;
    esac
done

if [ -z "$BIN" ] || [ -z "$MAP" ] || [ -z "$DWARF" ]; then
    echo "usage: $0 <bin> <map> <dwarf> [--break SYM_or_FILE:LINE]... [--steps N]" >&2
    exit 2
fi

# Resolve each --break argument to a PC.  FUNC -> map lookup; FILE:LINE ->
# pc2line.py --dump | grep.
resolveBreak() {
    local spec="$1"
    if [[ "$spec" == *:* ]]; then
        local file="${spec%:*}"
        local line="${spec##*:}"
        python3 "$SCRIPT_DIR/pc2line.py" --sidecar "$DWARF" --map "$MAP" --dump |
            awk -v f="$file" -v l="$line" '
                $2 == (f":" l) { print $1; exit }
            '
    else
        awk -v s="$spec" '$2 == s { print $1; exit }' "$MAP"
    fi
}

BP_PCS=()
for spec in "${BREAKS[@]+"${BREAKS[@]}"}"; do
    pc=$(resolveBreak "$spec")
    if [ -z "$pc" ]; then
        echo "mameDebug: can't resolve breakpoint '$spec'" >&2
        exit 1
    fi
    BP_PCS+=("$pc")
    echo "[mameDebug] break $spec -> $pc"
done

# Build a Lua script that MAME runs.  Halts at the first breakpoint,
# prints PC, single-steps STEPS times printing each PC, then exits.
LUA_FILE=$(mktemp --suffix=.lua)
trap 'rm -f "$LUA_FILE"' EXIT

cat > "$LUA_FILE" <<EOF
local cpu = manager.machine.devices[':maincpu']
local dbg = cpu.debug
local breaks = { $(IFS=,; echo "${BP_PCS[*]+"${BP_PCS[*]}"}") }
local steps  = ${STEPS}
for _, pc in ipairs(breaks) do
    dbg:bpset(pc)
end
emu.register_periodic(function()
    -- nothing; the debugger handles breaks via bpset.
end)
print("[mameDebug-lua] breakpoints set: " .. tostring(#breaks))
EOF

# Load + run the program via the same harness used by runInMame.sh, but
# with -debug enabled so the breakpoints fire.  Then capture trace output
# and pipe each emitted PC through pc2line.py.
echo "[mameDebug] launching MAME under bin=$BIN"
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout 60 mame apple2gs \
    -rompath "$ROOT/tools/mame/roms" \
    -ramsize 1m \
    -snapshot_directory /tmp \
    -window -seconds_to_run 1 \
    -autoboot_script "$LUA_FILE" \
    -video none -sound none -nothrottle 2>&1 |
    grep -E '^\[mameDebug-lua\]|^PC=' | tee /tmp/mameDebug.trace

# Resolve each PC line in the trace.
echo "[mameDebug] done; trace saved at /tmp/mameDebug.trace"
