#!/usr/bin/env bash
# verify-atarist-input.sh - Headless typed-character acceptance gate for
# the Atari ST port. Autostarts KEYS.PRG in Hatari (EmuTOS, GEMDOS
# drive), injects the RetroNet definition-of-done string through
# Hatari's command FIFO as IKBD key events, and reads the example's
# verification scanline back out of ST RAM with the built-in debugger.
# The row is planar (4 interleaved bitplane words per 16 pixels); the
# decode below reassembles the nibble pixels, which must equal the
# typed ASCII exactly.
#
# Injection encoding (Hatari 2.4 `hatari-event keypress <key>` parses a
# single-character argument as an ASCII character and a multi-character
# argument as an ST scancode): digits go as ASCII, '.' as scancode 52,
# and ':' as shift make (42) + ';' scancode 39 + shift break -- which
# also exercises the ISR's shift handling through the TOS keymap.
#
# Usage:   scripts/verify-atarist-input.sh
# Requires: hatari, EmuTOS image, build/atarist/bin/KEYS.PRG.
set -uo pipefail

repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
bin_dir=$repo/build/atarist/bin
tos=$repo/toolchains/emulators/support/emutos-512k.img

target="192.168.1.10:6510"

[ -f "$bin_dir/KEYS.PRG" ] || { echo "verify-atarist-input: build KEYS.PRG first (make atarist)" >&2; exit 2; }
[ -f "$tos" ] || { echo "verify-atarist-input: missing EmuTOS at $tos" >&2; exit 2; }

work=$(mktemp -d -t joeylib-st-input.XXXXXX)
cleanup() {
    [ -n "${hatari_pid:-}" ] && kill "$hatari_pid" 2>/dev/null
    rm -rf "$work"
}
trap cleanup EXIT

cd "$work"
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy hatari \
    --tos "$tos" \
    --harddrive "$bin_dir" \
    --gemdos-drive C \
    --auto 'C:\KEYS.PRG' \
    --cmd-fifo "$work/cmd.fifo" \
    --fast-forward on \
    > "$work/hatari.log" 2>&1 &
hatari_pid=$!

send() {
    timeout 5 bash -c "echo \"\$1\" > \"$work/cmd.fifo\"" _ "$1" || return 1
    sleep 0.2
}

# Wait for the FIFO, then for KEYS' A5A5 sentinel in the verify row.
# The video base is read from the shifter regs each attempt (planar
# sentinel = plane words 5000 A000 5000 A000).
for i in $(seq 1 30); do
    [ -p "$work/cmd.fifo" ] && break
    sleep 1
done
[ -p "$work/cmd.fifo" ] || { echo "verify-atarist-input: FAIL - hatari never created the FIFO" >&2; exit 1; }

rowAddr=""
for i in $(seq 1 45); do
    sleep 1
    send 'hatari-debug m $ff8201 4' || continue
    base=$(grep -E '^00FF8201:' "$work/hatari.log" | tail -1 | awk '{print $2 $4}')
    [ -n "$base" ] || continue
    addr=$(( (16#$base << 8) + 198 * 160 ))
    send "hatari-debug m \$$(printf '%x' "$addr") 8" || continue
    row=$(grep -E "^$(printf '%08X' "$addr"):" "$work/hatari.log" | tail -1)
    if echo "$row" | grep -qi "50 00 a0 00 50 00 a0 00"; then
        rowAddr=$addr
        break
    fi
done
if [ -z "$rowAddr" ]; then
    echo "verify-atarist-input: FAIL - KEYS sentinel never appeared" >&2
    tail -10 "$work/hatari.log" >&2
    exit 1
fi

# Type the string: digits as ASCII characters, '.' as scancode 52,
# ':' as shift + scancode 39.
for c in 1 9 2; do send "hatari-event keypress $c"; done
send "hatari-event keypress 52"
for c in 1 6 8; do send "hatari-event keypress $c"; done
send "hatari-event keypress 52"
send "hatari-event keypress 1"
send "hatari-event keypress 52"
for c in 1 0; do send "hatari-event keypress $c"; done
send "hatari-event keydown 42"
send "hatari-event keypress 39"
send "hatari-event keyup 42"
for c in 6 5 1 0; do send "hatari-event keypress $c"; done

sleep 3
send "hatari-debug m \$$(printf '%x' "$rowAddr") 160"
sleep 2

python3 - "$work/hatari.log" "$rowAddr" "$target" <<'PY'
import re
import sys

log, rowAddr, target = sys.argv[1], int(sys.argv[2]), sys.argv[3]
data = open(log, "rb").read().decode(errors="replace")
lines = {}
# Keep only the LAST dump of each address (earlier sentinel probes also match).
for m in re.finditer(r"^(00[0-9A-F]{6}): ((?:[0-9a-f]{2} ){1,16})", data, re.M):
    lines[int(m.group(1), 16)] = m.group(2)
raw = b""
for off in range(0, 160, 16):
    if rowAddr + off not in lines:
        print(f"verify-atarist-input: FAIL - missing dump line at +{off}")
        sys.exit(1)
    raw += bytes.fromhex(lines[rowAddr + off].replace(" ", ""))

def group(words):
    p = [int.from_bytes(words[i * 2:i * 2 + 2], "big") for i in range(4)]
    return [((p[0] >> b) & 1) | (((p[1] >> b) & 1) << 1) |
            (((p[2] >> b) & 1) << 2) | (((p[3] >> b) & 1) << 3)
            for b in range(15, -1, -1)]

pix = []
for g in range(len(raw) // 8):
    pix += group(raw[g * 8:g * 8 + 8])

if pix[0:4] != [0xA, 0x5, 0xA, 0x5]:
    print(f"verify-atarist-input: FAIL - sentinel wrong: {pix[0:4]}")
    sys.exit(1)
count = (pix[4] << 4) | pix[5]
chars = bytes((pix[8 + 2 * i] << 4) | pix[8 + 2 * i + 1] for i in range(count))
print(f"VERIFY-ST-INPUT count={count} chars={chars!r}")
if chars.decode("ascii", errors="replace") == target:
    print(f'verify-atarist-input: PASS (typed "{target}", all {len(target)} chars arrived via jlInputGetChar)')
else:
    print(f'verify-atarist-input: FAIL - expected "{target}"')
    sys.exit(1)
PY
