#!/bin/bash
# Routinewheel.app's executable when there's no native launcher (launcher.m / launcher.c are preferred: build_app.sh on a
# Mac with a compiler). Python runs as a child (not exec), so macOS counts it as part of Routinewheel.app.
#
# Python: the app's own (Contents/Resources/python, from build_app.sh). The website zip (make_mac_zip.py) has none: the
# first start downloads the same pinned standalone Python as build_app.sh, checks its SHA-256 and keeps it in
# ~/Library/Application Support/Routinewheel Python (no admin rights; curl doesn't mark it quarantined). Not in the data
# folder, so a backup of your macros doesn't carry 60 MB of Python. Written for macOS's own bash 3.2.
C="$(cd "$(dirname "$0")/.." && pwd)"
APP="$C/Resources/Routinewheel/app/macro_app.py"

# the same Python as build_app.sh (python-build-standalone "install_only"); make_mac_zip.py checks the two agree
PBS_TAG=20241016
PY_VERSION=3.12.7
SHA_ARM64=4c18852bf9c1a11b56f21bcf0df1946f7e98ee43e9e4c0c5374b2b3765cf9508
SHA_X86_64=60c5271e7edc3c2ab47440b7abf4ed50fbc693880b474f74f05768f5b657045a

say_problem() {   # a plain message box (no window of our own yet); also in the log beside the runtime
  echo "Routinewheel: $1" >&2
  /usr/bin/osascript -e 'on run argv' -e 'display dialog (item 1 of argv) buttons {"OK"} default button 1 with title "Routinewheel" with icon caution' \
    -e 'end run' "$1" >/dev/null 2>&1
}

get_python() {   # prints the path of a checked, ready Python, or says what went wrong and fails
  local arch sha base rt tmp tgz got note
  if [ "$(/usr/sbin/sysctl -n hw.optional.arm64 2>/dev/null)" = 1 ]; then   # Apple silicon (also under Rosetta)
    arch=aarch64; sha=$SHA_ARM64
  else
    arch=x86_64; sha=$SHA_X86_64
  fi
  base="$HOME/Library/Application Support/Routinewheel Python"
  rt="$base/cpython-$PY_VERSION+$PBS_TAG-$arch"
  if [ -x "$rt/python/bin/python3" ] && [ -f "$rt/ok" ]; then echo "$rt/python/bin/python3"; return 0; fi

  mkdir -p "$base" || { say_problem "Couldn't make the folder $base."; return 1; }
  tmp="$(mktemp -d "$base/.download.XXXXXX")" || { say_problem "Couldn't make a folder in $base."; return 1; }
  tgz="$tmp/python.tar.gz"
  # a note while it downloads (about 16 MB, once); closed when done
  /usr/bin/osascript -e 'display dialog "Getting Routinewheel ready. The first time, it downloads about 16 MB (Python, the language Routinewheel is written in). This takes a minute." buttons {"OK"} default button 1 with title "Routinewheel" giving up after 120' >/dev/null 2>&1 &
  note=$!
  if ! /usr/bin/curl -fsSL --retry 3 --connect-timeout 20 -o "$tgz" \
      "https://github.com/astral-sh/python-build-standalone/releases/download/$PBS_TAG/cpython-$PY_VERSION+$PBS_TAG-$arch-apple-darwin-install_only.tar.gz"; then
    kill "$note" 2>/dev/null; rm -rf "$tmp"
    say_problem "Couldn't download the parts Routinewheel needs (about 16 MB from github.com). Check the internet connection and open Routinewheel again."
    return 1
  fi
  got="$(/usr/bin/shasum -a 256 "$tgz" | cut -d ' ' -f 1)"
  if [ "$got" != "$sha" ]; then
    kill "$note" 2>/dev/null; rm -rf "$tmp"
    say_problem "The downloaded file wasn't the expected one (its SHA-256 check failed), so it wasn't used. Open Routinewheel again to retry."
    return 1
  fi
  if ! /usr/bin/tar -xzf "$tgz" -C "$tmp" || ! "$tmp/python/bin/python3" -c "import ctypes, json, plistlib, http.server, ssl" >/dev/null 2>&1; then
    kill "$note" 2>/dev/null; rm -rf "$tmp"
    say_problem "Couldn't set up Python for Routinewheel. Open Routinewheel again to retry."
    return 1
  fi
  rm -f "$tgz"
  /usr/bin/xattr -dr com.apple.quarantine "$tmp" 2>/dev/null
  touch "$tmp/ok"
  rm -rf "$rt"
  mv "$tmp" "$rt" || { kill "$note" 2>/dev/null; rm -rf "$tmp"; say_problem "Couldn't finish setting up in $base."; return 1; }
  for old in "$base"/cpython-* "$base"/.download.*; do   # an older Python from a previous version, or a broken download
    [ -e "$old" ] && [ "$old" != "$rt" ] && rm -rf "$old"
  done
  kill "$note" 2>/dev/null
  echo "$rt/python/bin/python3"
}

PY="$C/Resources/python/bin/python3"
if [ ! -x "$PY" ]; then
  PY="$(get_python)" || exit 1
  # compiled .py files go beside that Python, not into the app (which may be read-only, or signed later)
  export PYTHONPYCACHEPREFIX="$(dirname "$(dirname "$(dirname "$PY")")")/pycache"
fi
args=()
for a in "$@"; do case "$a" in -psn_*) ;; *) args+=("$a");; esac; done
"$PY" -s "$APP" ${args[@]+"${args[@]}"} &
child=$!
trap 'kill -TERM $child 2>/dev/null' TERM INT HUP QUIT
wait "$child"; rc=$?
if kill -0 "$child" 2>/dev/null; then wait "$child"; rc=$?; fi   # (a passed-on signal ends the first wait early)
exit $rc
