#!/usr/bin/env bash
# Fast Smalltalk-on-SX test runner — pipes directly to sx_server.exe.
# Mirrors lib/haskell/test.sh.
#
# Usage:
#   bash lib/smalltalk/test.sh                     # run all tests
#   bash lib/smalltalk/test.sh -v                  # verbose
#   bash lib/smalltalk/test.sh tests/tokenize.sx   # run one file

set -uo pipefail
cd "$(git rev-parse --show-toplevel)"

SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
if [ ! -x "$SX_SERVER" ]; then
  MAIN_ROOT=$(git worktree list | head -1 | awk '{print $1}')
  if [ -x "$MAIN_ROOT/$SX_SERVER" ]; then
    SX_SERVER="$MAIN_ROOT/$SX_SERVER"
  else
    echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
    exit 1
  fi
fi

VERBOSE=""
FILES=()
for arg in "$@"; do
  case "$arg" in
    -v|--verbose) VERBOSE=1 ;;
    *) FILES+=("$arg") ;;
  esac
done

if [ ${#FILES[@]} -eq 0 ]; then
  # tokenize.sx must load first — it defines the st-test helpers reused by
  # subsequent test files. Sort enforces this lexicographically.
  mapfile -t FILES < <(find lib/smalltalk/tests -maxdepth 2 -name '*.sx' | sort)
fi

TOTAL_PASS=0
TOTAL_FAIL=0
FAILED_FILES=()

for FILE in "${FILES[@]}"; do
  [ -f "$FILE" ] || { echo "skip $FILE (not found)"; continue; }
  TMPFILE=$(mktemp)
  if [ "$(basename "$FILE")" = "tokenize.sx" ]; then
    cat > "$TMPFILE" <<EPOCHS
(epoch 1)
(load "lib/smalltalk/tokenizer.sx")
(epoch 2)
(load "$FILE")
(epoch 3)
(eval "(list st-test-pass st-test-fail)")
EPOCHS
  else
    cat > "$TMPFILE" <<EPOCHS
(epoch 1)
(load "lib/smalltalk/tokenizer.sx")
(epoch 2)
(load "lib/smalltalk/parser.sx")
(epoch 3)
(load "lib/guest/reflective/class-chain.sx")
(load "lib/smalltalk/runtime.sx")
(epoch 4)
(load "lib/guest/reflective/env.sx")
(load "lib/smalltalk/eval.sx")
(epoch 5)
(load "lib/smalltalk/sunit.sx")
(epoch 6)
(load "lib/smalltalk/tests/tokenize.sx")
(epoch 7)
(load "$FILE")
(epoch 8)
(eval "(list st-test-pass st-test-fail)")
EPOCHS
  fi

  OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>&1 || true)
  rm -f "$TMPFILE"

  # Final epoch's value: either (ok N (P F)) on one line or
  # (ok-len N M)\n(P F) where the value is on the following line.
  LINE=$(echo "$OUTPUT" | awk '/^\(ok-len [0-9]+ / {getline; print}' | tail -1)
  if [ -z "$LINE" ]; then
    LINE=$(echo "$OUTPUT" | grep -E '^\(ok [0-9]+ \([0-9]+ [0-9]+\)\)' | tail -1 \
            | sed -E 's/^\(ok [0-9]+ //; s/\)$//')
  fi
  if [ -z "$LINE" ]; then
    echo "X $FILE: could not extract summary"
    echo "$OUTPUT" | tail -30
    TOTAL_FAIL=$((TOTAL_FAIL + 1))
    FAILED_FILES+=("$FILE")
    continue
  fi
  P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
  F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
  TOTAL_PASS=$((TOTAL_PASS + P))
  TOTAL_FAIL=$((TOTAL_FAIL + F))
  if [ "$F" -gt 0 ]; then
    FAILED_FILES+=("$FILE")
    printf 'X %-40s %d/%d\n' "$FILE" "$P" "$((P+F))"
    TMPFILE2=$(mktemp)
    if [ "$(basename "$FILE")" = "tokenize.sx" ]; then
      cat > "$TMPFILE2" <<EPOCHS
(epoch 1)
(load "lib/smalltalk/tokenizer.sx")
(epoch 2)
(load "$FILE")
(epoch 3)
(eval "(map (fn (f) (get f :name)) st-test-fails)")
EPOCHS
    else
      cat > "$TMPFILE2" <<EPOCHS
(epoch 1)
(load "lib/smalltalk/tokenizer.sx")
(epoch 2)
(load "lib/smalltalk/parser.sx")
(epoch 3)
(load "lib/guest/reflective/class-chain.sx")
(load "lib/smalltalk/runtime.sx")
(epoch 4)
(load "lib/guest/reflective/env.sx")
(load "lib/smalltalk/eval.sx")
(epoch 5)
(load "lib/smalltalk/sunit.sx")
(epoch 6)
(load "lib/smalltalk/tests/tokenize.sx")
(epoch 7)
(load "$FILE")
(epoch 8)
(eval "(map (fn (f) (get f :name)) st-test-fails)")
EPOCHS
    fi
    FAILS=$(timeout 180 "$SX_SERVER" < "$TMPFILE2" 2>&1 | grep -E '^\(ok [0-9]+ \(' | tail -1 || true)
    rm -f "$TMPFILE2"
    echo "    $FAILS"
  elif [ "$VERBOSE" = "1" ]; then
    printf 'OK %-40s %d passed\n' "$FILE" "$P"
  fi
done

TOTAL=$((TOTAL_PASS + TOTAL_FAIL))
if [ $TOTAL_FAIL -eq 0 ]; then
  echo "OK $TOTAL_PASS/$TOTAL smalltalk-on-sx tests passed"
else
  echo "FAIL $TOTAL_PASS/$TOTAL passed, $TOTAL_FAIL failed in: ${FAILED_FILES[*]}"
fi

[ $TOTAL_FAIL -eq 0 ]
