#!/usr/bin/env bash

set -o pipefail

fail() {
  printf 'FAIL: %s\n' "$1" >&2
  exit 1
}

SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)" \
  || fail "cannot resolve script directory"
cd "$SCRIPT_DIR" || fail "cannot enter script directory"

for injected in JAVA_TOOL_OPTIONS JDK_JAVA_OPTIONS _JAVA_OPTIONS; do
  if test -n "${!injected:-}"; then
    fail "$injected must be unset for this experiment"
  fi
done

test -n "${JDK17_HOME:-}" \
  || fail "set JDK17_HOME to a JDK 17 root"
JAVA="$JDK17_HOME/bin/java"
JAVAC="$JDK17_HOME/bin/javac"
JAVAP="$JDK17_HOME/bin/javap"
for tool in "$JAVA" "$JAVAC" "$JAVAP"; do
  test -x "$tool" || fail "missing executable: $tool"
done

JAVA_VERSION="$("$JAVA" -version 2>&1)" || fail "java -version failed"
JAVAC_VERSION="$("$JAVAC" -version 2>&1)" || fail "javac -version failed"
JAVAP_VERSION="$("$JAVAP" -version 2>&1)" || fail "javap -version failed"
grep -Eq 'version "17([.+-][^"]*)?"' <<<"$JAVA_VERSION" \
  || fail "java is not Java 17"
grep -Eq '^javac 17([.+-].*)?$' <<<"$JAVAC_VERSION" \
  || fail "javac is not Java 17"
grep -Eq '^17([.+-].*)?$' <<<"$JAVAP_VERSION" \
  || fail "javap is not Java 17"
printf '%s\n' "$JAVA_VERSION" "$JAVAC_VERSION" "javap $JAVAP_VERSION"

TMP_BASE="$(CDPATH= cd -- "${TMPDIR:-/tmp}" && pwd -P)" \
  || fail "cannot resolve temporary directory"
LAB_OUT="$(mktemp -d "$TMP_BASE/exception-semantics.XXXXXX")" \
  || fail "mktemp failed"
case "$LAB_OUT" in
  "$TMP_BASE"/exception-semantics.*) ;;
  *) fail "unsafe temporary path: $LAB_OUT" ;;
esac
cleanup() {
  if ! rm -rf -- "$LAB_OUT"; then
    printf 'FAIL: cannot clean temporary directory: %s\n' "$LAB_OUT" >&2
    trap - EXIT
    exit 1
  fi
}
trap cleanup EXIT

mkdir -p "$LAB_OUT/classes" || fail "cannot create class output"
"$JAVAC" --release 17 -Xlint:all -Werror \
  -d "$LAB_OUT/classes" ExceptionSemanticsDemo.java \
  || fail "demo compilation failed"

"$JAVAP" -c -v -p -s -classpath "$LAB_OUT/classes" \
  example.ExceptionSemanticsDemo >"$LAB_OUT/demo.javap" \
  || fail "javap failed"
awk '
  /^  private static void translatePreservingEvidence\(/ { capture = 1 }
  capture { print }
  capture && /^[[:space:]]*$/ { exit }
' "$LAB_OUT/demo.javap" >"$LAB_OUT/translate-method.block" \
  || fail "cannot extract translation method bytecode"
test -s "$LAB_OUT/translate-method.block" \
  || fail "translation method bytecode block is empty"
grep -Fq 'descriptor: (Lexample/ExceptionSemanticsDemo$FailingResource;)V' \
  "$LAB_OUT/translate-method.block" \
  || fail "translation method descriptor is missing"
grep -Fq 'Exception table:' "$LAB_OUT/translate-method.block" \
  || fail "translation method Exception table is missing"
grep -Fq 'java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V' \
  "$LAB_OUT/translate-method.block" \
  || fail "try-with-resources addSuppressed call is missing"

capture_success() {
  mode="$1"
  stderr_file="$LAB_OUT/$mode.err"
  output="$("$JAVA" -cp "$LAB_OUT/classes" \
    example.ExceptionSemanticsDemo "$mode" 2>"$stderr_file" \
    | tr -d '\r')" \
    || fail "$mode failed"
  test ! -s "$stderr_file" || fail "$mode wrote stderr"
  printf '%s' "$output"
}

PRESERVED_OUTPUT="$(capture_success preserved)" \
  || fail "cannot capture preserved output"
EXPECTED_PRESERVED="$(printf '%s\n' \
  'mode=preserved' \
  'cause=StorageFailure' \
  'suppressed=IOException' \
  'resource-closed=true')" \
  || fail "cannot build preserved expectation"
test "$PRESERVED_OUTPUT" = "$EXPECTED_PRESERVED" \
  || fail "unexpected preserved output"

RESTORED_OUTPUT="$(capture_success restored-interrupt)" \
  || fail "cannot capture restored-interrupt output"
EXPECTED_RESTORED="$(printf '%s\n' \
  'mode=restored-interrupt' \
  'reached-blocking-point=true' \
  'restored-interrupt=true' \
  'worker-exited=true')" \
  || fail "cannot build restored-interrupt expectation"
test "$RESTORED_OUTPUT" = "$EXPECTED_RESTORED" \
  || fail "unexpected restored-interrupt output"

assert_rejected() {
  mode="$1"
  expected_status="$2"
  shift 2
  "$JAVA" -cp "$LAB_OUT/classes" example.ExceptionSemanticsDemo "$mode" \
    >"$LAB_OUT/$mode.out" 2>"$LAB_OUT/$mode.err"
  actual_status=$?
  test "$actual_status" -eq "$expected_status" \
    || fail "$mode exit status is $actual_status, expected $expected_status"
  test ! -s "$LAB_OUT/$mode.out" || fail "$mode unexpectedly wrote stdout"
  actual="$(tr -d '\r' <"$LAB_OUT/$mode.err")" \
    || fail "cannot read $mode stderr"
  expected="$(printf '%s\n' "$@")" \
    || fail "cannot build $mode expectation"
  test "$actual" = "$expected" || fail "unexpected $mode stderr"
}

assert_rejected lost-cause 2 \
  'mode=lost-cause' \
  'cause=null' \
  'rejected=missing-cause'
assert_rejected swallowed-interrupt 3 \
  'mode=swallowed-interrupt' \
  'interrupt-after-catch=false' \
  'worker-exited=true' \
  'rejected=interrupt-lost'

printf '%s\n' \
  'bytecode=Exception-table+addSuppressed' \
  'preserved=cause+suppressed+closed' \
  'lost-cause=rejected(exit=2)' \
  'restored-interrupt=status-restored+worker-exited' \
  'swallowed-interrupt=rejected(exit=3)' \
  'PASS'
