#!/usr/bin/env bash
#
# Drives one fully unattended closed-loop experiment run:
#
#   1. Force-stops the sample app and clears Logcat so the cold start is
#      clean and the only ExperimentAuto: log lines are from this run.
#   2. Picks a wall-clock target start_at_wall_ms = now + APP_BOOT_PAD seconds.
#   3. Launches AppActivity via `am start` with the auto-mode intent extras
#      (model_name, start_at_wall_ms, duration_ms). The app's Compose
#      auto-driver selects the model, waits for the same wall-clock target,
#      runs the experiment buffer for duration_ms, writes the JSON, and
#      finishes the activity.
#   4. Sleeps until start_at_wall_ms locally on the Mac, then plays the
#      video in QuickTime at the requested --start-offset (seconds). Both
#      sides hit the same wall-clock instant.
#   5. Sleeps for the buffer duration, pauses QuickTime.
#   6. Diff-polls /sdcard/.../experiment_logs for the new JSON file (with a
#      10s timeout) and pulls it into experiments/<run-id>/.
#   7. Captures the ExperimentAuto Logcat lines for the manifest, writes
#      experiments/<run-id>/manifest.json with t0_wall_ms, video offset,
#      device label, model tag, and the captured log lines.
#
# Usage:
#   tools/run_auto_experiment.sh \
#       --video ~/Downloads/clip.mp4 \
#       --duration 10 \
#       --model-tag yolo11n_su_416 \
#       [--start-offset 10] \
#       [--device <serial>]
#
# After at least two runs (model A then model B against the same video,
# same --start-offset) point tools/compare_logs.py at experiments/ to
# build the comparison report.

set -euo pipefail

ADB="${ADB:-/Users/virtualintern/Library/Android/sdk/platform-tools/adb}"
PKG="com.nate.posedetection.androidApp"
ACTIVITY="$PKG/com.nate.posedetection.AppActivity"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
EXPERIMENTS_DIR="$REPO_ROOT/experiments"
REMOTE_LOGS_DIR="/sdcard/Android/data/$PKG/files/experiment_logs"
APP_BOOT_PAD_SECONDS="${APP_BOOT_PAD_SECONDS:-9}"
PULL_TIMEOUT_SECONDS="${PULL_TIMEOUT_SECONDS:-15}"

usage() {
    sed -n '3,33p' "$0"
    exit "${1:-1}"
}

VIDEO=""
DURATION=""
MODEL_TAG=""
START_OFFSET=0
DEVICE=""

while [ $# -gt 0 ]; do
    case "$1" in
        --video)        VIDEO="$2"; shift 2 ;;
        --duration)     DURATION="$2"; shift 2 ;;
        --model-tag)    MODEL_TAG="$2"; shift 2 ;;
        --start-offset) START_OFFSET="$2"; shift 2 ;;
        --device)       DEVICE="$2"; shift 2 ;;
        -h|--help)      usage 0 ;;
        *) echo "unknown arg: $1" >&2; usage ;;
    esac
done

[ -n "$VIDEO" ]     || { echo "error: --video is required" >&2; exit 2; }
[ -n "$DURATION" ]  || { echo "error: --duration is required" >&2; exit 2; }
[ -n "$MODEL_TAG" ] || { echo "error: --model-tag is required" >&2; exit 2; }

if [ ! -f "$VIDEO" ]; then
    echo "error: video file not found: $VIDEO" >&2
    exit 2
fi
VIDEO_ABS="$(cd "$(dirname "$VIDEO")" && pwd)/$(basename "$VIDEO")"

# Resolve device.
if [ -z "$DEVICE" ]; then
    CONNECTED="$("$ADB" devices | awk 'NR>1 && $2=="device" {print $1}')"
    NUM_CONNECTED="$(printf '%s\n' "$CONNECTED" | grep -c . || true)"
    if [ "$NUM_CONNECTED" -eq 0 ]; then
        echo "error: no devices connected." >&2
        exit 3
    elif [ "$NUM_CONNECTED" -gt 1 ]; then
        echo "error: multiple devices connected — pass --device <serial>." >&2
        printf '         %s\n' $CONNECTED >&2
        exit 3
    fi
    DEVICE="$CONNECTED"
    echo "==> auto-selected device: $DEVICE"
