#!/usr/bin/env bash
set -euo pipefail

CLASSIC_ROOT=${W3_CLASSIC_ROOT:-/opt/war3-runtime/classic}
CLASSIC_131_ROOT=${W3_CLASSIC_131_ROOT:-/opt/war3-runtime/classic-131}
PREFIX=${WINEPREFIX:-"$CLASSIC_ROOT/prefix"}
DISPLAY_NUMBER=${W3_D3D9_DISPLAY:-:11}
REPLAY_ROOT=${W3CS_REPLAY_ROOT:-/home/ubuntu/w3cs-lab/replays}
REPLAY_ID=${W3CS_REPLAY_ID:-default}
GAME_PROFILE=${W3CS_GAME_PROFILE:-auto}
PROFILE_ROOT=${W3CS_ENGINE_PROFILE_ROOT:-/home/ubuntu/w3cs-lab/engine-profiles}
REPLAY_DIRECTORY=Replays
REPLAY_WINDOWS='C:\users\ubuntu\Documents\Warcraft III\Replays\trace.w3g'
SEAT_STATE=${W3CS_SEAT_STATE:-/tmp/w3cs-seat-state.json}
WINE_BIN=/opt/wine-staging/bin/wine
WINESERVER_BIN=/opt/wine-staging/bin/wineserver
# The viewer selects the session's authoritative resolution. NOTE: only the
# 1.28.5 engine honors it (registry stamp); command-line -width/-height are
# NOT understood by the older engines - unknown arguments break their
# -loadfile parsing and strand the game at the main menu (verified live).
# Older lanes render at their desktop-derived size until a safe per-lane
# mechanism exists (candidate: resize the X window after launch).
SOURCE_WIDTH=${W3CS_SOURCE_WIDTH:-${W3_D3D9_SOURCE_WIDTH:-1024}}
SOURCE_HEIGHT=${W3CS_SOURCE_HEIGHT:-${W3_D3D9_SOURCE_HEIGHT:-768}}
if [[ ! "$SOURCE_WIDTH" =~ ^[0-9]{3,4}$ || ! "$SOURCE_HEIGHT" =~ ^[0-9]{3,4}$ ]]; then
  SOURCE_WIDTH=1024
  SOURCE_HEIGHT=768
fi
GAME_ARGS=(-window)

if [[ ! "$REPLAY_ID" =~ ^[A-Za-z0-9._-]{1,96}$ ]]; then
  echo "invalid replay id" >&2
  exit 2
fi
REPLAY_SOURCE="$REPLAY_ROOT/$REPLAY_ID.w3g"
REPLAY_METADATA="$REPLAY_ROOT/$REPLAY_ID.json"
if [[ ! -f "$REPLAY_SOURCE" ]]; then
  if [[ "$REPLAY_ID" =~ ^[0-9a-f]{40}$ &&
        -f "${W3CS_CATALOG_INDEX:-}" &&
        -n "${W3CS_CATALOG_BUCKET:-}" ]]; then
    python3 /home/ubuntu/w3cs-lab/materialize-catalog-replay.py \
      --index "$W3CS_CATALOG_INDEX" --source-sha1 "$REPLAY_ID" \
      --output-root "$REPLAY_ROOT" --bucket "$W3CS_CATALOG_BUCKET" \
      --region "${W3CS_CATALOG_REGION:-us-east-1}"
  else
    echo "replay not found: $REPLAY_ID" >&2
    exit 3
  fi
fi

if [[ "$GAME_PROFILE" == auto ]]; then
  [[ -f "$REPLAY_METADATA" ]] || {
    echo "automatic engine selection needs replay metadata: $REPLAY_ID" >&2
    exit 4
  }
  GAME_PROFILE=$(python3 - "$REPLAY_METADATA" <<'PY'
import json
import sys
with open(sys.argv[1], encoding="utf-8") as source:
    value = json.load(source).get("engineProfile")
if not isinstance(value, str):
    raise SystemExit("replay metadata has no engineProfile")
print(value)
PY
  )
