#!/usr/bin/env bash
# The Linux game folder from your own IPA (#84, ADR 0065, docs/play/linux-and-steam-deck.md): a
# local, personal-use-only build of the game for Linux x86_64 and the Steam Deck. It is never
# uploaded; everything stays below build/linux-game/ (git-ignored).
#
#   build [REV] [--worktree] [--arch amd64|arm64] [--container | --native]
#         [--glibc-floor V] [--build-arg A=B]... [--out DIR]
#         compile MapleThief with the generated code (linux-release preset), run its Linux
#         checks (platform.linux_portable, .version, .smoke) and write
#         build/linux-game/MapleThief-game-<version>-linux-<arch>/ and its .tar.gz
#         (mtport.linux_game). By default (and always on a Mac) in the portable build image of
#         tools/containers/linux-game/ (Ubuntu 24.04, so the result runs on glibc 2.39 and
#         later; x86_64 under Rosetta on Apple silicon), from a clean export of REV (default
#         HEAD; --worktree: tracked changes included) with work/ mounted read-only.
#         --native (Linux only) builds in this checkout with the host's compiler instead; the
#         result then needs the host's glibc or newer.
#   check [TARBALL] [--image IMAGE] [--frames N]
#         unpack TARBALL (default: the newest build/linux-game/MapleThief-game-*.tar.gz) and run
#         it in a plain distribution image without any extra package (default: Ubuntu 24.04 by
#         digest): MapleThief.sh --version, then the game headless (SDL's dummy drivers) with a
#         tap on Play at 3 s for N window frames (default 150, into the first map), which must
#         exit 0; its read-backs go to build/linux-game/check/capture/.
#   player [REV] [--worktree] [--arch amd64|arm64] [--container | --native]
#         [--glibc-floor V] [--build-arg A=B]... [--out DIR]
#         the release's player tarball (#182, ADR 0125) instead: the linux-player workflow
#         (MapleThief with the generated code and no bundle; platform.version,
#         .player_contents and .linux_portable; CPack), in the same image and with the same
#         glibc floor, only work/gen/ mounted. Writes
#         build/linux-game/MapleThief-<version>-linux-<arch>.tar.gz. It holds none of the
#         game's assets: scripts/release guards it in player mode and uploads it.
#
# Needs work/ipa/ (scripts/bootstrap; not for player) and work/gen/ (python3 -m recomp all),
# and Docker except for --native (OrbStack or Docker Desktop on a Mac). Environment:
# MAPLETHIEF_VERSION names the build (default 0.0.1); PYTHON=<interpreter> (3.10+).
set -euo pipefail

REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO"

# Ubuntu 24.04 LTS (glibc 2.39), multi-arch index digest of 2026-10-30; the same base as
# tools/containers/linux-game/Dockerfile.
DEFAULT_CHECK_IMAGE="ubuntu:24.04@sha256:fe81560990fae86dc4743202b2cb4d3df98d0bcf949173cecef15ce1127ef852"
# The glibc of that image: the newest GLIBC symbol version a container build may need.
CONTAINER_GLIBC_FLOOR=2.39

