#!/bin/sh

# Update the external CoolProp checkout to an explicit revision, rebuild its
# macOS shared library, synchronize LuaCoolProp's generated C ABI declarations,
# and report the loaded CoolProp version through texlua.
#
# Usage: scripts/update-coolprop.sh TAG_OR_BRANCH_OR_COMMIT
# A bare release such as 8.0.0 also resolves the corresponding v8.0.0 tag.

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

if [ "$#" -ne 1 ]; then
    fail "usage: $0 TAG_OR_BRANCH_OR_COMMIT"
fi

COOLPROP_REVISION=$1
case $COOLPROP_REVISION in
    ""|-*) fail "the CoolProp revision must not be empty or begin with '-'" ;;
esac

need_command awk
need_command cat
need_command cc
need_command chmod
need_command cmp
need_command dirname
need_command git
need_command grep
need_command mktemp
need_command mv
need_command rm
need_command tail
need_command texlua

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"
PARENT_DIR=$(CDPATH= cd -P "$PROJECT_DIR/.." && pwd) \
    || fail "could not determine the parent directory"
COOLPROP_SOURCE_DIR=$PARENT_DIR/coolprop-src
LUACOOLPROP_FILE=$PROJECT_DIR/luacoolprop.lua

# CoolProp always remains an external sibling checkout. Nothing below copies it
# into this repository or invokes git add/commit in either repository.
if [ ! -e "$COOLPROP_SOURCE_DIR" ]; then
    printf 'Cloning CoolProp into %s\n' "$COOLPROP_SOURCE_DIR"
    git clone https://github.com/CoolProp/CoolProp.git "$COOLPROP_SOURCE_DIR" \
        || fail "failed to clone CoolProp"
elif ! git -C "$COOLPROP_SOURCE_DIR" rev-parse --is-inside-work-tree \
    >/dev/null 2>&1; then
    fail "$COOLPROP_SOURCE_DIR exists but is not a Git checkout"
fi

# Refuse to overwrite work in the external checkout. Ignored build trees do not
# make the checkout dirty and are safe to reuse.
COOLPROP_STATUS=$(git -C "$COOLPROP_SOURCE_DIR" status --porcelain) \
    || fail "could not inspect the CoolProp checkout"
[ -z "$COOLPROP_STATUS" ] \
    || fail "$COOLPROP_SOURCE_DIR has uncommitted changes; preserve them before updating"

printf 'Fetching CoolProp revisions and tags\n'
git -C "$COOLPROP_SOURCE_DIR" fetch --prune --tags origin \
    || fail "failed to fetch CoolProp"

# A simple branch name selects the freshly fetched origin branch. Tags, full
# ref names, and commit IDs are resolved normally. For convenience, a bare
# release number such as 8.0.0 also tries CoolProp's v8.0.0 tag convention. The
# detached checkout is deterministic and does not create or rewrite local refs.
if TARGET_COMMIT=$(git -C "$COOLPROP_SOURCE_DIR" rev-parse --verify \
    "refs/remotes/origin/$COOLPROP_REVISION^{commit}" 2>/dev/null); then
    :
elif TARGET_COMMIT=$(git -C "$COOLPROP_SOURCE_DIR" rev-parse --verify \
    "$COOLPROP_REVISION^{commit}" 2>/dev/null); then
    :
elif [ "${COOLPROP_REVISION#v}" = "$COOLPROP_REVISION" ] \
    && TARGET_COMMIT=$(git -C "$COOLPROP_SOURCE_DIR" rev-parse --verify \
        "refs/tags/v$COOLPROP_REVISION^{commit}" 2>/dev/null); then
    printf 'Resolved CoolProp %s as tag v%s\n' \
        "$COOLPROP_REVISION" "$COOLPROP_REVISION"
else
    fail "CoolProp revision not found: $COOLPROP_REVISION"
fi

printf 'Checking out CoolProp revision %s (%s)\n' \
    "$COOLPROP_REVISION" "$TARGET_COMMIT"
git -C "$COOLPROP_SOURCE_DIR" checkout --detach "$TARGET_COMMIT" \
    || fail "failed to check out CoolProp revision $COOLPROP_REVISION"

printf 'Updating CoolProp submodules recursively\n'
git -C "$COOLPROP_SOURCE_DIR" submodule sync --recursive \
    || fail "failed to synchronize CoolProp submodule URLs"
git -C "$COOLPROP_SOURCE_DIR" submodule update --init --recursive \
    || fail "failed to update CoolProp submodules"

TEMP_DIR=
UPDATED_LUA=
cleanup()
{
    if [ -n "${UPDATED_LUA:-}" ] && [ -e "$UPDATED_LUA" ]; then
        rm -f "$UPDATED_LUA"
    fi
    if [ -n "${TEMP_DIR:-}" ] && [ -d "$TEMP_DIR" ]; then
        rm -rf "$TEMP_DIR"
    fi
}
trap cleanup 0
trap 'exit 1' HUP INT TERM

TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/luacoolprop-update.XXXXXX") \
    || fail "could not create a temporary directory"
UPDATED_LUA=$(mktemp "$PROJECT_DIR/.luacoolprop.lua.XXXXXX") \
    || fail "could not create a temporary Lua file"

