#!/bin/bash

# HOMEPAGE: https://github.com/markus-perl/ffmpeg-build-script
# LICENSE: https://github.com/markus-perl/ffmpeg-build-script/blob/main/LICENSE

# Entry point. The script itself lives in src/ and is sourced from here, in the
# explicit order below - not through a glob, whose order depends on LC_COLLATE
# and which would also pick up stale fragments left behind by an in-place
# upgrade over an older tree.
#
# Everything is sourced into this shell rather than executed in subshells: the
# package functions mutate CONFIGURE_OPTIONS, CFLAGS/LDFLAGS/CXXFLAGS,
# EXTRALIBS, PATH and the OPENSSL_* exports, and download() leaves the shell
# inside the extracted source directory. Order is therefore load-bearing.

# Where the fragments live. Only used for sourcing - CWD in src/20-globals.sh
# stays the invocation directory, which is what decides where packages/ and
# workspace/ are created.
#
# Symlinks are followed by hand: a "ln -s /repo/build-ffmpeg ~/bin/build-ffmpeg"
# has to find /repo/src, not ~/bin/src. "readlink -f" and "realpath" are not
# portable (stock macOS has neither), plain "readlink" is. The hop count is
# capped so a symlink cycle cannot spin forever. CDPATH is cleared so a cd of a
# relative directory cannot be redirected to a same-named decoy elsewhere.
SCRIPT_SOURCE=${BASH_SOURCE[0]}
SCRIPT_LINK_HOPS=0
while [ -L "$SCRIPT_SOURCE" ] && [ "$SCRIPT_LINK_HOPS" -lt 16 ]; do
    SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$SCRIPT_SOURCE")" && pwd)
    SCRIPT_SOURCE=$(readlink -- "$SCRIPT_SOURCE")
    case $SCRIPT_SOURCE in
    /*) ;;
    *) SCRIPT_SOURCE="$SCRIPT_DIR/$SCRIPT_SOURCE" ;;
    esac
    SCRIPT_LINK_HOPS=$((SCRIPT_LINK_HOPS + 1))
done
SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$SCRIPT_SOURCE")" && pwd)
# Exported so the fragments see it too - do_update() in src/30-helpers.sh
# replaces exactly this tree, and it is the only thing in the script that cares
# where the script itself lives rather than where it was invoked.
export SCRIPT_DIR
unset SCRIPT_SOURCE SCRIPT_LINK_HOPS

if [ ! -d "$SCRIPT_DIR/src" ]; then
    echo "Error: $SCRIPT_DIR/src is missing."
    echo ""
    echo "build-ffmpeg is no longer a single file - it needs the src/ directory"
    echo "next to it. Downloading this file on its own cannot work."
    echo ""
    echo "Get the whole tree instead:"
    echo "  curl -sSL https://raw.githubusercontent.com/markus-perl/ffmpeg-build-script/main/web-install.sh | bash"
    echo "or"
    echo "  git clone https://github.com/markus-perl/ffmpeg-build-script.git"
    exit 1
fi

# A fragment that fails to load has to stop the script. There is no set -e, so
# bash prints an error and carries on after a failed "."; the dispatch loop then
# skips that fragment's packages one "command not found" at a time and FFmpeg is
# configured without them, so an hour-long build "succeeds" minus its libraries.
#
# The || fires on any non-zero status, so the handler tells the two cases apart:
# a missing or unreadable fragment is fatal, while a fragment that merely ended
# on a non-zero command is left alone - the status of a "." is the status of the
# sourced file's last command, and src/90-build-order.sh ends with the dispatch
# loop.
fragment_failed() {
    if [ -r "$SCRIPT_DIR/src/$1" ]; then
        return 0
    fi
    echo "Error: $SCRIPT_DIR/src/$1 is missing or unreadable." >&2
    echo "" >&2
    echo "The src/ tree is incomplete. A partial tree builds an FFmpeg that is" >&2
    echo "silently missing the packages of the fragments that did not load." >&2
    echo "Get the whole tree again:" >&2
    echo "  git clone https://github.com/markus-perl/ffmpeg-build-script.git" >&2
    exit 1
}

# shellcheck source=src/00-header.sh
. "$SCRIPT_DIR/src/00-header.sh" || fragment_failed 00-header.sh
# shellcheck source=src/10-versions.sh
. "$SCRIPT_DIR/src/10-versions.sh" || fragment_failed 10-versions.sh
# shellcheck source=src/20-globals.sh
. "$SCRIPT_DIR/src/20-globals.sh" || fragment_failed 20-globals.sh
# shellcheck source=src/30-helpers.sh
. "$SCRIPT_DIR/src/30-helpers.sh" || fragment_failed 30-helpers.sh

# From here on everything the script prints is mirrored into $CWD/build.log:
# start_build_logging() tees stdout and stderr, so the terminal keeps showing
# the live build output while a full copy accumulates in the file for a bug
# report. The traps funnel every way this script can stop - normal exit, a
# failed build step, Ctrl+C - into on_exit(), which restores .git, removes the
# temp dirs registered in EXIT_CLEANUP_DIRS and, for a genuine build failure,
# tells the user where to report it.
INVOCATION_ARGS="$*"
trap on_exit EXIT
trap 'USER_INTERRUPTED=true; exit 130' INT
trap 'USER_INTERRUPTED=true; exit 143' TERM
start_build_logging
# shellcheck source=src/40-cli.sh
. "$SCRIPT_DIR/src/40-cli.sh" || fragment_failed 40-cli.sh

# shellcheck source=src/packages/10-build-tools.sh
. "$SCRIPT_DIR/src/packages/10-build-tools.sh" || fragment_failed packages/10-build-tools.sh
# shellcheck source=src/packages/20-tls.sh
. "$SCRIPT_DIR/src/packages/20-tls.sh" || fragment_failed packages/20-tls.sh
# shellcheck source=src/packages/25-cmake.sh
. "$SCRIPT_DIR/src/packages/25-cmake.sh" || fragment_failed packages/25-cmake.sh
# shellcheck source=src/packages/30-video.sh
. "$SCRIPT_DIR/src/packages/30-video.sh" || fragment_failed packages/30-video.sh
# shellcheck source=src/packages/40-audio.sh
. "$SCRIPT_DIR/src/packages/40-audio.sh" || fragment_failed packages/40-audio.sh
# shellcheck source=src/packages/50-image.sh
. "$SCRIPT_DIR/src/packages/50-image.sh" || fragment_failed packages/50-image.sh
# shellcheck source=src/packages/55-other.sh
. "$SCRIPT_DIR/src/packages/55-other.sh" || fragment_failed packages/55-other.sh
# shellcheck source=src/packages/60-text-subtitle.sh
. "$SCRIPT_DIR/src/packages/60-text-subtitle.sh" || fragment_failed packages/60-text-subtitle.sh
# shellcheck source=src/packages/70-optical.sh
. "$SCRIPT_DIR/src/packages/70-optical.sh" || fragment_failed packages/70-optical.sh
# shellcheck source=src/packages/75-zmq.sh
. "$SCRIPT_DIR/src/packages/75-zmq.sh" || fragment_failed packages/75-zmq.sh
# shellcheck source=src/packages/80-hwaccel.sh
. "$SCRIPT_DIR/src/packages/80-hwaccel.sh" || fragment_failed packages/80-hwaccel.sh

# shellcheck source=src/90-build-order.sh
. "$SCRIPT_DIR/src/90-build-order.sh" || fragment_failed 90-build-order.sh
# shellcheck source=src/95-ffmpeg.sh
# shellcheck disable=SC2317 # only reachable when the fragment is missing; 95-ffmpeg.sh ends in exit 0
. "$SCRIPT_DIR/src/95-ffmpeg.sh" || fragment_failed 95-ffmpeg.sh
