#!/bin/sh

# Build the two deterministic archives expected for a CTAN upload:
#   luacoolprop.zip      browsing layout with top-level luacoolprop/
#   luacoolprop.tds.zip  installable TeX Directory Structure
# The TDS archive is installation-tested before either result is published.
# CoolProp stays external and is never copied into either archive.

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"
}

[ "$#" -le 1 ] || fail "usage: $0 [OUTPUT_DIRECTORY]"
[ -n "${LUACOOLPROP_LIB:-}" ] \
    || fail "LUACOOLPROP_LIB is required to test the installed TDS archive"
[ -f "$LUACOOLPROP_LIB" ] \
    || fail "CoolProp library not found: $LUACOOLPROP_LIB"

for command_name in chmod cp date dirname find mkdir mktemp mv rm sed sort touch unzip zip; do
    need_command "$command_name"
done

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"
MANIFEST=$PROJECT_DIR/MANIFEST.txt
[ -f "$MANIFEST" ] || fail "release manifest not found: $MANIFEST"
VERSION_FILE=$PROJECT_DIR/VERSION
[ -f "$VERSION_FILE" ] || fail "release metadata not found: $VERSION_FILE"

OUTPUT_DIR=${1:-$PROJECT_DIR/dist}
mkdir -p "$OUTPUT_DIR" || fail "could not create $OUTPUT_DIR"
OUTPUT_DIR=$(CDPATH= cd -P "$OUTPUT_DIR" && pwd) \
    || fail "could not resolve the output directory"

VERSION=$(sed -n \
    's/^\\ProvidesPackage{luacoolprop}\[[^ ]* v\([^ ]*\) .*/\1/p' \
    "$PROJECT_DIR/luacoolprop.sty")
BUNDLE_VERSION=$(sed -n 's/^VERSION=//p' "$VERSION_FILE")
[ -n "$BUNDLE_VERSION" ] || fail "VERSION does not define VERSION"
[ "$VERSION" = "$BUNDLE_VERSION" ] \
    || fail "version mismatch: luacoolprop.sty=$VERSION, VERSION=$BUNDLE_VERSION"

if [ -n "${SOURCE_DATE_EPOCH:-}" ]; then
    RELEASE_EPOCH=$SOURCE_DATE_EPOCH
else
    RELEASE_EPOCH=$(sed -n 's/^SOURCE_DATE_EPOCH=//p' "$VERSION_FILE") \
        || fail "could not read SOURCE_DATE_EPOCH from VERSION"
fi
case $RELEASE_EPOCH in
    ''|*[!0-9]*) fail "SOURCE_DATE_EPOCH must be a non-negative integer" ;;
esac
SOURCE_DATE_EPOCH=$RELEASE_EPOCH
FORCE_SOURCE_DATE=1
export SOURCE_DATE_EPOCH FORCE_SOURCE_DATE

# BSD date (macOS) and GNU date use different epoch options.
if TOUCH_TIMESTAMP=$(date -u -r "$RELEASE_EPOCH" '+%Y%m%d%H%M.%S' 2>/dev/null); then
    :
elif TOUCH_TIMESTAMP=$(date -u -d "@$RELEASE_EPOCH" '+%Y%m%d%H%M.%S' 2>/dev/null); then
    :
else
    fail "could not convert SOURCE_DATE_EPOCH with the platform date command"
fi

TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/luacoolprop-release.XXXXXX") \
    || fail "could not create a temporary directory"
cleanup()
{
    rm -rf "$TEMP_DIR"
}
trap cleanup 0
trap 'exit 1' HUP INT TERM

PACKAGE_NAME=luacoolprop
PACKAGE_DIR=$TEMP_DIR/$PACKAGE_NAME
TDS_DIR=$TEMP_DIR/tds
mkdir -p "$PACKAGE_DIR" "$TDS_DIR" \
    || fail "could not create staging directories"

