#!/bin/sh

# Build all bundled documents with their native TeX Live format:
# lualatex for LaTeX, luatex for plain TeX, and context for ConTeXt.
# Console logs stay in the log directories, transient auxiliary files are
# removed, and completed PDFs are moved next to their source files.

set -eu

fail()
{
    printf 'error: %s\n' "$*" >&2
    exit 1
}

need_command()
{
    command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
}

[ -n "${LUACOOLPROP_LIB:-}" ] \
    || fail "LUACOOLPROP_LIB must point to the CoolProp shared library"
case ${LUACOOLPROP_LIB##*/} in
    libCoolProp.dylib|libCoolProp.so|CoolProp.dll) ;;
    *) fail "LUACOOLPROP_LIB must name libCoolProp.dylib, libCoolProp.so, or CoolProp.dll" ;;
esac
[ -f "$LUACOOLPROP_LIB" ] \
    || fail "CoolProp library not found: $LUACOOLPROP_LIB"
[ -r "$LUACOOLPROP_LIB" ] \
    || fail "CoolProp library is not readable: $LUACOOLPROP_LIB"

need_command dirname
need_command awk
need_command context
need_command lualatex
need_command luatex
need_command texlua
need_command makeindex
need_command mkdir
need_command mv
need_command cp
need_command grep
need_command rm
need_command sed
need_command tail

# Resolve paths before changing directory, including a relative library path
# supplied by the caller. LuaTeX then receives an absolute library path.
LIBRARY_DIR=$(CDPATH= cd -P "$(dirname "$LUACOOLPROP_LIB")" && pwd) \
    || fail "could not resolve LUACOOLPROP_LIB"
LIBRARY_NAME=${LUACOOLPROP_LIB##*/}
LUACOOLPROP_LIB=$LIBRARY_DIR/$LIBRARY_NAME
export LUACOOLPROP_LIB

SCRIPT_DIR=$(CDPATH= cd -P "$(dirname "$0")" && pwd) \
    || fail "could not determine the script directory"
PROJECT_DIR=$(CDPATH= cd -P "$SCRIPT_DIR/.." && pwd) \
    || fail "could not determine the project directory"

# LuaTeX and ConTeXt honor SOURCE_DATE_EPOCH for PDF metadata. VERSION records
# the package epoch so the archive can be rebuilt outside a Git checkout.
if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then
    SOURCE_DATE_EPOCH=$(sed -n 's/^SOURCE_DATE_EPOCH=//p' \
        "$PROJECT_DIR/VERSION") \
        || fail "could not read SOURCE_DATE_EPOCH from VERSION"
fi
case $SOURCE_DATE_EPOCH in
    ''|*[!0-9]*) fail "SOURCE_DATE_EPOCH must be a non-negative integer" ;;
esac
FORCE_SOURCE_DATE=1
export SOURCE_DATE_EPOCH FORCE_SOURCE_DATE
MANUAL_LOG_DIR=$PROJECT_DIR/build-logs
EXAMPLE_LOG_DIR=$PROJECT_DIR/examples/logs
TUTORIAL_LOG_DIR=$PROJECT_DIR/tutorial/logs

mkdir -p "$MANUAL_LOG_DIR" "$EXAMPLE_LOG_DIR" "$TUTORIAL_LOG_DIR" \
    || fail "could not create the log directories"

# Sandboxed builds can provide a writable luaotfload cache without
# changing ConTeXt's independent resolver cache.
if [ -n "${LUACOOLPROP_TEX_CACHE:-}" ]; then
    mkdir -p "$LUACOOLPROP_TEX_CACHE" \
        || fail "could not create LUACOOLPROP_TEX_CACHE"
    LUACOOLPROP_TEX_CACHE=$(CDPATH= cd -P "$LUACOOLPROP_TEX_CACHE" && pwd) \
        || fail "could not resolve LUACOOLPROP_TEX_CACHE"
fi