fi

case "$GAME_PROFILE" in
  native-1285)
    BASE_GAME_DIR="$CLASSIC_ROOT/war3-1285/Warcraft III"
    GAME_EXE='Warcraft III.exe'
    ;;
  native-124ab|native-124cde)
    BASE_GAME_DIR="$CLASSIC_ROOT/war3rk/Warcraft III"
    GAME_EXE='war3.exe'
    ENGINE_OVERLAY="$PROFILE_ROOT/${GAME_PROFILE/native-/classic-}/files"
    ;;
  replaykit|replaykit-124ab)
    BASE_GAME_DIR="$CLASSIC_ROOT/war3rk/Warcraft III"
    GAME_EXE='ReplayKit.exe'
    ;;
  native-1140)
    BASE_GAME_DIR="$CLASSIC_ROOT/war3-1140/Warcraft III"
    GAME_EXE='war3.exe'
    ;;
  native-1311-d3d9)
    BASE_GAME_DIR="$CLASSIC_131_ROOT/source/Warcraft III"
    GAME_EXE='x86/Warcraft III.exe'
    PREFIX="${W3CS_PREFIX64:-$CLASSIC_131_ROOT/prefix64}"
    REPLAY_DIRECTORY=Replay
    REPLAY_WINDOWS='C:\users\ubuntu\Documents\Warcraft III\Replay\trace.w3g'
    WINE_BIN=/usr/bin/wine
    WINESERVER_BIN=/usr/bin/wineserver
    GAME_ARGS=(-graphicsapi Direct3D9 -windowmode windowed \
      -width "$SOURCE_WIDTH" -height "$SOURCE_HEIGHT")
    ;;
  *)
    echo "unsupported game profile: $GAME_PROFILE" >&2
    exit 4
    ;;
esac
if [[ "$GAME_PROFILE" != native-124ab && \
      "$GAME_PROFILE" != native-124cde && \
      ! -f "$BASE_GAME_DIR/$GAME_EXE" ]]; then
  echo "game profile is not installed: $GAME_PROFILE" >&2
  exit 5
fi

export WINEPREFIX="$PREFIX"
if [[ "$GAME_PROFILE" != native-1311-d3d9 ]]; then
  export WINEARCH=win32
else
  unset WINEARCH
fi
export WINEDEBUG=${WINEDEBUG:--all}
export PATH="$(dirname "$WINE_BIN"):$PATH"
export DISPLAY="$DISPLAY_NUMBER"
mkdir -p "$PREFIX/drive_c/users/ubuntu/Temp"

# Give each viewer an isolated game tree. Symlinks share the immutable MPQs,
# maps, and page cache, while patch DLLs and session maps stay private. This is
# fast enough for watch startup and safe when seats use different patches.
SESSION_ROOT=$(mktemp -d /tmp/w3cs-game-session.XXXXXX)
GAME_DIR="$SESSION_ROOT/game"

cleanup() {
  "$WINESERVER_BIN" -k 2>/dev/null || true
  rm -f -- "$SEAT_STATE"
  if [[ "${SESSION_ROOT:-}" == /tmp/w3cs-game-session.* ]]; then
    rm -rf -- "$SESSION_ROOT"
  fi
}
trap cleanup EXIT INT TERM

mkdir -p "$GAME_DIR"
cp -as "$BASE_GAME_DIR/." "$GAME_DIR/"
if [[ "$GAME_PROFILE" == native-1311-d3d9 ]]; then
  # CASC requires a writable per-process shared-memory file beside its
  # immutable data archives. The archives remain symlinked and page-shared.
  touch "$GAME_DIR/Data/data/shmem"
fi

