#!/usr/bin/env bash
#
# pre-push: run the cheap local gates before a commit reaches the forge.
#
# Install once per clone:
#
#     git config core.hooksPath .githooks
#
# The AKGL build and mutation harness are opt-in because they are expensive:
#
#     AKBASIC_HOOK_AKGL=1 git push
#     AKBASIC_HOOK_MUTATION=1 git push
#
# Bypass everything with Git's escape hatch: git push --no-verify

set -u

ZERO_SHA=0000000000000000000000000000000000000000

root=$(git rev-parse --show-toplevel) || exit 1
cd "$root" || exit 1

# A push that only deletes refs, or an empty push input, has no new commit to
# test. Git calls hooks for those pushes too.
has_updates=0
while read -r _local_ref local_sha _remote_ref _remote_sha; do
    if [ "$local_sha" != "$ZERO_SHA" ]; then
        has_updates=1
    fi
done
if [ "$has_updates" -eq 0 ]; then
    exit 0
fi

if [ ! -f deps/libakerror/CMakeLists.txt ]; then
    echo "pre-push: deps/libakerror is empty. Run:" >&2
    echo "    git submodule update --init --recursive" >&2
    exit 1
fi

builddir="${AKBASIC_HOOK_BUILD_DIR:-$(git rev-parse --git-dir)/akbasic-prepush}"
mkdir -p "$builddir" || exit 1
logfile="$builddir/last.log"

# Keep output quiet on success, but retain the complete failing command output
# so a failed push is actionable without rerunning the gate by hand.
run() {
    if ! "$@" >"$logfile" 2>&1; then
        echo >&2
        echo "pre-push: FAILED: $*" >&2
        echo "---------------------------------------------------------------" >&2
        cat "$logfile" >&2
        echo "---------------------------------------------------------------" >&2
        echo "pre-push: push aborted. Use 'git push --no-verify' to override." >&2
        exit 1
    fi
}

if [ -x scripts/cppcheck.sh ]; then
    if command -v cppcheck > /dev/null 2>&1; then
        echo "pre-push: cppcheck"
        run scripts/cppcheck.sh
    else
        echo "pre-push: cppcheck not installed, skipping the optional check" >&2
    fi
else
    echo "pre-push: scripts/cppcheck.sh not present, skipping the optional check" >&2
fi

echo "pre-push: default build + ctest"
run cmake -S . -B "$builddir/default"
run cmake --build "$builddir/default" --parallel 2
run ctest --test-dir "$builddir/default" --output-on-failure

echo "pre-push: sanitizer build + ctest"
run cmake -S . -B "$builddir/sanitize" -DAKBASIC_SANITIZE=ON
run cmake --build "$builddir/sanitize" --parallel 2
run ctest --test-dir "$builddir/sanitize" --output-on-failure

if [ "${AKBASIC_HOOK_AKGL:-0}" = "1" ]; then
    echo "pre-push: AKGL build + ctest"
    run cmake -S . -B "$builddir/akgl" -DAKBASIC_WITH_AKGL=ON
    run cmake --build "$builddir/akgl" --parallel 2
    run env SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
        SDL_RENDER_DRIVER=software ctest --test-dir "$builddir/akgl" --output-on-failure
fi

if [ "${AKBASIC_HOOK_MUTATION:-0}" = "1" ]; then
    threshold="${AKBASIC_MUTATION_THRESHOLD:-65}"
    mutants="${AKBASIC_HOOK_MUTANTS:-260}"
    echo "pre-push: mutation testing, threshold ${threshold}%"
    run python3 scripts/mutation_test.py \
        --target src/symtab.c \
        --max-mutants "$mutants" \
        --threshold "$threshold"
fi

echo "pre-push: OK"
exit 0