# Search this checkout before the installed TeX tree. The trailing empty path
# component tells TeX Live to retain its standard search locations.
TEXINPUTS=$PROJECT_DIR:${TEXINPUTS-}:
export TEXINPUTS

run_lua_engine()
{
    if [ -n "${LUACOOLPROP_TEX_CACHE:-}" ]; then
        TEXMFCACHE=$LUACOOLPROP_TEX_CACHE
        export TEXMFCACHE
    fi
    "$@"
}

run_document()
{
    tex_path=$1
    log_dir=$2
    saved_log=$3
    tex_format=$4
    tex_absolute=$PROJECT_DIR/$tex_path
    base_name=${tex_path##*/}
    base_name=${base_name%.tex}
    console_log=$log_dir/.$base_name-console.log

    # Never accept a PDF left by an earlier build as the output of this run.
    rm -f "$log_dir/$base_name.pdf" "$log_dir/$base_name-error.log" \
        || fail "could not clear stale output for $tex_path"

    printf 'Building %s with %s\n' "$tex_path" "$tex_format"

    # Terminal output is captured separately because every engine also writes
    # a format-specific log. ConTeXt is forced to its LuaTeX/MkIV engine: the
    # default LuaMetaTeX/LMTX engine does not provide the required FFI module.
    engine_result=0
    case $tex_format in
        latex)
            if (run_lua_engine lualatex --shell-escape --interaction=nonstopmode \
                --halt-on-error --file-line-error \
                --output-directory="$log_dir" "$tex_absolute" \
                >"$console_log" 2>&1); then
                :
            else
                engine_result=$?
            fi
            ;;
        plain)
            if (run_lua_engine luatex --shell-escape --interaction=nonstopmode \
                --halt-on-error --file-line-error \
                --output-directory="$log_dir" "$tex_absolute" \
                >"$console_log" 2>&1); then
                :
            else
                engine_result=$?
            fi
            ;;
        context)
            if (cd "$log_dir" && context --luatex --once --batchmode \
                --shell-escape --path="$PROJECT_DIR" --result="$base_name" \
                "$tex_absolute") >"$console_log" 2>&1; then
                :
            else
                engine_result=$?
            fi
            ;;
        *)
            fail "unsupported TeX format for $tex_path: $tex_format"
            ;;
    esac

    # Preserve the console transcript using the project's existing log naming.
    mv -f "$console_log" "$saved_log" \
        || fail "could not save the log for $tex_path"

    # Some ConTeXt launchers have historically returned success after a TeX
    # failure, so inspect their explicit fatal markers as an additional guard.
    context_failed=false
    if [ "$tex_format" = context ] && awk '
        /^tex error[[:space:]]*>/ || /fatal error: return code:/ { found=1 }
        END { exit(found ? 0 : 1) }
    ' "$saved_log"; then
        context_failed=true
    fi

    if [ "$engine_result" -ne 0 ] || [ "$context_failed" = true ]; then
        printf 'error: %s failed for %s (log: %s)\n' \
            "$tex_format" "$tex_path" "$saved_log" >&2
        tail -n 15 "$saved_log" >&2
        return 1
    fi
}

