#!/usr/bin/env bash
# Yanis Online launcher for Linux: game/launcher/yanis in the package, run by the yanis-online
# stub and the menu entries (install.sh). It lives in the game folder, so patches keep it current.
#
#   yanis-online            the Yanis Online window (launcher/yanis-launcher.py: updates, then Start,
#                           Setup, Report, Exit) when there is a desktop and Tk; otherwise `play`
#   yanis-online play       update the client, then play, without the window
#   yanis-online setup      the game's settings (resolution, full screen, sound): OpenSetup
#   yanis-online report     report a problem
#   yanis-online winecfg    Wine's settings for the game's own Wine
# Steps the window runs one by one: prepare, base, update, run.
#
# Package layout (tools/client/build_linux.sh):
#   game/    the client (patched in place by launcher/yanis-update.py)
#   wine/    Wine (Kron4ek's portable WoW64 build: 32-bit Windows programs, no 32-bit host libraries)
#   prefix/  the game's Wine prefix, made at first start      logs/  wine.log, update.log, reports
# Graphics: the renderer in settings.conf (see renderer() below) next to the exe.
set -uo pipefail

ROOT=$(cd "$(dirname "$(readlink -f "$0")")/../.." && pwd)
GAME="$ROOT/game"
LOGS="$ROOT/logs"
WINE="${YANIS_WINE:-$ROOT/wine/bin/wine}"
export WINEPREFIX="$ROOT/prefix"
export WINEDEBUG="${WINEDEBUG:--all}"
export WINEDLLOVERRIDES="ddraw=n,b;winemenubuilder.exe=d;mscoree=d;mshtml=d"
mkdir -p "$LOGS"

say() {   # a desktop notification when there is a desktop, the terminal otherwise
  if command -v notify-send >/dev/null && [ ! -t 1 ] && [ -z "${YANIS_WINDOW:-}" ]; then notify-send -a "Yanis Online" "Yanis Online" "$*"; fi
  echo "$*"
}
ask() {   # ask "question" -> 0 for yes
  if command -v zenity >/dev/null; then zenity --question --title="Yanis Online" --width=420 --text="$1" 2>/dev/null
  elif command -v kdialog >/dev/null; then kdialog --title "Yanis Online" --yesno "$1"
  elif [ -t 0 ]; then read -r -p "$1 [y/N] " a; [ "$a" = y ] || [ "$a" = Y ]
  else return 1; fi
}
fail() {
  if command -v zenity >/dev/null; then zenity --error --title="Yanis Online" --width=420 --text="$1" 2>/dev/null
  elif command -v kdialog >/dev/null; then kdialog --title "Yanis Online" --error "$1"; fi
  echo "yanis-online: $1" >&2; exit 1
}

[ -x "$WINE" ] || fail "Wine is missing from $ROOT/wine. Download Yanis Online again."
[ -f "$GAME/Yanis.exe" ] || fail "The game is missing from $GAME. Download Yanis Online again."

# First start: make the prefix, the client's settings (the registry keys the 2010 client needs
# to open a window at all) and no crash dialog (Wine writes the crash dump to the log instead).
prepare() {
  [ -f "$WINEPREFIX/.yanis-ready" ] && return 0
  say "Preparing Yanis Online for its first start (about half a minute) ..."
  "$WINE" wineboot -i >>"$LOGS/setup.log" 2>&1 || fail "Wine could not set up the game (see $LOGS/setup.log)."
  "$WINE" regedit /S "Z:$(printf %s "$GAME/yanis-defaults.reg" | tr / '\\')" >>"$LOGS/setup.log" 2>&1   # Z: is /
  "$WINE" reg add 'HKCU\Software\Wine\WineDbg' /v ShowCrashDialog /t REG_DWORD /d 0 /f >>"$LOGS/setup.log" 2>&1
  "$ROOT/wine/bin/wineserver" -w
  touch "$WINEPREFIX/.yanis-ready"
}

# The base data (data.grf, rdata.grf, the music: 1.8 GB) comes once, on the first start, from
# yanis.json's mirrors; a progress window shows it when zenity is there.
base() {
  command -v python3 >/dev/null || fail "Yanis Online needs python3 to download the game data. Install it from your distribution's packages."
  local py=(python3 "$GAME/launcher/yanis-update.py" base "$GAME") rc
  if command -v zenity >/dev/null && [ ! -t 1 ] && [ -z "${YANIS_WINDOW:-}" ]; then
    "${py[@]}" 2>&1 | tee -a "$LOGS/update.log" | sed -u 's/^/# /' \
      | zenity --progress --pulsate --auto-close --no-cancel --title="Yanis Online" --width=460 \
               --text="Checking the game data ..." 2>/dev/null
    rc=${PIPESTATUS[0]}
  else
    # No zenity (KDE without it): a notification, so a long first download is not silent.
    if [ ! -t 1 ] && [ -z "${YANIS_WINDOW:-}" ] && command -v notify-send >/dev/null && ! python3 - "$GAME" <<'PY'
import json, sys
from pathlib import Path
g = Path(sys.argv[1]); b = json.loads((g / "yanis.json").read_text()).get("base", {})
sys.exit(any(not (g / f["name"]).is_file() or (g / f["name"]).stat().st_size != f["size"] for f in b.get("files", [])))
PY
    then
      notify-send -a "Yanis Online" "Yanis Online" "Downloading the game data (1.8 GB, once). The game starts when it is done."
    fi
    "${py[@]}" 2>&1 | tee -a "$LOGS/update.log"; rc=${PIPESTATUS[0]}
  fi
  [ "$rc" -eq 0 ] || fail "The game data could not be downloaded (see $LOGS/update.log). Check that this PC is online and start again: it continues where it stopped."
}