while IFS= read -r relative_path || [ -n "$relative_path" ]; do
    case $relative_path in
        ''|'#'*) continue ;;
        /*|..|../*|*/../*) fail "unsafe manifest entry: $relative_path" ;;
    esac
    source_path=$PROJECT_DIR/$relative_path
    target_path=$PACKAGE_DIR/$relative_path
    [ -f "$source_path" ] || fail "manifest file not found: $relative_path"
    mkdir -p "$(dirname "$target_path")" \
        || fail "could not create a directory for $relative_path"
    cp "$source_path" "$target_path" \
        || fail "could not stage $relative_path"
done <"$MANIFEST"

install_tds_file()
{
    relative_source=$1
    relative_target=$2
    source_file=$PACKAGE_DIR/$relative_source
    target_file=$TDS_DIR/$relative_target
    [ -f "$source_file" ] || fail "TDS source is missing: $relative_source"
    mkdir -p "$(dirname "$target_file")" \
        || fail "could not create a TDS directory for $relative_target"
    cp "$source_file" "$target_file" \
        || fail "could not install TDS member $relative_target"
}

# Runtime files. Each frontend lives in its standard format subtree, while the
# two Lua modules use tex/luatex so LuaTeX's kpathsea loader can require them.
for file_name in luacoolprop.tex pgflibrarypgfplots.autonode.code.tex pgfplots-autonode.code.tex; do
    install_tds_file "$file_name" "tex/generic/luacoolprop/$file_name"
done
for file_name in luacoolprop.sty pgfplots-autonode.sty; do
    install_tds_file "$file_name" "tex/latex/luacoolprop/$file_name"
done
for file_name in p-luacoolprop.tex luacoolprop-plain.tex; do
    install_tds_file "$file_name" "tex/plain/luacoolprop/$file_name"
done
install_tds_file t-luacoolprop.tex \
    tex/context/third/luacoolprop/t-luacoolprop.tex
for file_name in luacoolprop.lua pgfplots-autonode.lua; do
    install_tds_file "$file_name" "tex/luatex/luacoolprop/$file_name"
done

# User documentation and executable examples.
for file_name in README.md CHANGELOG.md LICENSE LICENSE-CoolProp.txt VERSION \
    luacoolprop-manual.pdf; do
    install_tds_file "$file_name" "doc/generic/luacoolprop/$file_name"
done
install_tds_file tutorial/README.md \
    doc/generic/luacoolprop/tutorial/README.md
for file_name in methane-linde-cycle.png butane-pv-ideal-gas.png; do
    install_tds_file "docs/images/$file_name" \
        "doc/generic/luacoolprop/docs/images/$file_name"
done
find "$PACKAGE_DIR/examples" "$PACKAGE_DIR/tutorial" -type f \
    \( -name '*.tex' -o -name '*.lua' -o -name '*.pdf' \) -print \
    >"$TEMP_DIR/document-files"
while IFS= read -r source_file; do
    relative_source=${source_file#"$PACKAGE_DIR/"}
    install_tds_file "$relative_source" \
        "doc/generic/luacoolprop/$relative_source"
done <"$TEMP_DIR/document-files"

# Sources and maintainer tools. Generated PDFs and logs do not belong here.
install_tds_file luacoolprop-manual.tex \
    source/generic/luacoolprop/luacoolprop-manual.tex
install_tds_file MANIFEST.txt source/generic/luacoolprop/MANIFEST.txt
install_tds_file VERSION source/generic/luacoolprop/VERSION
find "$PACKAGE_DIR/docs" "$PACKAGE_DIR/examples" "$PACKAGE_DIR/scripts" \
    "$PACKAGE_DIR/tests" "$PACKAGE_DIR/tutorial/source" -type f \
    ! -name '*.pdf' ! -name '*.log' ! -name '*.png' -print \
    >"$TEMP_DIR/source-files"
while IFS= read -r source_file; do
    relative_source=${source_file#"$PACKAGE_DIR/"}
    install_tds_file "$relative_source" \
        "source/generic/luacoolprop/$relative_source"
done <"$TEMP_DIR/source-files"

normalize_tree()
{
    tree=$1
    find "$tree" -type d -exec chmod 755 {} + \
        || fail "could not normalize directory modes in $tree"
    find "$tree" -type f -exec chmod 644 {} + \
        || fail "could not normalize file modes in $tree"
    if [ -d "$tree/scripts" ]; then
        find "$tree/scripts" -type f -name '*.sh' -exec chmod 755 {} + \
            || fail "could not normalize script modes in $tree"
    fi
    if [ -d "$tree/source/generic/luacoolprop/scripts" ]; then
        find "$tree/source/generic/luacoolprop/scripts" -type f -name '*.sh' \
            -exec chmod 755 {} + \
            || fail "could not normalize TDS script modes"
    fi
    find "$tree" -exec touch -t "$TOUCH_TIMESTAMP" {} + \
        || fail "could not normalize timestamps in $tree"
}

zip_tree_contents()
{
    tree=$1
    archive=$2
    (CDPATH= cd -P "$tree" && find tex doc source -type f -print) \
        >"$TEMP_DIR/zip-files.unsorted"
    LC_ALL=C sort "$TEMP_DIR/zip-files.unsorted" >"$TEMP_DIR/zip-files"
    (CDPATH= cd -P "$tree" && zip -X -q "$archive" -@ \
        <"$TEMP_DIR/zip-files") \
        || fail "could not create $archive"
}

normalize_tree "$PACKAGE_DIR"
normalize_tree "$TDS_DIR"

TDS_ARCHIVE=$TEMP_DIR/luacoolprop.tds.zip
zip_tree_contents "$TDS_DIR" "$TDS_ARCHIVE"

# Test the archive after compression and extraction, not merely the staging
# directory. This catches missing members and resolver-layout mistakes.
"$SCRIPT_DIR/test-tds.sh" "$TDS_ARCHIVE"

# CTAN places the TDS archive beside the browsing directory in the upload ZIP.
touch -t "$TOUCH_TIMESTAMP" "$TDS_ARCHIVE"
(CDPATH= cd -P "$TEMP_DIR" && {
    find "$PACKAGE_NAME" -type f -print
    printf '%s\n' luacoolprop.tds.zip
}) >"$TEMP_DIR/outer-files.unsorted"
LC_ALL=C sort "$TEMP_DIR/outer-files.unsorted" >"$TEMP_DIR/outer-files"
BROWSING_ARCHIVE=$TEMP_DIR/luacoolprop.zip
(CDPATH= cd -P "$TEMP_DIR" && zip -X -q "$BROWSING_ARCHIVE" -@ \
    <"$TEMP_DIR/outer-files") \
    || fail "could not create the CTAN browsing archive"

FINAL_TDS=$OUTPUT_DIR/luacoolprop.tds.zip
FINAL_BROWSING=$OUTPUT_DIR/luacoolprop.zip
cp "$TDS_ARCHIVE" "$FINAL_TDS" || fail "could not publish $FINAL_TDS"
mv -f "$BROWSING_ARCHIVE" "$FINAL_BROWSING" \
    || fail "could not publish $FINAL_BROWSING"

printf '%s\n%s\n' "$FINAL_BROWSING" "$FINAL_TDS"