# Treat actionable TeX diagnostics as build failures. Notifications that do not
# use TeX's Warning syntax (for example PGFPlots compatibility notices) remain
# visible in the saved log but do not fail the build.
check_log_diagnostics()
{
    log_file=$1
    source_file=$2
    allow_rerun=${3:-false}
    diagnostics=$(awk -v allow_rerun="$allow_rerun" '
        allow_rerun == "true" &&
        (/^Package rerunfilecheck Warning:/ ||
         /^LaTeX Warning: Label\(s\) may have changed/ ||
         /^LaTeX Warning: Reference .* undefined/ ||
         /^LaTeX Warning: There were undefined references\./) {
            next
        }
        /^(LaTeX( [^:]*)?|Package .*|Class .*) Warning:/ ||
        /^(pdfTeX|LuaTeX|LuaHBTeX) warning/ ||
        /^pgfplots-autonode warning:/ ||
        /^(Over|Under)full \\[hv]box/ {
            if (++count <= 10) {
                print NR ":" $0
            }
        }
    ' "$log_file")

    if [ -n "$diagnostics" ]; then
        printf 'error: TeX diagnostics in %s (log: %s)\n' \
            "$source_file" "$log_file" >&2
        printf '%s\n' "$diagnostics" >&2
        return 1
    fi
}

# Examples marked with summary diagnostics are integration tests, not merely
# documents which happen to compile.  Require proof that the end-axis hook ran
# and that the Lua solver reached a final assignment.
check_autonode_lifecycle()
{
    source_file=$1
    log_file=$2
    if grep -F 'auto node debug=summary' "$source_file" >/dev/null 2>&1; then
        grep -E 'pgfplots-autonode: solve: [1-9][0-9]* label' \
            "$log_file" >/dev/null 2>&1 \
            || fail "autonode solve hook did not run for $source_file"
        grep -F 'pgfplots-autonode: final status: overlaps=0 outside=0 hidden=0' \
            "$log_file" >/dev/null 2>&1 \
            || fail "autonode did not produce a complete conflict-free assignment for $source_file"
    fi
}

# TeX Live versions may leave trailing spaces in their console banner. Remove
# those spaces so regenerated logs remain suitable for patches and comparisons.
normalize_log()
{
    log_file=$1
    normalized_log=$log_file.normalized
    awk '{ sub(/[[:space:]]+$/, ""); print }' "$log_file" \
        >"$normalized_log" \
        || fail "could not normalize $log_file"
    mv -f "$normalized_log" "$log_file" \
        || fail "could not install normalized log $log_file"
}

cd "$PROJECT_DIR" || fail "could not enter the project directory"

# Keep the exhaustive reference synchronized with the public TeX and autonode
# surfaces before spending time on document compilation.
"$SCRIPT_DIR/check-documentation.sh"

# Exercise the documented Lua API independently from TeX macro expansion. Keep
# the transcript alongside the document logs and avoid machine-specific output
# so the generated log remains reproducible across CoolProp installations.
lua_api_log=$EXAMPLE_LOG_DIR/lua-api-smoke.log
printf 'Testing examples/lua-api-smoke.lua with texlua\n'
if texlua "$PROJECT_DIR/examples/lua-api-smoke.lua" >"$lua_api_log" 2>&1; then
    normalize_log "$lua_api_log"
else
    printf 'error: texlua failed for examples/lua-api-smoke.lua (log: %s)\n' \
        "$lua_api_log" >&2
    tail -n 15 "$lua_api_log" >&2
    exit 1
fi

# Build examples first because the manual embeds selected example PDFs.
example_count=0
latex_count=0
plain_count=0
context_count=0
for example_file in "$PROJECT_DIR"/examples/*.tex; do
    [ -f "$example_file" ] || fail "no examples/*.tex files found"
    example_name=${example_file##*/}
    example_stem=${example_name%.tex}
    example_format=latex
    case $example_name in
        context-*) example_format=context ;;
        plain-*) example_format=plain ;;
    esac

    run_document "examples/$example_name" "$EXAMPLE_LOG_DIR" \
        "$EXAMPLE_LOG_DIR/$example_stem.log" "$example_format"
    normalize_log "$EXAMPLE_LOG_DIR/$example_stem.log"
    check_log_diagnostics "$EXAMPLE_LOG_DIR/$example_stem.log" \
        "examples/$example_name"
    check_autonode_lifecycle "$example_file" \
        "$EXAMPLE_LOG_DIR/$example_stem.log"

    example_pdf=$EXAMPLE_LOG_DIR/$example_stem.pdf
    [ -f "$example_pdf" ] \
        || fail "$example_format did not produce $example_stem.pdf"
    mv -f "$example_pdf" "$PROJECT_DIR/examples/$example_stem.pdf" \
        || fail "could not install $example_stem.pdf"
    rm -f "$EXAMPLE_LOG_DIR/$example_stem.aux" \
        "$EXAMPLE_LOG_DIR/$example_stem.pgf" \
        "$EXAMPLE_LOG_DIR/$example_stem.tuc" \
        "$EXAMPLE_LOG_DIR/$example_stem-error.log" \
        || fail "could not remove auxiliary files for $example_name"
    example_count=$((example_count + 1))
    case $example_format in
        latex) latex_count=$((latex_count + 1)) ;;
        plain) plain_count=$((plain_count + 1)) ;;
        context) context_count=$((context_count + 1)) ;;
    esac