"$WINESERVER_BIN" -k 2>/dev/null || true
if [[ -f "$REPLAY_METADATA" ]]; then
  python3 /home/ubuntu/w3cs-lab/stage-replay-session.py \
    --metadata "$REPLAY_METADATA" --replay "$REPLAY_SOURCE" \
    --prefix "$PREFIX" --game-directory "$GAME_DIR" \
    --replay-directory "$REPLAY_DIRECTORY"
else
  mkdir -p "$PREFIX/drive_c/users/ubuntu/Documents/Warcraft III/$REPLAY_DIRECTORY"
  cp "$REPLAY_SOURCE" \
    "$PREFIX/drive_c/users/ubuntu/Documents/Warcraft III/$REPLAY_DIRECTORY/trace.w3g"
fi
cd "$GAME_DIR"
if [[ "$GAME_PROFILE" == native-124ab || \
      "$GAME_PROFILE" == native-124cde ]]; then
  [[ -f "$ENGINE_OVERLAY/war3.exe" ]] || {
    echo "engine overlay is not installed: $GAME_PROFILE" >&2
    exit 6
  }
  # ReplayKit is a build-time materializer only. The watch path installs one
  # immutable engine overlay, then launches the selected replay directly.
  # Large MPQs and maps stay shared. No replay-specific CRIU image is needed.
  rm -f "$GAME_DIR/Game.dll" "$GAME_DIR/war3.exe" "$GAME_DIR/game.dll"
  for file in Storm.dll War3Patch.mpq war3.exe game.dll; do
    cp "$ENGINE_OVERLAY/$file" "$GAME_DIR/$file"
  done
fi

PROXY_DIRECTORY="$GAME_DIR/$(dirname "$GAME_EXE")"
rm -f "$PROXY_DIRECTORY/d3d9.dll"
cp /home/ubuntu/w3cs-lab/d3d9.dll "$PROXY_DIRECTORY/d3d9.dll"

if [[ "$GAME_PROFILE" == replaykit || \
      "$GAME_PROFILE" == replaykit-124ab ]]; then
  # This is the cold engine-profile builder path. ReplayKit reads lowercase
  # game.dll. Linux can retain an old lowercase file beside uppercase
  # Game.dll, so remove both names before restoring the 1.28f base.
  BASE_DIR="$CLASSIC_ROOT/war3-1285/Warcraft III"
  rm -f "$GAME_DIR/Game.dll" "$GAME_DIR/game.dll" "$GAME_DIR/war3.exe"
  rm -rf -- "$GAME_DIR/ReplayKitBackup"
  cp "$BASE_DIR/Warcraft III.exe" "$GAME_DIR/Warcraft III.exe"
  cp "$BASE_DIR/Game.dll" "$GAME_DIR/game.dll"
  cp "$BASE_DIR/Storm.dll" "$GAME_DIR/Storm.dll"
  cp "$BASE_DIR/War3Patch.mpq" "$GAME_DIR/War3Patch.mpq"
  # ReplayKit resolves Wine's stable C: replay path reliably. A Z: path below
  # the private symlink view can be rejected by its legacy path parser.
  REPLAYKIT_WINDOWS="$REPLAY_WINDOWS"
  replaykit_args=("$GAME_EXE" "$REPLAYKIT_WINDOWS" -names -colors -window)
  if [[ "$GAME_PROFILE" == replaykit ]]; then
    replaykit_args+=(-totalsilence)
  fi
  wine "${replaykit_args[@]}" &
  replaykit_pid=$!
  game_pid=
  for _ in $(seq 1 240); do
    if [[ "$GAME_PROFILE" == replaykit-124ab ]]; then
      while read -r window; do
        [[ -n "$window" ]] || continue
        xdotool key --window "$window" Return >/dev/null 2>&1 || true
      done < <(xdotool search --onlyvisible --name '^ReplayKit v1.28b$' \
        2>/dev/null || true)
    fi
    game_pid=$(pgrep -n -u "$(id -u)" -x war3.exe 2>/dev/null || true)
    [[ -n "$game_pid" ]] && break
    if ! kill -0 "$replaykit_pid" 2>/dev/null; then
      wait "$replaykit_pid" || true
      echo "ReplayKit exited before starting the game" >&2
      exit 7
    fi
    sleep .25
  done
  [[ -n "$game_pid" ]] || {
    echo "ReplayKit timed out before starting the game" >&2
    exit 8
  }
  while kill -0 "$game_pid" 2>/dev/null; do
    sleep .5
  done
  exit 0
