#!/bin/bash
set -euo pipefail

# fix-freefem-rpaths.sh
#
# 使い方:
#   bash fix-freefem-rpaths.sh /Applications/FreeFem++.app
#   bash fix-freefem-rpaths.sh /Applications/FreeFem++.app --dry-run
#
# 目的:
#   FreeFem++.app 内に埋め込まれた
#     /opt/homebrew/opt/gcc/lib/gcc/current/libgfortran.5.dylib
#     /opt/homebrew/opt/gcc/lib/gcc/current/libquadmath.0.dylib
#   のような絶対参照を、app 内の gnu/ 以下への相対参照に直す。

APP="${1:-/Applications/FreeFem++.app}"
DRY_RUN=0
if [[ "${2:-}" == "--dry-run" ]] || [[ "${1:-}" == "--dry-run" ]]; then
  DRY_RUN=1
fi

if [[ ! -d "$APP" ]]; then
  echo "error: app not found: $APP" >&2
  exit 1
fi

ROOT="$APP/Contents"

if [[ ! -d "$ROOT" ]]; then
  echo "error: not an app bundle? missing $ROOT" >&2
  exit 1
fi

# FreeFem のバージョン付き本体ディレクトリを推定
FFROOT="$(find "$ROOT" -maxdepth 1 -type d -name 'ff-*' | head -n 1 || true)"
if [[ -z "$FFROOT" ]]; then
  echo "error: could not find ff-* directory under $ROOT" >&2
  exit 1
fi

GNUDIR="$FFROOT/gnu"
if [[ ! -d "$GNUDIR" ]]; then
  echo "error: gnu dir not found: $GNUDIR" >&2
  exit 1
fi

LIBG="$(find "$GNUDIR" -maxdepth 1 -type f -name 'libgfortran.5.dylib' | head -n 1 || true)"
LIBQ="$(find "$GNUDIR" -maxdepth 1 -type f -name 'libquadmath.0.dylib' | head -n 1 || true)"

if [[ -z "$LIBG" || -z "$LIBQ" ]]; then
  echo "error: required libraries not found under $GNUDIR" >&2
  echo "  libgfortran.5.dylib = ${LIBG:-MISSING}" >&2
  echo "  libquadmath.0.dylib = ${LIBQ:-MISSING}" >&2
  exit 1
fi

# 典型的な壊れた参照候補
BROKEN_GFORTRAN=(
  "/opt/homebrew/opt/gcc/lib/gcc/current/libgfortran.5.dylib"
  "/usr/local/opt/gcc/lib/gcc/current/libgfortran.5.dylib"
)

BROKEN_QUADMATH=(
  "/opt/homebrew/opt/gcc/lib/gcc/current/libquadmath.0.dylib"
  "/usr/local/opt/gcc/lib/gcc/current/libquadmath.0.dylib"
)

echo "App      : $APP"
echo "FF root  : $FFROOT"
echo "GNU dir  : $GNUDIR"
echo "Dry-run  : $DRY_RUN"
echo

# 相対パス計算
relpath_python() {
  python3 - "$1" "$2" <<'PY'
import os, sys
target = sys.argv[1]
base   = sys.argv[2]
print(os.path.relpath(target, base))
PY
}

# Mach-O かどうか判定
is_macho() {
  file -b "$1" 2>/dev/null | grep -Eq 'Mach-O'
}

# install_name_tool 実行ラッパ
run_cmd() {
  if [[ $DRY_RUN -eq 1 ]]; then
    printf '[dry-run] '
    printf '%q ' "$@"
    printf '\n'
  else
    "$@"
  fi
}

patched_files=0
changed_refs=0

while IFS= read -r -d '' f; do
  is_macho "$f" || continue

  # otool が失敗するものは飛ばす
  if ! deps="$(otool -L "$f" 2>/dev/null)"; then
    continue
  fi

  need_patch=0
  for old in "${BROKEN_GFORTRAN[@]}" "${BROKEN_QUADMATH[@]}"; do
    if grep -Fq "$old" <<<"$deps"; then
      need_patch=1
      break
    fi
  done
  [[ $need_patch -eq 1 ]] || continue

  dir="$(dirname "$f")"

  # 実行ファイルかライブラリかで基準を分ける
  case "$f" in
    *.dylib|*.so)
      rg="$(relpath_python "$LIBG" "$dir")"
      rq="$(relpath_python "$LIBQ" "$dir")"
      new_g="@loader_path/$rg"
      new_q="@loader_path/$rq"
      ;;
    *)
      rg="$(relpath_python "$LIBG" "$dir")"
      rq="$(relpath_python "$LIBQ" "$dir")"
      new_g="@executable_path/$rg"
      new_q="@executable_path/$rq"
      ;;
  esac

  echo "patch: $f"

  local_changed=0

  for old in "${BROKEN_GFORTRAN[@]}"; do
    if grep -Fq "$old" <<<"$deps"; then
      echo "  $old"
      echo "    -> $new_g"
      run_cmd install_name_tool -change "$old" "$new_g" "$f"
      changed_refs=$((changed_refs + 1))
      local_changed=1
    fi
  done

  for old in "${BROKEN_QUADMATH[@]}"; do
    if grep -Fq "$old" <<<"$deps"; then
      echo "  $old"
      echo "    -> $new_q"
      run_cmd install_name_tool -change "$old" "$new_q" "$f"
      changed_refs=$((changed_refs + 1))
      local_changed=1
    fi
  done

  if [[ $local_changed -eq 1 ]]; then
    patched_files=$((patched_files + 1))
  fi

done < <(find "$APP" \( -type f -perm -111 -o -type f -name '*.dylib' -o -type f -name '*.so' \) -print0)

echo
echo "patched files : $patched_files"
echo "changed refs  : $changed_refs"

if [[ $DRY_RUN -eq 0 ]]; then
  echo
  echo "Re-signing app bundle (ad-hoc)..."
  run_cmd codesign --force --deep --sign - "$APP"

  echo
  echo "Verifying signature..."
  if codesign --verify --deep --strict "$APP"; then
    echo "codesign verify: OK"
  else
    echo "warning: codesign verification reported a problem" >&2
  fi
fi

echo
echo "Done."