done

# Tutorial drivers are built before the manual because the tutorial chapter
# embeds their PDFs as source/result demonstrations. Keep this list identical
# to the release manifest; local exercise drafts in tutorial/ are not package
# build inputs. Shared files below tutorial/source are not standalone targets.
tutorial_count=0
for tutorial_name in \
    r717-worksheet.tex r717-solution.tex \
    r134a-air-conditioner.tex butane-pv.tex \
    r1234yf-heat-pump-worksheet.tex r1234yf-heat-pump-solution.tex \
    methane-linde-worksheet.tex methane-linde-solution.tex; do
    tutorial_file=$PROJECT_DIR/tutorial/$tutorial_name
    [ -f "$tutorial_file" ] || fail "tutorial source not found: $tutorial_file"
    tutorial_stem=${tutorial_name%.tex}

    run_document "tutorial/$tutorial_name" "$TUTORIAL_LOG_DIR" \
        "$TUTORIAL_LOG_DIR/$tutorial_stem.log" latex
    normalize_log "$TUTORIAL_LOG_DIR/$tutorial_stem.log"
    check_log_diagnostics "$TUTORIAL_LOG_DIR/$tutorial_stem.log" \
        "tutorial/$tutorial_name"

    tutorial_pdf=$TUTORIAL_LOG_DIR/$tutorial_stem.pdf
    [ -f "$tutorial_pdf" ] \
        || fail "latex did not produce $tutorial_stem.pdf"
    mv -f "$tutorial_pdf" "$PROJECT_DIR/tutorial/$tutorial_stem.pdf" \
        || fail "could not install $tutorial_stem.pdf"
    rm -f "$TUTORIAL_LOG_DIR/$tutorial_stem.aux" \
        "$TUTORIAL_LOG_DIR/$tutorial_stem.pgf" \
        "$TUTORIAL_LOG_DIR/$tutorial_stem-error.log" \
        || fail "could not remove auxiliary files for $tutorial_name"
    tutorial_count=$((tutorial_count + 1))
done

# Start the manual from a clean auxiliary state.  In particular, never let an
# index or table of contents from an earlier checkout influence the first pass.
rm -f "$MANUAL_LOG_DIR/luacoolprop-manual.aux" \
    "$MANUAL_LOG_DIR/luacoolprop-manual.listing" \
    "$MANUAL_LOG_DIR/luacoolprop-manual.out" \
    "$MANUAL_LOG_DIR/luacoolprop-manual.toc" \
    "$MANUAL_LOG_DIR/commands.idx" \
    "$MANUAL_LOG_DIR/commands.ilg" \
    "$MANUAL_LOG_DIR/commands.ind" \
    "$MANUAL_LOG_DIR/keys.idx" \
    "$MANUAL_LOG_DIR/keys.ilg" \
    "$MANUAL_LOG_DIR/keys.ind" \
    || fail "could not clear stale manual auxiliary files"

# The first pass writes the table of contents and the two independent indexes.
run_document "luacoolprop-manual.tex" "$MANUAL_LOG_DIR" \
    "$MANUAL_LOG_DIR/luacoolprop-manual-pass1.log" latex