update() {
  if ! command -v python3 >/dev/null; then
    echo "python3 is not installed: skipping updates." >>"$LOGS/update.log"; return 0
  fi
  python3 "$GAME/launcher/yanis-update.py" patch "$GAME" >>"$LOGS/update.log" 2>&1
  case $? in
    0) ;;
    2) say "The update server could not be reached; starting without checking for updates." ;;
    *) ask "The update did not install (see $LOGS/update.log). Start the game anyway?" || exit 1 ;;
  esac
}

# The renderer next to the exe, from settings.conf (RENDERER=, set in the Yanis Online window):
#   d7vk      D7VK, DirectX 7 on Vulkan (default; Wine's own ddraw draws white with this client)
#   dgvoodoo  dgVoodoo, DirectX 7 on D3D11 (through Wine's D3D11), resolutions up to 4K
# YANIS_RENDERER overrides it for one start. Wine looks DLL names up case-insensitively, so both
# spellings of ddraw.dll go before the chosen one is copied in.
renderer_name() {
  local r=${YANIS_RENDERER:-}
  [ -n "$r" ] || r=$(sed -n 's/^RENDERER=//p' "$ROOT/settings.conf" 2>/dev/null | tail -1)
  case "$r" in dgvoodoo) echo dgvoodoo ;; *) echo d7vk ;; esac
}
renderer() {
  rm -f "$GAME/ddraw.dll" "$GAME/DDraw.dll" "$GAME/D3DImm.dll" "$GAME/d3dimm.dll"
  case "$(renderer_name)" in
    dgvoodoo)
      cp "$GAME/renderer/dgvoodoo/DDraw.dll" "$GAME/ddraw.dll"
      cp "$GAME/renderer/dgvoodoo/D3DImm.dll" "$GAME/D3DImm.dll"
      [ -f "$GAME/dgVoodoo.conf" ] || cp "$GAME/renderer/dgvoodoo/dgVoodoo.conf" "$GAME/dgVoodoo.conf"
      export WINEDLLOVERRIDES="$WINEDLLOVERRIDES;d3dimm=n,b" ;;
    *) cp "$GAME/renderer/d7vk/ddraw.dll" "$GAME/ddraw.dll" ;;
  esac
}

run() {
  renderer
  [ -f "$LOGS/wine.log" ] && mv -f "$LOGS/wine.log" "$LOGS/wine.prev.log"
  cd "$GAME" || exit 1
  "$WINE" Yanis.exe >"$LOGS/wine.log" 2>&1
  code=$?
  "$ROOT/wine/bin/wineserver" -w 2>/dev/null
  if [ "$code" -ne 0 ] || grep -q "Unhandled exception" "$LOGS/wine.log"; then
    if ask "Yanis Online closed with an error (code $code). Send a report so it can be fixed? You will see everything before it is sent."; then
      python3 "$GAME/launcher/yanis-report.py" "$GAME" --crash "$code"
    fi
  fi
}

play() {
  prepare
  base
  update
  run
}

# The window when there is a desktop and Tk (python3-tk / tk), the plain flow otherwise.
window_ok() {
  [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ] && command -v python3 >/dev/null \
    && python3 -c 'import tkinter' 2>/dev/null
}

case "${1:-}" in
  "") if window_ok; then exec python3 "$GAME/launcher/yanis-launcher.py"; else play; fi ;;
  prepare) prepare ;;
  base) python3 "$GAME/launcher/yanis-update.py" base "$GAME" 2>&1 | tee -a "$LOGS/update.log"; exit "${PIPESTATUS[0]}" ;;
  update)
    command -v python3 >/dev/null || exit 2
    python3 "$GAME/launcher/yanis-update.py" patch "$GAME" 2>&1 | tee -a "$LOGS/update.log"; exit "${PIPESTATUS[0]}" ;;
  run) run ;;
  play) play ;;
  setup) prepare; cd "$GAME" && exec "$WINE" opensetup.exe ;;
  report) exec python3 "$GAME/launcher/yanis-report.py" "$GAME" ;;
  winecfg) prepare; exec "$WINE" winecfg ;;
  *) echo "usage: yanis-online [play|setup|report|winecfg]" >&2; exit 2 ;;
esac