fi

now_ms() { python3 -c 'import time; print(int(time.time()*1000))'; }

DURATION_MS=$(python3 -c "print(int(float('$DURATION') * 1000))")

echo "==> auto experiment"
echo "    model_tag      = $MODEL_TAG"
echo "    duration       = ${DURATION}s ($DURATION_MS ms)"
echo "    video          = $VIDEO_ABS"
echo "    start_offset   = ${START_OFFSET}s"

# 1. Snapshot the existing logs so we can diff-pull only the new file.
LOGS_BEFORE="$("$ADB" -s "$DEVICE" shell "ls -1 $REMOTE_LOGS_DIR 2>/dev/null" \
    | tr -d '\r' | grep '\.json$' || true)"
EXISTING_COUNT=$(printf '%s\n' "$LOGS_BEFORE" | grep -c '\.json$' || true)
echo "==> $EXISTING_COUNT existing log(s) on device (will be ignored)"

# 2. Pre-open the video in QuickTime *first* and position to start_offset
#    while paused. Large 4K files can take 10-60s to index, so we do this
#    before any wall-clock timing matters. The 600s timeout covers extreme
#    cases (default AppleEvent timeout is 60s and can fire on big files).
echo "==> pre-opening video in QuickTime (large files may take a moment)..."
osascript <<APPLESCRIPT
with timeout of 600 seconds
    tell application "QuickTime Player"
        activate
        if (count of documents) > 0 then
            try
                close every document saving no
            end try
        end if
        open POSIX file "$VIDEO_ABS"
        repeat while (count of documents) is 0
            delay 0.1
        end repeat
        tell front document
            pause
            set current time to $START_OFFSET
            present
        end tell
    end tell
end timeout
APPLESCRIPT

# 3. Cold-start the app: kill any existing instance and clear Logcat.
echo "==> force-stopping app and clearing Logcat..."
"$ADB" -s "$DEVICE" shell am force-stop "$PKG" >/dev/null
"$ADB" -s "$DEVICE" logcat -c >/dev/null

# 4. Compute the wall-clock target and launch via intent with auto-mode extras.
NOW_MS=$(now_ms)
START_AT_WALL_MS=$((NOW_MS + APP_BOOT_PAD_SECONDS * 1000))
RUN_ID="${START_AT_WALL_MS}_$(printf '%s' "$MODEL_TAG" | tr -c 'A-Za-z0-9._-' '_' | sed 's/^_*//; s/_*$//')"
RUN_DIR="$EXPERIMENTS_DIR/$RUN_ID"
echo "    start_at_wall  = $START_AT_WALL_MS  (in ${APP_BOOT_PAD_SECONDS}s)"
echo "    run_dir        = $RUN_DIR"

echo "==> launching $ACTIVITY with auto-mode extras..."
"$ADB" -s "$DEVICE" shell am start -n "$ACTIVITY" \
    --ez experiment_auto true \
    --es model_name "$MODEL_TAG" \
    --el start_at_wall_ms "$START_AT_WALL_MS" \
    --el duration_ms "$DURATION_MS" >/dev/null

# 5. Wait until the wall-clock target, then play (already pre-loaded so this
#    is sub-second). t0_wall_ms is captured immediately before the play call.
WAIT_MS=$((START_AT_WALL_MS - $(now_ms)))
if [ "$WAIT_MS" -gt 0 ]; then
    python3 -c "import time; time.sleep($WAIT_MS/1000.0)"
fi

T0_WALL_MS=$(now_ms)
echo "==> playing video at offset ${START_OFFSET}s (t0_wall_ms=$T0_WALL_MS)"
osascript -e 'tell application "QuickTime Player" to play front document' >/dev/null

# 5. Sleep the buffer duration plus a small safety margin then pause.
echo "==> sleeping ${DURATION}s for buffer to fill..."
sleep "$DURATION"
osascript -e 'tell application "QuickTime Player" to pause front document' >/dev/null