usage() {
    cat >&2 <<'EOF'
Usage: scripts/linux-game build [REV] [--worktree] [--arch amd64|arm64] [--container | --native]
                                [--glibc-floor V] [--build-arg NAME=VALUE]... [--out DIR]
       scripts/linux-game check [TARBALL] [--image IMAGE] [--frames N]
       scripts/linux-game player [REV] [build's options]

  build           compile MapleThief with the generated code (linux-release), check it and
                  write the game folder and tarball to build/linux-game/; in the portable
                  build image (Ubuntu 24.04, runs on glibc 2.39+) from a clean export of REV
                  (default HEAD), unless --native
  check           run a game tarball in a plain distribution image (default Ubuntu 24.04):
                  --version, then the game headless into the first map
  player          like build, but the release's player tarball (linux-player preset):
                  the game without its bundle, MapleThief-<version>-linux-<arch>.tar.gz

  --worktree      build, player: export the working tree's tracked files, changes included
  --arch ARCH     build in the container: amd64 (default) or arm64
  --container     build in the container (the default)
  --native        build (Linux only) in this checkout with the host's compiler; the result
                  needs the host's glibc or newer
  --glibc-floor V build: fail unless the executable needs at most GLIBC_V (container: 2.39)
  --build-arg A=B build in the container: passed to docker build of its image
  --out DIR       build, player: where the output goes (default build/linux-game)
  --image IMAGE   check: the image to run in (default Ubuntu 24.04, pinned by digest)
  --frames N      check: window frames to run (default 150)

The build folder holds the game built from your own IPA: keep it to your own machines. The
player tarball holds none of the game's files (ADR 0125).
EOF
    exit "${1:-2}"
}

die() { echo "scripts/linux-game: $*" >&2; exit 1; }

# Git for Windows' bash (`just build linux` on Windows, scripts/build.ps1): no shasum, Docker
# wants C:/... paths (so -v arguments must not go through MSYS path conversion), and with
# core.autocrlf the working copy's Dockerfile has CRLF lines, which break the scripts it writes:
# there the image is built from the commit's files instead (LF, via git archive).
windows=0
case "$(uname -s)" in
    MINGW* | MSYS* | CYGWIN*) windows=1; host_dir() { (cd "$1" && pwd -W); } ;;
    *) host_dir() { (cd "$1" && pwd -P); } ;;
esac
sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum; else shasum -a 256; fi; }
docker() { MSYS_NO_PATHCONV=1 command docker "$@"; }

[ "$#" -ge 1 ] || usage
player=0
case "$1" in
    build | check) cmd="$1"; shift ;;
    player) cmd=build; player=1; shift ;;
    -h | --help) usage 0 ;;
    *) echo "scripts/linux-game: unknown command: $1" >&2; usage ;;
esac

rev=HEAD
worktree=0
arch=amd64
native=0
floor=""
out="build/linux-game"
image="$DEFAULT_CHECK_IMAGE"
frames=150
tarball=""
build_args=()
while [ "$#" -gt 0 ]; do
    case "$1" in
        --worktree) worktree=1; shift ;;
        --native) native=1; shift ;;
        --container) native=0; shift ;;
        --arch | --out | --image | --frames | --glibc-floor | --build-arg)
            [ "$#" -ge 2 ] || usage
            case "$1" in
                --arch) arch="$2" ;;
                --out) out="$2" ;;
                --image) image="$2" ;;
                --frames) frames="$2" ;;
                --glibc-floor) floor="$2" ;;
                --build-arg) build_args+=(--build-arg "$2") ;;
            esac
            shift 2
            ;;
        -h | --help) usage 0 ;;
        -*) echo "scripts/linux-game: unknown option: $1" >&2; usage ;;
        *)
            if [ "$cmd" = build ]; then rev="$1"; else tarball="$1"; fi
            shift
            ;;
    esac
done
case "$arch" in
    amd64 | arm64) ;;
    *) echo "scripts/linux-game: --arch must be amd64 or arm64, not $arch" >&2; exit 2 ;;
esac
case "$frames" in
    '' | *[!0-9]*) echo "scripts/linux-game: --frames must be a number, not $frames" >&2; exit 2 ;;
esac
case "$floor" in
    '' | [0-9]*.[0-9]*) ;;
    *) echo "scripts/linux-game: --glibc-floor must be a version like 2.39, not $floor" >&2; exit 2 ;;
esac

PY="${PYTHON:-python3}"
command -v "$PY" >/dev/null 2>&1 || die "need Python 3.10+, but '$PY' was not found (set PYTHON)"
"$PY" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 10) else 1)' 2>/dev/null ||
    die "need Python 3.10+, but '$PY' is older (set PYTHON=/path/to/python3)"
linux_game() { PYTHONPATH="$REPO/tools" "$PY" -B -m mtport.linux_game "$@"; }

bundle=work/ipa/Payload/MapleThief.app
need_inputs() {
    [ "$player" -eq 1 ] || [ -d "$bundle" ] || die "no $bundle: run scripts/bootstrap"
    [ -f work/gen/gen.cmake ] || die "no work/gen/gen.cmake: run python3 -m recomp all"
}

