#!/usr/bin/env bash
# runViaFinder.sh — boot real GS/OS 6.0.2 in MAME, drive Finder via
# Lua keyboard automation to launch a user OMF, sample memory at
# specific frames to verify the program executed.
#
# Usage: runViaFinder.sh <omf-file> [--data /VOL/PATH/NAME=local_file]...
#                                   --check <addr>=<value>...
# The OMF file is injected as /DATA/HELLO on a separate 800K data
# disk; Lua drives Finder to open the Data volume and launch HELLO.
# Each --data option also injects an arbitrary file (raw bytes) onto
# the disk at the requested ProDOS path — used for stdio smoke tests
# that need a known file present at runtime (`tmpfile`, `posixfile`
# GS/OS path, `cxxstdlib::filesystem`).
#
# /VOL is one of /DATA (the injected data disk, default) or /SYS (the
# boot disk).  Sub-directories are auto-created via cadius CREATEFOLDER.
# The on-disk basename is the trailing component of the path; the file
# is dropped as a ProDOS type=$06 (BIN) so GS/OS treats it as a plain
# readable file via gsosOpen.  Pass multiple `--data` options to inject
# more than one file.
#
# Memory checks happen at frame 5400 (~90s emulated, well after the
# launch path completes) and exit 0 / 1 depending on whether each
# requested address holds the requested value.
#
# Requires:
#   - tools/gsos/sys602.po          (GS/OS 6.0.2 boot disk)
#   - /tmp/cadius/cadius            (forked-file-aware ProDOS tool)
#   - mame apple2gs in PATH

set -euo pipefail

OMF="$1"
shift
[ -f "$OMF" ] || { echo "missing: $OMF" >&2; exit 2; }

