#!/usr/bin/env bash
# Launch the built Apple IIgs examples.
#
#   scripts/run-iigs.sh              GSplus + Finder: boots GS/OS with
#                                    joey.2mg mounted; pick the demo by
#                                    double-clicking it in Finder.
#   scripts/run-iigs.sh staxi        MAME auto-launch: builds a
#                                    single-example run disk
#                                    (joey-run.2mg -- joey.2mg is left
#                                    alone so benches stay honest),
#                                    boots GS/OS at real speed, and
#                                    drives Finder via Lua to launch
#                                    the example. No clicking needed.
#                                    GS/OS itself takes ~50 s to boot;
#                                    that part is authentic.
#
# The MAME path needs the apple2gs ROM set (default ~/.mame/roms,
# override with MAME_ROMPATH). MAME_HEADLESS=1 runs it windowless with
# periodic PNG snapshots (batch verification).

set -euo pipefail

repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
bin_dir=$repo/build/iigs/bin
sys_disk=$repo/toolchains/emulators/support/gsos-system.po

if [[ $# -gt 1 ]]; then
    echo "usage: $0 [example-name]" >&2
    exit 2
fi

# ---------------------------------------------------------------- MAME
# Example given: auto-launching MAME path.
if [[ $# -eq 1 ]]; then
    target=${1^^}
    if [[ ! -f "$bin_dir/$target" ]]; then
        echo "$bin_dir/$target not built. Run 'make iigs' first." >&2
        if compgen -G "$bin_dir/*" > /dev/null; then
            echo "available examples in $bin_dir:" >&2
            find "$bin_dir" -maxdepth 1 -type f -printf '  %f\n' \
                | grep -vE '\.2mg$|\.txt$' >&2 || true
        fi
        exit 1
    fi
    if [[ ! -f $sys_disk ]]; then
        echo "missing: $sys_disk (run ./toolchains/install.sh)" >&2
        exit 1
    fi

    # ------------------------------------------------------------- AGI
    # AGI needs ~880KB (240KB interpreter + 644KB KQ3 resources), which
    # overflows the 800KB 3.5" floppy the generic path uses, and MAME's
    # IIgs 3.5 drive is 800K-GCR-only. So AGI runs off a CFFA2 CompactFlash
    # card instead: partition 1 (-hard1) is a bootable GS/OS, partition 2
    # (-hard2) is a 2MB ProDOS volume with AGI + the game data. The boot
    # scan hits slot 7 first, boots GS/OS from hard1, and GS/OS then mounts
    # the AGI volume; the Lua driver opens it and launches AGI. No BSS/BRAM
    # or startup-slot changes are needed with this two-partition topology.
    if [[ $target == AGI ]]; then
        game=${JOEY_AGI_GAME:-kq3}
        # make-agi-iigs-disk.sh needs the toolchain env (cadius path).
        if [[ -z "${LLVM816_ROOT:-}" ]]; then
            # shellcheck disable=SC1091
            source "$repo/toolchains/env.sh" >/dev/null 2>&1 || true
        fi

        # MAME loads the CFFA2 firmware as an a2cffa2 device ROM. Prefer the
        # copy staged in the repo (toolchains/install.sh territory) so the
        # run does not depend on the user's ~/.mame; fall back to the user
        # rompath. Legal: the firmware is freely redistributed by dreher.net
        # and the card is owned.
        staged_roms=$repo/toolchains/emulators/support/mame-roms
        user_roms="${MAME_ROMPATH:-$HOME/.mame/roms}"
        rompath="$user_roms"
        if [[ -f "$staged_roms/a2cffa2/cffa20eec02.bin" ]]; then
            rompath="$user_roms;$staged_roms"
        elif [[ ! -f "$user_roms/a2cffa2/cffa20eec02.bin" ]]; then
            cat >&2 <<EOF
missing CFFA2 firmware: a2cffa2/cffa20eec02.bin
  Needed to mount AGI's data disk (4096 bytes, SHA1
  080ff88f19de22328e162954ee2b51ee65f9d5cd). It ships in dreher.net's
  Run6_CDROM.zip as Firmware/V2.0/CFFA20EEC02.BIN. Drop it in either
  $staged_roms/a2cffa2/  or  $user_roms/a2cffa2/
EOF
            exit 1
        fi

        headless=${MAME_HEADLESS:-0}
        work=$(mktemp -d -t joeylib-run-agi.XXXXXX)
        mame_pid=""
        cleanup() {
            if [[ -n $mame_pid ]] && kill -0 "$mame_pid" 2>/dev/null; then
                kill "$mame_pid" 2>/dev/null || true
                sleep 1
                kill -9 "$mame_pid" 2>/dev/null || true
            fi
            rm -rf "$work"
        }
        trap cleanup EXIT INT TERM

        # hard1: writable bootable GS/OS copy. hard2: freshly built AGI vol.
        cp "$sys_disk" "$work/boot.po"
        echo "run-iigs: building AGI CFFA data disk ($game)..."
        "$repo/scripts/make-agi-iigs-disk.sh" "$work/agi.2mg" "$game"

        # Finder driver: at calibrated frame counts type A (select the AGI
        # volume -- boot volume is System.Disk, so 'A' is unambiguous), Cmd-O
        # to open it, the full name AGI to select the app, Cmd-O to launch.
        cat > "$work/launch.lua" <<LUA
local nat = manager.machine.natkeyboard
local function get_field(port, field_name)
    local p = manager.machine.ioport.ports[port]
    if p == nil then return nil end
    return p.fields[field_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
local headless = ($headless == 1)
local frames   = 0
local steps = {
    { 3000, function() nat:post("A") end },
    { 3120, function() press(key_cmd) end },
    { 3126, function() nat:post("o") end },
    { 3180, function() release(key_cmd) end },
    { 3540, function() nat:post("AGI") end },
    { 3660, function() press(key_cmd) end },
    { 3666, function() nat:post("o") end },
    { 3720, function() release(key_cmd) end },
}
if headless then
    table.insert(steps, { 4200, function() manager.machine.video:snapshot() end })
    table.insert(steps, { 6600, function() manager.machine.video:snapshot() end })
    table.insert(steps, { 9000, function() manager.machine.video:snapshot() end })
    table.insert(steps, { 9100, function() manager.machine:exit() end })
end
table.sort(steps, function(a, b) return a[1] < b[1] end)
local step_idx = 1
emu.register_periodic(function()
    frames = frames + 1
    while step_idx <= #steps and frames >= steps[step_idx][1] do
        steps[step_idx][2]()
        step_idx = step_idx + 1
    end
end)
LUA

        video_arg="-window"
        sound_arg=""
        throttle_arg=""
        if [[ "$headless" = "1" ]]; then
            video_arg="-video soft"
            sound_arg="-sound none"
            throttle_arg="-nothrottle"
            export QT_QPA_PLATFORM=offscreen
            export SDL_VIDEODRIVER=dummy
            export SDL_AUDIODRIVER=dummy
        else
            export SDL_MOUSE_RELATIVE_MODE_WARP=0
        fi

        cat <<EOF
MAME apple2gs: AGI ($game) on a CFFA2 disk. GS/OS boots from hard1
(~50 s) and mounts the AGI volume on hard2; the Lua driver opens it and
launches AGI. Quit MAME (or close its window) to end the session.
EOF

        cd "$work"
        # -ramsize 4M: AGI's ~240KB app plus GS/OS/Finder leaves the stock
        # 2MB Memory Manager pool too tight -- its first jlAlloc (the view
        # cache) returns NULL and the app exits. 4MB is the assumed target.
        mame apple2gs \
            -ramsize 4M \
            -rompath "$rompath" \
            -sl7 cffa2 -hard1 "$work/boot.po" -hard2 "$work/agi.2mg" \
            $video_arg $sound_arg $throttle_arg \
            -skip_gameinfo \
            -snapshot_directory "$work/snap" \
            -autoboot_script "$work/launch.lua" &
        mame_pid=$!
        wait "$mame_pid" || true
        mame_pid=""
        if [[ -d $work/snap ]]; then
            rm -rf /tmp/run-iigs-snap
            mv "$work/snap" /tmp/run-iigs-snap
            echo "run-iigs: snapshots -> /tmp/run-iigs-snap"
        fi
        exit 0
    fi

    # Dedicated run disk so joey.2mg (the bench/golden disk) is never
    # replaced -- a stale-swapped joey.2mg silently corrupts the next
    # UBER bench run. Rebuild when missing, holding a different
    # example, or older than the example binary.
    run_disk=$bin_dir/joey-run.2mg
    stamp=$run_disk.contents
    need_disk=0
    if [[ ! -f $run_disk ]]; then
        need_disk=1
    elif [[ "$(cat "$stamp" 2>/dev/null)" != "$target" ]]; then
        need_disk=1
    elif [[ "$bin_dir/$target" -nt $run_disk ]]; then
        need_disk=1
    fi
    if [[ $need_disk -eq 1 ]]; then
        if [[ -z "${LLVM816_ROOT:-}" ]]; then
            # make-iigs-disk.sh needs the toolchain env (cadius path).
            # shellcheck disable=SC1091
            source "$repo/toolchains/env.sh" >/dev/null 2>&1 || true
        fi
        echo "run-iigs: building single-example run disk ($target)..."
        JOEY_DISK_EXAMPLES="$target" "$repo/scripts/make-iigs-disk.sh" "$run_disk"
        printf '%s' "$target" > "$stamp"
    else
        echo "run-iigs: run disk is current ($target)"
    fi

    work=$(mktemp -d -t joeylib-run-iigs.XXXXXX)
    mame_pid=""
    cleanup() {
        # Kill the emulator if we are exiting first (Ctrl-C, timeout):
        # bash does not forward signals to a foreground child, and an
        # orphaned MAME keeps burning CPU with its work dir deleted
        # from under it.
        if [[ -n $mame_pid ]] && kill -0 "$mame_pid" 2>/dev/null; then
            kill "$mame_pid" 2>/dev/null || true
            sleep 1
            kill -9 "$mame_pid" 2>/dev/null || true
        fi
        # Preserve the post-run disk (high scores etc.) for inspection.
        if [[ -f $work/joey-run.2mg ]]; then
            cp "$work/joey-run.2mg" "$bin_dir/joey-run-after.2mg"
        fi
        rm -rf "$work"
    }
    trap cleanup EXIT INT TERM

    cp "$sys_disk" "$work/boot.po"
    cp "$run_disk" "$work/joey-run.2mg"

    # Slim Finder driver -- the keyboard-injection steps proven in
    # run-iigs-mame.sh's harness, without the debugger/crash plumbing:
    # at calibrated frame counts, type J (select the JOEYLIB volume),
    # Cmd-O (open it), the example's full name (select; full-name
    # typing disambiguates e.g. DRAW vs DATA), Cmd-O (launch). Frame
    # counts are emulated frames, so throttling doesn't shift them.
    headless=${MAME_HEADLESS:-0}
    cat > "$work/launch.lua" <<LUA
local nat = manager.machine.natkeyboard

local function get_field(port, field_name)
    local p = manager.machine.ioport.ports[port]
    if p == nil then return nil end
    return p.fields[field_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

local headless = ($headless == 1)
local frames   = 0
local steps = {
    { 3000, function() nat:post("J") end },
    { 3120, function() press(key_cmd) end },
    { 3126, function() nat:post("o") end },
    { 3180, function() release(key_cmd) end },
    { 3540, function() nat:post("$target") end },
    { 3660, function() press(key_cmd) end },
    { 3666, function() nat:post("o") end },
    { 3720, function() release(key_cmd) end },
}
if headless then
    -- Batch verification: snapshot right after the launch lands, mid
    -- asset load, and after the example settles on its first screen,
    -- then exit so the caller regains control. Interactive runs never
    -- exit on their own.
    table.insert(steps, { 3900, function() manager.machine.video:snapshot() end })
    table.insert(steps, { 6600, function() manager.machine.video:snapshot() end })
    table.insert(steps, { 9000, function() manager.machine.video:snapshot() end })
    table.insert(steps, { 9100, function() manager.machine:exit() end })
end
local step_idx = 1

emu.register_periodic(function()
    frames = frames + 1
    if headless and frames % 600 == 0 then
        -- Pacing heartbeat for batch runs (rescued with the snaps).
        local f = io.open("launch-progress.txt", "w")
        if f ~= nil then
            f:write(string.format("frames=%d\n", frames))
            f:close()
        end
    end
    while step_idx <= #steps and frames >= steps[step_idx][1] do
        steps[step_idx][2]()
        step_idx = step_idx + 1
    end
end)
LUA

    video_arg="-window"
    sound_arg=""
    throttle_arg=""
    if [[ "$headless" = "1" ]]; then
        # Verification mode: no window, no sound, and unthrottled so
        # the 12k-frame scripted run finishes in about a minute.
        video_arg="-video soft"
        sound_arg="-sound none"
        throttle_arg="-nothrottle"
        export QT_QPA_PLATFORM=offscreen
        export SDL_VIDEODRIVER=dummy
        export SDL_AUDIODRIVER=dummy
    fi
    rompath="${MAME_ROMPATH:-$HOME/.mame/roms}"

    cat <<EOF
MAME apple2gs: auto-launching $target off $run_disk.
GS/OS takes ~50 s to reach Finder; the Lua driver then opens JOEYLIB
and launches $target for you. Quit MAME (or close its window) to end
the session.
EOF

    cd "$work"
    # Throttled (real IIgs speed) -- this is the "play it" path,
    # unlike the verify/bench harnesses which run -nothrottle. Runs
    # as a tracked child so the cleanup trap can reap it on Ctrl-C.
    # -skip_gameinfo matters: without it MAME parks on the "driver is
    # not working correctly" info screen waiting for a keypress and
    # the machine never boots (the -debug harnesses never hit this).
    # -snapshot_directory is explicit because MAME otherwise resolves
    # it against the user's mame.ini (snapshots vanished into
    # ~/.mame/snap during bring-up).
    mame apple2gs \
        -rompath "$rompath" \
        -flop3 "$work/boot.po" \
        -flop4 "$work/joey-run.2mg" \
        $video_arg $sound_arg $throttle_arg \
        -skip_gameinfo \
        -snapshot_directory "$work/snap" \
        -autoboot_script "$work/launch.lua" &
    mame_pid=$!
    wait "$mame_pid" || true
    mame_pid=""
    if [[ -d $work/snap ]]; then
        rm -rf /tmp/run-iigs-snap
        mv "$work/snap" /tmp/run-iigs-snap
        echo "run-iigs: snapshots -> /tmp/run-iigs-snap"
    fi
    if [[ -f $work/launch-progress.txt ]]; then
        cp "$work/launch-progress.txt" /tmp/run-iigs-progress.txt
    fi
    exit 0
fi

# -------------------------------------------------------------- GSplus
# No argument: original GSplus + interactive Finder path.
gsplus=$repo/toolchains/emulators/gsplus/bin/gsplus
rom=$repo/toolchains/emulators/support/apple-iigs.rom
data_disk=$bin_dir/joey.2mg
null_c600=$repo/toolchains/emulators/support/iigs-null-c600.rom

for f in "$gsplus" "$rom" "$sys_disk" "$data_disk" "$null_c600"; do
    if [[ ! -f $f ]]; then
        echo "missing: $f" >&2
        if [[ $f == "$data_disk" ]]; then
            echo "run 'make iigs-disk' to build it." >&2
        else
            echo "run ./toolchains/install.sh (support files should have been staged)." >&2
        fi
        exit 1
    fi
done

# GSplus writes back to disk images during the session; stage writable
# copies so repeated runs do not mutate the originals.
work=$(mktemp -d -t joeylib-iigs.XXXXXX)

cleanup() {
    # Stash the post-run disk image for manual inspection.
    if [[ -f $work/joey.2mg ]]; then
        cp "$work/joey.2mg" "$repo/build/iigs/bin/joey-after-run.2mg"
        echo "run-iigs: saved post-run disk -> $repo/build/iigs/bin/joey-after-run.2mg" >&2
    fi
    rm -rf "$work"
}
trap cleanup EXIT

cp "$sys_disk"  "$work/boot.po"
cp "$data_disk" "$work/joey.2mg"

# Stage the null slot-6 PROM as c600.rom in the work dir. GSplus loads
# any file named c600.rom in its search path (cwd first) as the slot 6
# ROM card, overriding the built-in Disk II firmware. The staged image
# is 256 bytes with a leading RTS and no Pascal 1.1 firmware signature,
# so the IIgs boot ROM's slot scan skips slot 6 and the two empty 5.25
# drives never get probed. Source: toolchains/install.sh's
# install_support_iigs_null_c600.
cp "$null_c600" "$work/c600.rom"

cat <<EOF
GSplus launching GS/OS 6.0.4.
Once Finder is up:
  1. Open the JOEYLIB disk on the desktop.
  2. Double-click whichever demo you want to run.
(Tip: '$0 <example>' auto-launches via MAME instead.)
EOF

# GSplus auto-creates config.kegs in its cwd on first run. cd into
# the scratch dir so that file lands there and gets cleaned up with
# the rest of the temp state, rather than polluting the directory
# the user invoked the script from.
cd "$work"
# GSplus accepts any config.kegs key as a CLI override via "-<key>
# <value>". g_limit_speed=2 caps emulation at the IIgs's native
# 2.8 MHz; without this override GSplus boots at 8 MHz and our
# performance characteristics don't match real hardware. The other
# settings (0=unlimited, 1=1.024 MHz, 3=8 MHz) are reachable through
# the in-emulator speed-cycle key if needed.
#
# No exec: let the bash EXIT trap fire so the scratch dir (and the
# config.kegs GSplus auto-creates) gets cleaned up.
"$gsplus" -rom "$rom" \
    -s5d1 "$work/boot.po" \
    -s5d2 "$work/joey.2mg" \
    -g_limit_speed 2