# The player tarball (ADR 0125): the linux-player workflow with the glibc floor, then CPack.
build_player_native() {
    local preset=linux-player name
    echo "== cmake --preset $preset -DMAPLETHIEF_LINUX_GLIBC_FLOOR=$floor"
    cmake --preset "$preset" "-DMAPLETHIEF_LINUX_GLIBC_FLOOR=$floor"
    echo "== cmake --build --preset $preset (MapleThief only)"
    cmake --build --preset "$preset"
    echo "== ctest --preset $preset: platform.version, .player_contents, .linux_portable"
    ctest --preset "$preset"
    rm -f "build/$preset"/MapleThief-[0-9]*-linux-*.tar.gz
    echo "== cpack --preset $preset"
    cpack --preset "$preset"
    name="$(cd "build/$preset" && ls MapleThief-[0-9]*-linux-*.tar.gz)"
    mkdir -p "$out"
    cp "build/$preset/$name" "$out/$name"
    echo "== $out/$name ($(wc -c <"$out/$name" | tr -d ' ') bytes)"
}

build_native() {
    [ "$(uname -s)" = Linux ] || die "--native builds only on Linux (on a Mac: the container)"
    need_inputs
    if [ "$player" -eq 1 ]; then
        build_player_native
        return
    fi
    echo "== cmake --preset linux-release -DMAPLETHIEF_LINUX_GLIBC_FLOOR=$floor"
    cmake --preset linux-release "-DMAPLETHIEF_LINUX_GLIBC_FLOOR=$floor"
    echo "== cmake --build --preset linux-release --target MapleThief"
    cmake --build --preset linux-release --target MapleThief
    # Only the tests of the executable itself: the rest of the suite is scripts/test's.
    echo "== ctest: platform.linux_portable, platform.version, platform.smoke"
    ctest --preset linux-release -R '^platform\.(linux_portable|version|smoke)$'
    local program=build/linux-release/platform/MapleThief version
    version="$("$program" --version)"
    version="${version#MapleThief }"
    linux_game stage --program "$program" --bundle "$bundle" --version "$version" \
        --arch "$(uname -m)" --out "$out"
}

build_container() {
    type -P docker >/dev/null || die "docker not found (on a Mac: OrbStack or Docker Desktop)"
    need_inputs
    local commit label dockerfile image_hash build_image downloads host_out
    if [ "$worktree" -eq 1 ]; then
        commit="$(git stash create)"
        commit="${commit:-$(git rev-parse HEAD)}"
        label="working tree of $(git rev-parse --short HEAD)"
    else
        commit="$(git rev-parse --verify "$rev^{commit}")"
        label="$(git rev-parse --short "$commit")"
    fi
    # Tagged by a hash of the Dockerfile and the build arguments, as scripts/test-linux does.
    dockerfile=tools/containers/linux-game/Dockerfile
    image_hash="$(
        {
            tr -d '\r' < "$dockerfile"
            printf '%s\n' ${build_args[@]+"${build_args[@]}"}
        } | sha256 | cut -c1-12
    )"
    build_image="maplethief-linux-game:$image_hash-$arch"
    if ! docker image inspect "$build_image" >/dev/null 2>&1; then
        echo "== Building $build_image from $dockerfile"
        local context=tools/containers/linux-game
        if [ "$windows" -eq 1 ]; then
            context=build/linux-game/image-context
            rm -rf "$context"
            mkdir -p "$context"
            git -c core.autocrlf=false archive --format=tar "$commit:tools/containers/linux-game" |
                tar -x -C "$context"
        fi
        docker build --platform "linux/$arch" ${build_args[@]+"${build_args[@]}"} \
            -t "$build_image" "$context"
    fi
    mkdir -p build/_downloads "$out"
    downloads="$(host_dir build/_downloads)"
    host_out="$(host_dir "$out")"
    local env=() mode=build what="game build" work_mount
    [ -n "${MAPLETHIEF_VERSION:-}" ] && env+=(-e MAPLETHIEF_VERSION)
    # The game folder needs the bundle (work/ipa) and the generated code; the player tarball
    # only the generated code, so only work/gen is mounted for it.
    work_mount="$(host_dir work):/src/work:ro"
    if [ "$player" -eq 1 ]; then
        mode=player
        what="player build"
        work_mount="$(host_dir work/gen):/src/work/gen:ro"
    fi
    echo "== Linux ($arch) $what in $build_image on $label"
    # The export holds no IPA (Git LFS smudge off all the same); the game comes from work/,
    # mounted read-only. The build tree stays in the container; only the folder and the
    # tarball come back. core.autocrlf off: on Windows git archive would give it CRLF scripts.
    git -c core.autocrlf=false -c filter.lfs.process= -c filter.lfs.smudge=cat -c filter.lfs.required=false \
        archive --format=tar "$commit" |
        docker run --rm -i --platform "linux/$arch" ${env[@]+"${env[@]}"} \
            -v "$downloads:/cache/downloads" -v "$work_mount" -v "$host_out:/out" \
            "$build_image" bash -c '
