#!/usr/bin/env bash
# Reproducible build of the GhostPDL WebAssembly conversion engines.
#
# Usage:  apps/pcl-to-pdf/engine/build.sh
#
# Environment overrides (all optional):
#   GHOSTPDL_TARBALL  path to an already-downloaded ghostpdl-10.08.0.tar.xz
#                     (its sha256 is still verified)
#   ENGINE_TARGETS    space-separated executables to build (default: "gpcl6 gs")
#   OPT_LEVEL         emscripten optimisation flag (default: -Oz)
#   JOBS              parallel make jobs (default: number of CPUs)
#   REUSE_TREE=1      skip re-extracting/configuring when the tree exists
#                     (development convenience; release builds should not set it)
set -euo pipefail

GS_VERSION="10.08.0"
GS_URL="https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs10080/ghostpdl-${GS_VERSION}.tar.xz"
GS_SHA256="94943e3c4adb7115d132b3c28b913672f2e62e6a9d802f7e754e716b1634174a"
EXPECTED_EMCC_MAJOR="6"
# mkromfs embeds a build timestamp; pin it (GhostPDL 10.08.0 release date) so builds are reproducible.
export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-1788825600}"

ENGINE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_DIR="$(dirname "${ENGINE_DIR}")"
BUILD_ROOT="${ENGINE_DIR}/.build"
SRC_DIR="${BUILD_ROOT}/ghostpdl-${GS_VERSION}"
TARBALL="${GHOSTPDL_TARBALL:-${BUILD_ROOT}/ghostpdl-${GS_VERSION}.tar.xz}"
OUT_DIR="${APP_DIR}/public/engine"
ENGINE_TARGETS="${ENGINE_TARGETS:-gpcl6 gs}"
OPT_LEVEL="${OPT_LEVEL:--Oz}"
JOBS="${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)}"

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

sha256_of() {
  if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
  else shasum -a 256 "$1" | cut -d' ' -f1; fi
}

for tool in emcc emar emranlib make cc curl tar; do
  command -v "${tool}" >/dev/null 2>&1 || die "required tool not found: ${tool}"
done
EMCC_VERSION="$(emcc --version | head -n1)"
case "${EMCC_VERSION}" in
  *" ${EXPECTED_EMCC_MAJOR}."*) ;;
  *) echo "warning: built and tested with emcc ${EXPECTED_EMCC_MAJOR}.x, found: ${EMCC_VERSION}" >&2 ;;
esac

mkdir -p "${BUILD_ROOT}" "${OUT_DIR}"

# ---------------------------------------------------------------- source
if [[ ! -f "${TARBALL}" ]]; then
  log "Downloading ${GS_URL}"
  curl -fL --retry 3 -o "${TARBALL}.part" "${GS_URL}"
  mv "${TARBALL}.part" "${TARBALL}"
fi
log "Verifying sha256 of $(basename "${TARBALL}")"
actual_sha="$(sha256_of "${TARBALL}")"
[[ "${actual_sha}" == "${GS_SHA256}" ]] || die "sha256 mismatch: expected ${GS_SHA256}, got ${actual_sha}"

