#!/usr/bin/env bash
# =============================================================================
# start.sh — ONE command to bring up the whole hand stack.
#
# Starts the Docker container that runs all three pieces together — the RS-485
# service (inspire_g1), the TCP->DDS bridge (hand_bridge) and the web dashboard
# on :8088 — then waits until the dashboard actually answers before telling you
# it is up.
#
#   ./start.sh              start it (builds the image the first time)
#   ./start.sh stop         stop the container
#   ./start.sh restart      stop + start
#   ./start.sh status       is it up, what is it serving, are the hands alive
#   ./start.sh logs         follow the container log
#   ./start.sh rebuild      force an image rebuild, then start
#
# RUN IT ON THE ROBOT. Run it on the workstation and it rsyncs this folder to
# the robot and runs itself there — the hands, the DDS interface and Docker are
# all on the robot, so there is nothing for it to do locally.
#
# Env: ROBOT=unitree@192.168.123.164   (override for the wifi IP)
#      PORT=8088
# =============================================================================
set -euo pipefail

HERE="$(cd "$(dirname "$0")" && pwd)"
ROBOT="${ROBOT:-unitree@192.168.123.164}"
REMOTE_DIR='~/DFX_inspire_service'
PORT="${PORT:-8088}"
IMG="${IMG:-inspire-hand:latest}"
CMD="${1:-start}"

say()  { printf '\033[36m>>\033[0m %s\n' "$*"; }
ok()   { printf '\033[32m>>\033[0m %s\n' "$*"; }
die()  { printf '\033[31m>>\033[0m %s\n' "$*" >&2; exit 1; }

# The robot is the only place this can run: it owns the hands (/dev/ttyUSB*), the
# DDS interface to the arm, and Docker. aarch64 is the reliable tell — the
# workstation is x86_64 and the image is built for arm64.
is_robot() { [ "$(uname -m)" = "aarch64" ] && command -v docker >/dev/null 2>&1; }

remote() {
  say "not the robot — deploying to $ROBOT and running there"
  command -v rsync >/dev/null || die "rsync not installed"
  # build/ is excluded: the image compiles its own binaries, and the host copy is
  # 55MB of artifacts that only the (optional) native path uses.
  rsync -az --delete --exclude='build/' --exclude='docker/.ctx/' \
        -e "ssh -o ConnectTimeout=8" "$HERE/" "$ROBOT:$REMOTE_DIR/"
  # shellcheck disable=SC2029
  ssh -o ConnectTimeout=8 -t "$ROBOT" "cd $REMOTE_DIR && ./start.sh $CMD"
  exit $?
}

have_image() { docker image inspect "$IMG" >/dev/null 2>&1; }   # (kept for status checks)

# run.sh tails the container log, which spills the dashboard's HTTP lines over this
# script's own output. Keep it unless something actually fails.
run_quiet() {
  local log="/tmp/hand_start.$$.log"
  if ! "$HERE/docker/run.sh" >"$log" 2>&1; then
    cat "$log"; rm -f "$log"; die "docker run failed"
  fi
  rm -f "$log"
}

build() {
  say "building $IMG (compiles the binaries + python DDS in-image; a few minutes)"
  "$HERE/docker/build_image.sh"
}

wait_up() {
  # Poll the dashboard rather than trusting `docker run` — the container can be up
  # while the service inside is still opening the serial ports, and reporting a URL
  # that 404s is worse than waiting three more seconds for it.
  say "waiting for the dashboard..."
  for _ in $(seq 1 40); do
    if curl -fsS "http://127.0.0.1:$PORT/api/status" >/dev/null 2>&1; then
      return 0
    fi
    sleep 1
  done
  return 1
}

report() {
  local s
  s="$(curl -fsS "http://127.0.0.1:$PORT/api/status" 2>/dev/null || true)"
  [ -n "$s" ] || { echo "   dashboard not answering on :$PORT"; return 1; }
  python3 - "$s" <<'PY' 2>/dev/null || echo "   $s"
import json, sys
d = json.loads(sys.argv[1])
print("   service=%s  bridge=%s  iface=%s  ports=%s"
      % (d.get("service"), d.get("bridge"), d.get("iface"), ", ".join(d.get("ports") or []) or "none"))
if d.get("mismatch"):
    print("   WARNING: port drift — the service opened different ttys than are live now")
PY
  local r
  r="$(curl -fsS "http://127.0.0.1:$PORT/api/rec/status" 2>/dev/null || true)"
  case "$r" in
    *'"ready":true'*)  echo "   recorder: ready (record/replay panel enabled)" ;;
    *'"ready":false'*) echo "   recorder: NOT found — rebuild with ./start.sh rebuild" ;;
  esac
  # Skip docker's own bridge addresses (172.17-19.x) — nothing outside the robot can
  # reach those, and printing them as if they were the dashboard URL wastes a click.
  for ip in $(hostname -I 2>/dev/null); do
    case "$ip" in 172.1[789].*|127.*) continue ;; esac
    echo "   http://$ip:$PORT"
  done
}

is_robot || remote

case "$CMD" in
  start|restart)
    # ALWAYS build, never "build only if the image is missing". Docker's layer cache makes
    # this a second or two when nothing changed, and seconds when only web/ changed — but
    # skipping it means an edited dashboard silently keeps running the old image, which is
    # a very expensive minute of confusion when a fix "does not work".
    build
    run_quiet
    wait_up || die "dashboard did not come up — ./start.sh logs"
    ok "up"
    report
    ;;
  rebuild)
    build
    run_quiet
    wait_up || die "dashboard did not come up — ./start.sh logs"
    ok "rebuilt and up"
    report
    ;;
  stop)
    "$HERE/docker/stop.sh"
    ;;
  status)
    docker ps --filter name=inspire-hand --format '   {{.Names}} · {{.Status}} · {{.Image}}' | grep . \
      || echo "   inspire-hand is not running"
    report || true
    ;;
  logs)
    docker logs -f --tail 60 inspire-hand
    ;;
  usbfix)
    # The card reader (05e3:0749) shares the hands' USB hub and power-cycles constantly with
    # no card in it, re-enumerating the branch and renumbering /dev/ttyUSB*. Deauthorizing
    # needs root, which this box asks a password for — hence a subcommand rather than
    # something ./start.sh does silently.
    say "installing the udev rule + deauthorizing the card reader (asks for your password)"
    sudo cp "$HERE/udev/99-inspire-hub-cardreader.rules" /etc/udev/rules.d/
    sudo udevadm control --reload
    for d in /sys/bus/usb/devices/*/; do
      [ "$(cat "$d/idVendor" 2>/dev/null)" = "05e3" ] && \
      [ "$(cat "$d/idProduct" 2>/dev/null)" = "0749" ] && {
        echo 0 | sudo tee "$d/authorized" >/dev/null
        ok "deauthorized $(basename "$d")"
      }
    done
    ls /dev/ttyUSB* 2>/dev/null | sed 's/^/   still present: /'
    ;;
  *)
    sed -n '2,26p' "$0" | sed 's/^# \{0,1\}//'
    exit 1
    ;;
esac