# Collect optional --data injections before --check.
DATA_INJECTS=()
while [ $# -gt 0 ] && [ "$1" = "--data" ]; do
    [ $# -ge 2 ] || { echo "usage: $0 <omf> [--data /VOL/PATH/NAME=path]... --check <addr>=<val>..." >&2; exit 2; }
    DATA_INJECTS+=("$2")
    shift 2
done
[ "${1:-}" = "--check" ] || { echo "usage: $0 <omf> [--data /VOL/PATH/NAME=path]... --check <addr>=<val>..." >&2; exit 2; }
shift

PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CADIUS=${CADIUS:-$PROJECT_ROOT/tools/cadius/cadius}
SYSDISK=${SYSDISK:-$PROJECT_ROOT/tools/gsos/sys602.po}

[ -x "$CADIUS" ]   || { echo "cadius not found at $CADIUS" >&2; exit 2; }
[ -f "$SYSDISK" ]  || { echo "sysdisk not found at $SYSDISK" >&2; exit 2; }

WORK=$(mktemp -d -t finderlaunch.XXXXXX)
trap 'rm -rf "$WORK"' EXIT

cp "$SYSDISK" "$WORK/disk.po"
# Create a separate 800K data disk and put HELLO on it. Keeps the
# boot disk untouched (and avoids the "20K free" limit on sys602.po
# that fails for OMFs > ~15K).
"$CADIUS" CREATEVOLUME "$WORK/data.po" DATA 800KB >/dev/null
cp "$OMF" "$WORK/HELLO#B30000"
"$CADIUS" ADDFILE "$WORK/data.po" /DATA "$WORK/HELLO#B30000" >/dev/null

# Inject extra data files.  Path syntax: /VOL/[sub/dirs/]NAME=local_file.
# Each gets type=$06 (BIN, generic data) so GS/OS treats it as a
# plain file readable via gsosOpen.  Sub-directories are CREATEFOLDER'd
# as needed (cadius is idempotent — a CREATEFOLDER on an existing path
# is a no-op).
for inj in "${DATA_INJECTS[@]}"; do
    targetPath="${inj%=*}"
    srcPath="${inj#*=}"
    [ -f "$srcPath" ] || { echo "missing data injection source: $srcPath" >&2; exit 2; }
    # Map the user-facing volume prefix (/SYS or /DATA) to (a) the .po
    # file cadius mutates and (b) the volume name as known to the disk
    # image itself (which differs — sys602.po is `/System.Disk`).
    case "$targetPath" in
        /SYS/*)
            targetDisk="$WORK/disk.po"
            volPrefix="/System.Disk"
            relPath="${targetPath#/SYS/}";;
        /DATA/*)
            targetDisk="$WORK/data.po"
            volPrefix="/DATA"
            relPath="${targetPath#/DATA/}";;
        *)
            echo "--data path must start with /SYS/ or /DATA/: $targetPath" >&2
            exit 2;;
    esac
    inVolName="${relPath##*/}"            # trailing component = filename
    subDirs="${relPath%"$inVolName"}"     # leading dirs (with trailing /)
    subDirs="${subDirs%/}"                # strip trailing /
    # Walk sub-dirs and CREATEFOLDER each one progressively.  cadius
    # is idempotent on CREATEFOLDER for an already-existing path, so
    # callers can re-inject without manually pruning.
    if [ -n "$subDirs" ]; then
        accum="$volPrefix"
        IFS='/' read -r -a dirParts <<<"$subDirs"
        for part in "${dirParts[@]}"; do
            accum="$accum/$part"
            "$CADIUS" CREATEFOLDER "$targetDisk" "$accum" >/dev/null 2>&1 || true
        done
        parentDir="$volPrefix/$subDirs"
    else
        parentDir="$volPrefix"
    fi
    cp "$srcPath" "$WORK/${inVolName}#060000"
    "$CADIUS" ADDFILE "$targetDisk" "$parentDir" "$WORK/${inVolName}#060000" >/dev/null
done

LUA_CHECKS=""
EXPECTS=()
for pair in "$@"; do
    [ "$pair" = "--check" ] && continue
    addr="${pair%=*}"; val="${pair#*=}"
    EXPECTS+=("$pair")
    LUA_CHECKS="$LUA_CHECKS print(string.format('MAME-READ %s=%02x', '$addr', mem:read_u8($addr)))"$'\n'
done

cat > "$WORK/finder.lua" <<LUA
-- Boot Finder, navigate to HELLO icon, launch via Cmd-O.
local cpu = manager.machine.devices[":maincpu"]
local mem = cpu.spaces["program"]
local nat = manager.machine.natkeyboard
local frame = 0
local idx = 1

local function get_field(port, name)
    local p = manager.machine.ioport.ports[port]
    if p == nil then return nil end
    return p.fields[name]
end
local key_cmd = get_field(":macadb:KEY3", "Command / Open Apple")
local function press(f)   if f then f:set_value(1) end end
local function release(f) if f then f:set_value(0) end end

-- Keystroke timeline: open DATA volume (the second disk), then launch HELLO.
local steps = {
    {3300, function() nat:post("D") end},     -- select Data
    {3540, function() press(key_cmd) end},
    {3546, function() nat:post("o") end},     -- Cmd-O opens volume
    {3600, function() release(key_cmd) end},
    {4200, function() nat:post("H") end},     -- select HELLO
    {4500, function() press(key_cmd) end},
    {4506, function() nat:post("o") end},     -- Cmd-O launches
    {4560, function() release(key_cmd) end},
    {6000, function()
$LUA_CHECKS
        manager.machine:exit()
    end},
}
emu.register_frame_done(function()
    frame = frame + 1
    while idx <= #steps and frame >= steps[idx][1] do
        steps[idx][2]()
        idx = idx + 1
    end
end)
LUA

OUT=$(SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout 150 mame apple2gs -rompath "$PROJECT_ROOT/tools/mame/roms" \
    -video none -nothrottle -sound none \
    -seconds_to_run 130 -flop3 "$WORK/disk.po" -flop4 "$WORK/data.po" \
    -autoboot_script "$WORK/finder.lua" </dev/null 2>&1)

# Verify each expected value.
fail=0
for pair in "${EXPECTS[@]}"; do
    addr="${pair%=*}"; want="${pair#*=}"
    line=$(echo "$OUT" | grep "MAME-READ $addr=" | tail -1)
    got=$(echo "$line" | sed -E 's/.*=([0-9a-f]+).*/\1/')
    # Compare numerically (handles case differences and 0x prefix variants).
    gotN=$(printf '%d' "0x$got" 2>/dev/null || echo -1)
    wantN=$(printf '%d' "$want" 2>/dev/null || echo -2)
    if [ "$gotN" = "$wantN" ]; then
        echo "  $addr = 0x$got (want $want) ✓"
    else
        echo "  $addr = 0x$got (want $want) ✗"
        fail=1
    fi
done
exit $fail