# 6. Poll for the new log file with a timeout.
echo "==> polling for new log file (timeout ${PULL_TIMEOUT_SECONDS}s)..."
NEW_LOGS=""
DEADLINE=$(( $(now_ms) + PULL_TIMEOUT_SECONDS * 1000 ))
while [ "$(now_ms)" -lt "$DEADLINE" ]; do
    LOGS_AFTER="$("$ADB" -s "$DEVICE" shell "ls -1 $REMOTE_LOGS_DIR 2>/dev/null" \
        | tr -d '\r' | grep '\.json$' || true)"
    NEW_LOGS="$(comm -13 \
        <(printf '%s\n' "$LOGS_BEFORE" | sort -u) \
        <(printf '%s\n' "$LOGS_AFTER"  | sort -u) \
        | grep '\.json$' || true)"
    if [ -n "$NEW_LOGS" ]; then
        break
    fi
    sleep 0.25
done

mkdir -p "$RUN_DIR"

if [ -z "$NEW_LOGS" ]; then
    echo "error: no new log file appeared on device after ${PULL_TIMEOUT_SECONDS}s" >&2
    echo "       recent ExperimentAuto Logcat:" >&2
    "$ADB" -s "$DEVICE" logcat -d -s ExperimentAuto:I 2>/dev/null | tail -30 >&2 || true
    exit 4
fi

echo "==> pulling new log(s) into $RUN_DIR/"
while IFS= read -r f; do
    [ -z "$f" ] && continue
    "$ADB" -s "$DEVICE" pull "$REMOTE_LOGS_DIR/$f" "$RUN_DIR/" >/dev/null
    echo "    pulled $f"
done <<< "$NEW_LOGS"

# 7. Capture the ExperimentAuto log lines (for the manifest).
LOGCAT_EXCERPT="$("$ADB" -s "$DEVICE" logcat -d -s ExperimentAuto:I 2>/dev/null \
    | grep ExperimentAuto || true)"

DEVICE_MANUFACTURER="$("$ADB" -s "$DEVICE" shell getprop ro.product.manufacturer 2>/dev/null | tr -d '\r' || true)"
DEVICE_MODEL_NAME="$("$ADB" -s "$DEVICE" shell getprop ro.product.model 2>/dev/null | tr -d '\r' || true)"
DEVICE_LABEL="$DEVICE_MANUFACTURER $DEVICE_MODEL_NAME"

NEW_LOGS_JOINED="$(printf '%s\n' "$NEW_LOGS" | tr '\n' ',' | sed 's/,$//')"

RUN_ID="$RUN_ID" \
MODEL_TAG="$MODEL_TAG" \
VIDEO_ABS="$VIDEO_ABS" \
START_OFFSET="$START_OFFSET" \
DURATION="$DURATION" \
T0_WALL_MS="$T0_WALL_MS" \
START_AT_WALL_MS="$START_AT_WALL_MS" \
DEVICE="$DEVICE" \
DEVICE_LABEL="$DEVICE_LABEL" \
NEW_LOGS_JOINED="$NEW_LOGS_JOINED" \
LOGCAT_EXCERPT="$LOGCAT_EXCERPT" \
python3 -c '
import json, os
files = [f for f in os.environ["NEW_LOGS_JOINED"].split(",") if f]
logcat = [l for l in os.environ["LOGCAT_EXCERPT"].splitlines() if l.strip()]
manifest = {
    "run_id": os.environ["RUN_ID"],
    "mode": "auto",
    "model_tag": os.environ["MODEL_TAG"],
    "video_path": os.environ["VIDEO_ABS"],
    "video_start_offset_seconds": float(os.environ["START_OFFSET"]),
    "duration_seconds": float(os.environ["DURATION"]),
    "t0_wall_ms": int(os.environ["T0_WALL_MS"]),
    "start_at_wall_ms": int(os.environ["START_AT_WALL_MS"]),
    "device_serial": os.environ["DEVICE"],
    "device_label": os.environ["DEVICE_LABEL"].strip(),
    "log_files": files,
    "logcat_excerpt": logcat,
}
print(json.dumps(manifest, indent=2))
' > "$RUN_DIR/manifest.json"

echo "==> wrote $RUN_DIR/manifest.json"
echo
echo "Run dir: $RUN_DIR"
ls -la "$RUN_DIR"