if [[ "${REUSE_TREE:-0}" != "1" || ! -f "${SRC_DIR}/Makefile" ]]; then
  log "Extracting into ${SRC_DIR}"
  rm -rf "${SRC_DIR}"
  tar -xJf "${TARBALL}" -C "${BUILD_ROOT}"

  shopt -s nullglob
  for patch in "${ENGINE_DIR}"/patches/*.patch; do
    log "Applying $(basename "${patch}")"
    patch -d "${SRC_DIR}" -p1 --forward --batch < "${patch}"
  done
  shopt -u nullglob

  # wasm32 is an ILP32 little-endian target with x86-like alignment, so the
  # 32-bit MSVC arch header (pre-generated by genarch) describes it exactly.
  # Cross builds cannot run genarch for the target, hence the explicit header.
  ARCH_H="${SRC_DIR}/arch/windows-x86-msvc.h"

  log "Configuring (host tools with native cc, target with emcc)"
  (
    cd "${SRC_DIR}"
    # CCAUX builds genarch/mkromfs/echogs/genconf for the build machine.
    ./configure \
      --host=wasm32-unknown-emscripten \
      --build="$(./config.guess)" \
      --with-arch_h="${ARCH_H}" \
      CC=emcc AR=emar RANLIB=emranlib CCAUX=cc CFLAGSAUX= \
      CFLAGS="${OPT_LEVEL} -g0" \
      --with-pcl=gpcl6 \
      --with-pdf=no \
      --with-xps=no \
      --with-gpdl=no \
      --with-drivers=pdfwrite \
      --disable-threading \
      --disable-contrib \
      --disable-cups \
      --disable-dbus \
      --disable-fontconfig \
      --disable-gtk \
      --disable-openjpeg \
      --disable-sse2 \
      --disable-neon \
      --without-x \
      --without-libtiff \
      --without-libpaper \
      --without-libidn \
      --without-pdftoraster \
      --without-ijs \
      --without-jbig2dec \
      --without-tesseract \
      --without-urf \
      --without-so \
      --without-cal \
      --with-libiconv=no \
      --enable-mkromfs-quiet
  )
fi

# ---------------------------------------------------------------- build
# Link flags are passed at make time (XLDFLAGS) so configure's own link tests
# are not affected by them.
EM_LDFLAGS=(
  "${OPT_LEVEL}" -g0
  -sMODULARIZE=1
  -sEXPORT_ES6=1
  -sENVIRONMENT=web,worker,node
  -sINVOKE_RUN=0
  -sEXIT_RUNTIME=1
  -sFORCE_FILESYSTEM=1
  "-sEXPORTED_RUNTIME_METHODS=FS,callMain"
  -sALLOW_MEMORY_GROWTH=1
  -sINITIAL_MEMORY=67108864
  -sMAXIMUM_MEMORY=2147483648
  -sSTACK_SIZE=8388608
)

for target in ${ENGINE_TARGETS}; do
  log "Building ${target}"
  make -C "${SRC_DIR}" -j"${JOBS}" \
    XE=".js" \
    XCFLAGS="${OPT_LEVEL} -g0" \
    XLDFLAGS="${EM_LDFLAGS[*]}" \
    "${target}"
done

# ---------------------------------------------------------------- install
log "Installing artifacts into ${OUT_DIR}"
rm -f "${OUT_DIR}"/*.js "${OUT_DIR}"/*.wasm "${OUT_DIR}/SHA256SUMS"
for target in ${ENGINE_TARGETS}; do
  cp "${SRC_DIR}/bin/${target}.js" "${OUT_DIR}/${target}.js"
  cp "${SRC_DIR}/bin/${target}.wasm" "${OUT_DIR}/${target}.wasm"
done

max_bytes=$((20 * 1024 * 1024))
(
  cd "${OUT_DIR}"
  : > SHA256SUMS
  for f in *.js *.wasm; do
    size=$(wc -c < "${f}" | tr -d ' ')
    [[ "${size}" -le "${max_bytes}" ]] || die "${f} is ${size} bytes, over the 20 MiB per-file limit"
    printf '%s  %s\n' "$(sha256_of "${f}")" "${f}" >> SHA256SUMS
  done
  cat SHA256SUMS
)

log "Sizes (raw / gzip -9 / brotli -q 11)"
for f in "${OUT_DIR}"/*.js "${OUT_DIR}"/*.wasm; do
  raw=$(wc -c < "${f}" | tr -d ' ')
  gz=$(gzip -9 -c "${f}" | wc -c | tr -d ' ')
  if command -v brotli >/dev/null 2>&1; then br=$(brotli -q 11 -c "${f}" | wc -c | tr -d ' '); else br="n/a"; fi
  printf '%-14s %10s %10s %10s\n' "$(basename "${f}")" "${raw}" "${gz}" "${br}"
done