fi
if [[ "$GAME_PROFILE" == native-1311-d3d9 ]]; then
  export WINEDLLOVERRIDES='d3d9=n,b;dxgi,d3d11=b'
fi
python3 - "$SEAT_STATE" "$GAME_PROFILE" "$PREFIX" "$GAME_DIR" \
  "$REPLAY_DIRECTORY" "$DISPLAY_NUMBER" "$REPLAY_ID" <<'PY'
import json
import os
from pathlib import Path
import sys

target = Path(sys.argv[1])
temporary = target.with_suffix(target.suffix + ".tmp")
temporary.write_text(json.dumps({
    "engineProfile": sys.argv[2],
    "winePrefix": sys.argv[3],
    "gameDirectory": sys.argv[4],
    "replayDirectory": sys.argv[5],
    "display": sys.argv[6],
    "replayId": sys.argv[7],
}, sort_keys=True), encoding="utf-8")
os.replace(temporary, target)
PY
# Also stamp the resolution into this profile's prefix immediately before
# launch: 1.28.5 reads it from the registry, and every lane then keeps the
# game, command stream, input mapping, and browser on one value end to end.
if [[ "$SOURCE_WIDTH" =~ ^[0-9]{3,4}$ && "$SOURCE_HEIGHT" =~ ^[0-9]{3,4}$ ]]; then
  "$WINE_BIN" reg add \
    'HKCU\Software\Blizzard Entertainment\Warcraft III\Video' \
    /v reswidth /t REG_DWORD /d "$SOURCE_WIDTH" /f >/dev/null 2>&1 || true
  "$WINE_BIN" reg add \
    'HKCU\Software\Blizzard Entertainment\Warcraft III\Video' \
    /v resheight /t REG_DWORD /d "$SOURCE_HEIGHT" /f >/dev/null 2>&1 || true
fi

# Old-engine resolution status (verified live): pre-1.28 engines size their
# window AND backbuffer from the desktop at device creation (1432x1046 on
# the 1920x1440 root), ignore reswidth/resheight in windowed mode, break on
# unknown -width/-height arguments, and keep their backbuffer through a
# window-manager resize. Forcing 1024x768 for them therefore needs a proxy-
# level override of the CreateDevice presentation parameters (or fullscreen
# plumbing); until then these lanes stream at their native windowed size.
"$WINE_BIN" "$GAME_EXE" "${GAME_ARGS[@]}" -loadfile "$REPLAY_WINDOWS" &
launcher_pid=$!
set +e
wait "$launcher_pid"
launcher_status=$?
set -e

# Some Classic binaries replace their initial Wine process during startup.
# Waiting only for the launcher makes the EXIT trap kill the relaunched game
# in the loading screen. Keep the seat wrapper alive while any WC3 process in
# this isolated process group remains. Allow a short gap between the launcher
# exit and the replacement process becoming visible.
session_pgid=$(ps -o pgid= -p $$ | tr -d ' ')
game_processes() {
  pgrep -g "$session_pgid" -f \
    'Warcraft III\.exe|(^|[/ ])war3\.exe([ $]|$)' 2>/dev/null || true
}
replacement=
for _ in $(seq 1 20); do
  replacement=$(game_processes)
  [[ -n "$replacement" ]] && break
  sleep .25
done
while [[ -n "$replacement" ]]; do
  sleep .5
  replacement=$(game_processes)
done
exit "$launcher_status"