for index_name in commands keys; do
    index_source=$MANUAL_LOG_DIR/$index_name.idx
    index_output=$MANUAL_LOG_DIR/$index_name.ind
    index_log=$MANUAL_LOG_DIR/$index_name.ilg
    [ -f "$index_source" ] \
        || fail "manual did not produce $index_name.idx"
    # TeX Live's default openout_any=p policy rejects absolute output paths.
    # Run in the dedicated log directory and give makeindex local filenames.
    if (cd "$MANUAL_LOG_DIR" && \
        makeindex -o "$index_name.ind" -t "$index_name.ilg" \
            "$index_name.idx" >/dev/null 2>&1); then
        :
    else
        printf 'error: makeindex failed for %s (log: %s)\n' \
            "$index_name" "$index_log" >&2
        if [ -f "$index_log" ]; then
            tail -n 15 "$index_log" >&2
        fi
        exit 1
    fi
    if awk '/Input index error/ || /[1-9][0-9]* rejected/ { found=1 }
        END { exit(found ? 0 : 1) }' "$index_log"; then
        printf 'error: makeindex rejected entries for %s (log: %s)\n' \
            "$index_name" "$index_log" >&2
        tail -n 15 "$index_log" >&2
        exit 1
    fi
done

# The second pass installs the generated indexes and resolves references.
run_document "luacoolprop-manual.tex" "$MANUAL_LOG_DIR" \
    "$MANUAL_LOG_DIR/luacoolprop-manual-pass2.log" latex

# Index insertion can move later pages after the second pass.  A final pass
# settles the table of contents, destinations, and page references and is the
# one that must be completely free of rerun requests.
run_document "luacoolprop-manual.tex" "$MANUAL_LOG_DIR" \
    "$MANUAL_LOG_DIR/luacoolprop-manual-pass3.log" latex

# Rerun requests are expected while indexes and pagination settle.  They are
# the only warnings allowlisted before the final, completely clean pass.
normalize_log "$MANUAL_LOG_DIR/luacoolprop-manual-pass1.log"
normalize_log "$MANUAL_LOG_DIR/luacoolprop-manual-pass2.log"
normalize_log "$MANUAL_LOG_DIR/luacoolprop-manual-pass3.log"
check_log_diagnostics \
    "$MANUAL_LOG_DIR/luacoolprop-manual-pass1.log" \
    "luacoolprop-manual.tex (pass 1)" true
check_log_diagnostics \
    "$MANUAL_LOG_DIR/luacoolprop-manual-pass2.log" \
    "luacoolprop-manual.tex (pass 2)" true
check_log_diagnostics \
    "$MANUAL_LOG_DIR/luacoolprop-manual-pass3.log" \
    "luacoolprop-manual.tex"

cp -f "$MANUAL_LOG_DIR/luacoolprop-manual-pass3.log" \
    "$MANUAL_LOG_DIR/luacoolprop-manual.log" \
    || fail "could not save the final manual log"

manual_pdf=$MANUAL_LOG_DIR/luacoolprop-manual.pdf
[ -f "$manual_pdf" ] || fail "latex did not produce luacoolprop-manual.pdf"
mv -f "$manual_pdf" "$PROJECT_DIR/luacoolprop-manual.pdf" \
    || fail "could not install luacoolprop-manual.pdf"
rm -f "$MANUAL_LOG_DIR/luacoolprop-manual.aux" \
    "$MANUAL_LOG_DIR/luacoolprop-manual.listing" \
    "$MANUAL_LOG_DIR/luacoolprop-manual.out" \
    "$MANUAL_LOG_DIR/luacoolprop-manual.toc" \
    "$MANUAL_LOG_DIR/commands.idx" \
    "$MANUAL_LOG_DIR/commands.ilg" \
    "$MANUAL_LOG_DIR/commands.ind" \
    "$MANUAL_LOG_DIR/keys.idx" \
    "$MANUAL_LOG_DIR/keys.ilg" \
    "$MANUAL_LOG_DIR/keys.ind" \
    || fail "could not remove the manual auxiliary files"

printf 'Built %s examples (%s LaTeX, %s plain TeX, %s ConTeXt), %s tutorials, and the manual successfully.\n' \
    "$example_count" "$latex_count" "$plain_count" "$context_count" \
    "$tutorial_count"