printf 'Rebuilding the CoolProp shared library\n'
BUILD_TRANSCRIPT=$TEMP_DIR/build-coolprop.log
if "$SCRIPT_DIR/build-coolprop-macos.sh" >"$BUILD_TRANSCRIPT" 2>&1; then
    cat "$BUILD_TRANSCRIPT"
else
    cat "$BUILD_TRANSCRIPT" >&2
    fail "CoolProp shared-library rebuild failed"
fi

# build-coolprop-macos.sh intentionally prints the absolute dylib path last.
COOLPROP_LIBRARY=$(tail -n 1 "$BUILD_TRANSCRIPT")
[ "${COOLPROP_LIBRARY##*/}" = libCoolProp.dylib ] \
    || fail "the build script did not report libCoolProp.dylib"
[ -f "$COOLPROP_LIBRARY" ] \
    || fail "rebuilt CoolProp library not found: $COOLPROP_LIBRARY"

if [ -f "$COOLPROP_SOURCE_DIR/include/CoolProp/CoolPropLib.h" ]; then
    COOLPROP_HEADER=$COOLPROP_SOURCE_DIR/include/CoolProp/CoolPropLib.h
elif [ -f "$COOLPROP_SOURCE_DIR/include/CoolPropLib.h" ]; then
    COOLPROP_HEADER=$COOLPROP_SOURCE_DIR/include/CoolPropLib.h
else
    fail "CoolPropLib.h was not found in the selected CoolProp revision"
fi

printf 'Generating FFI declarations from %s\n' "$COOLPROP_HEADER"
PREPROCESSED_HEADER=$TEMP_DIR/CoolPropLib.preprocessed.h
FFI_DECLARATIONS=$TEMP_DIR/CoolPropLib.cdef
cc -E -P -x c -include stdbool.h "$COOLPROP_HEADER" \
    >"$PREPROCESSED_HEADER" \
    || fail "failed to preprocess CoolPropLib.h"

# The public header preprocesses to C declarations. Collapse each declaration
# onto one line so changes in header formatting do not create noisy Lua diffs.
awk '
    NF {
        line = $0
        gsub(/[[:space:]]+/, " ", line)
        sub(/^ /, "", line)
        sub(/ $/, "", line)
        if (declaration == "") declaration = line
        else declaration = declaration " " line
        if (line ~ /;$/) {
            print " " declaration
            declaration = ""
        }
    }
    END {
        if (declaration != "") print " " declaration
    }
' "$PREPROCESSED_HEADER" >"$FFI_DECLARATIONS" \
    || fail "failed to normalize the CoolProp C declarations"

[ -s "$FFI_DECLARATIONS" ] || fail "CoolPropLib.h yielded no declarations"
grep -q 'PropsSI' "$FFI_DECLARATIONS" \
    || fail "generated declarations do not contain PropsSI"
grep -q 'get_global_param_string' "$FFI_DECLARATIONS" \
    || fail "generated declarations do not contain get_global_param_string"

# Replace only the explicitly marked generated block. Exactly one start and end
# marker are required, preventing accidental edits to thermodynamic Lua code.
awk '
    FNR == NR {
        declarations = declarations $0 "\n"
        next
    }
    /-- BEGIN GENERATED COOLPROP FFI DECLARATIONS/ {
        starts++
        print
        print "ffi.cdef[["
        printf "%s", declarations
        print "]]"
        replacing = 1
        next
    }
    /-- END GENERATED COOLPROP FFI DECLARATIONS/ {
        ends++
        replacing = 0
        print
        next
    }
    !replacing { print }
    END {
        if (starts != 1 || ends != 1 || replacing) exit 42
    }
' "$FFI_DECLARATIONS" "$LUACOOLPROP_FILE" >"$UPDATED_LUA" \
    || fail "could not replace the generated FFI declaration block"
chmod 644 "$UPDATED_LUA" || fail "could not set permissions on the updated Lua file"

# Load the candidate module with texlua before replacing the working copy. This
# checks the generated ffi.cdef syntax and queries the rebuilt library itself.
VERSION_SCRIPT=$TEMP_DIR/print-coolprop-version.lua
cat >"$VERSION_SCRIPT" <<'LUA'
local module_path = assert(arg[1], "missing luacoolprop.lua path")
local chunk = assert(loadfile(module_path))
local coolprop = assert(chunk())
local version = coolprop.version()
assert(type(version) == "string" and version ~= "", "CoolProp returned no version")
io.write("CoolProp version (texlua via luacoolprop.lua): ", version, "\n")
LUA

VERSION_OUTPUT=$TEMP_DIR/coolprop-version.txt
if LUACOOLPROP_LIB="$COOLPROP_LIBRARY" \
    texlua "$VERSION_SCRIPT" "$UPDATED_LUA" >"$VERSION_OUTPUT"; then
    :
else
    fail "texlua could not load the rebuilt library through luacoolprop.lua"
fi

if cmp -s "$UPDATED_LUA" "$LUACOOLPROP_FILE"; then
    printf 'The FFI declarations are already up to date.\n'
else
    mv -f "$UPDATED_LUA" "$LUACOOLPROP_FILE" \
        || fail "could not update luacoolprop.lua"
    UPDATED_LUA=
    printf 'Updated the generated FFI declarations in luacoolprop.lua.\n'
fi

cat "$VERSION_OUTPUT"