set -euo pipefail
untar - /src
mkdir -p build
ln -s /cache/downloads build/_downloads
echo "$("${CXX:-c++}" --version | head -n 1); $(ldd --version | head -n 1); arch $(uname -m)"
scripts/linux-game "$2" --native --glibc-floor "$1" --out /out
' bash "${floor:-$CONTAINER_GLIBC_FLOOR}" "$mode"
    if [ "$player" -eq 1 ]; then
        echo "== Player tarball in $out/ (no game files: guard it with mtport.release --mode player)"
    else
        echo "== Game folder and tarball in $out/ (your own build: keep it to your own machines)"
    fi
}

cmd_build() {
    if [ "$native" -eq 1 ]; then
        build_native
    else
        build_container
    fi
}

cmd_check() {
    type -P docker >/dev/null || die "docker not found (on a Mac: OrbStack or Docker Desktop)"
    if [ -z "$tarball" ]; then
        # shellcheck disable=SC2012 # names are ours: MapleThief-game-<version>-linux-<arch>
        tarball="$(ls -t build/linux-game/MapleThief-game-*.tar.gz 2>/dev/null | head -n 1 || true)"
        [ -n "$tarball" ] || die "no build/linux-game/MapleThief-game-*.tar.gz: run scripts/linux-game build"
    fi
    [ -f "$tarball" ] || die "no $tarball"
    local check=build/linux-game/check top name platform
    mkdir -p "$check/capture"
    find "$check" -mindepth 1 -maxdepth 1 ! -name capture -exec rm -rf {} +
    find "$check/capture" -mindepth 1 -delete
    top="$(linux_game unpack "$tarball" "$check/unpacked")"
    name="$(basename "$top")"
    case "$name" in
        *-linux-x86_64) platform=linux/amd64 ;;
        *-linux-aarch64) platform=linux/arm64 ;;
        *) die "$tarball: no -linux-x86_64 or -linux-aarch64 in its folder's name $name" ;;
    esac
    echo "== $tarball in $image ($platform)"
    # A plain image: nothing installed, root, HOME=/tmp/home for the data directory.
    docker run --rm --platform "$platform" \
        -v "$(host_dir "$check/unpacked"):/game:ro" \
        -v "$(host_dir "$check/capture"):/capture" \
        -e HOME=/tmp/home -e SDL_VIDEO_DRIVER=dummy -e SDL_AUDIO_DRIVER=dummy \
        "$image" sh -c '
set -eu
. /etc/os-release
echo "image: $PRETTY_NAME, $(ldd --version | head -n 1), arch $(uname -m)"
mkdir -p "$HOME"
game="/game/$1"
"$game/MapleThief.sh" --version
"$game/MapleThief.sh" --frames "$2" --tap 3,106,165 --capture /capture
echo "game: exit 0 after $2 frames"
' sh "$name" "$frames"
    echo "== check passed; read-backs in $check/capture/:"
    ls "$check/capture"
}

"cmd_$cmd"
