#!/usr/bin/env bash
# ============================================================================
# install-driver.sh <vendor>
# ----------------------------------------------------------------------------
# Idempotent HOST installer for capture-card runtime drivers / SDKs.
#
# Runs ON the cluster node's HOST kernel. The node-agent invokes it inside a
# one-shot PRIVILEGED ubuntu container that bind-mounts this repo plus the host
# paths needed to affect the host kernel (/lib/modules, /usr/src, /boot, /dev,
# and the host apt/dpkg via the mounted root). dkms / modprobe / ldconfig
# therefore operate against the running host kernel.
#
# Reads the proprietary vendor file(s) from  sdk/<vendor>/  (in this repo).
# NO binaries are committed — if the expected file is missing the script exits
# non-zero with a clear message telling the operator what to drop in.
#
# Vendors (ALLOWLIST — nothing else is accepted):
#   blackmagic   Desktop Video .deb           (DKMS kernel module)
#   aja          NTV2 driver source/SDK       (built kernel module)
#   deltacast    VideoMaster installer        (kernel module)
#   ndi          redistributable runtime libs (user-space only, no module)
#
# Exit codes:
#   0  installed (or already present / up to date)
#   2  bad usage / unknown vendor
#   3  expected vendor file missing in sdk/<vendor>/
#   4  missing kernel headers (cannot build DKMS / module)
#   5  build / install / module-load failure
#
# `bash -n` must pass. set -euo pipefail with `|| true` guarding every probe.
# ============================================================================
set -euo pipefail

# ---------------------------------------------------------------------------
# Resolve our own repo dir (deploy/ -> repo root), regardless of CWD.
# ---------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
REPO_DIR="$(cd "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd)"
SDK_ROOT="$REPO_DIR/sdk"

VENDOR="${1:-}"
KVER="$(uname -r 2>/dev/null || echo unknown)"
REBOOT_REQUIRED=0

log()  { echo "[install-driver] $*"; }
warn() { echo "[install-driver] WARN: $*" >&2; }
die()  { echo "[install-driver] ERROR: $*" >&2; exit "${2:-5}"; }

usage() {
  echo "Usage: install-driver.sh <blackmagic|aja|deltacast|ndi>" >&2
  exit 2
}

[ -n "$VENDOR" ] || usage
case "$VENDOR" in
  blackmagic|aja|deltacast|ndi) : ;;
  *) echo "[install-driver] ERROR: unknown vendor '$VENDOR' (allowed: blackmagic aja deltacast ndi)" >&2; exit 2 ;;
esac

VENDOR_DIR="$SDK_ROOT/$VENDOR"
log "vendor=$VENDOR  kernel=$KVER  repo=$REPO_DIR"
log "reading vendor files from $VENDOR_DIR"
[ -d "$VENDOR_DIR" ] || die "vendor dir $VENDOR_DIR does not exist (repo not mounted?)" 3

# Pick the newest file matching a glob; echo its path or empty.
newest_match() {
  # shellcheck disable=SC2012
  ls -1t $1 2>/dev/null | head -n1 || true
}

ensure_headers() {
  if [ -d "/lib/modules/$KVER/build" ] || dpkg -s "linux-headers-$KVER" >/dev/null 2>&1; then
    log "kernel headers for $KVER present"
    return 0
  fi
  log "installing linux-headers-$KVER ..."
  apt-get update -y >/dev/null 2>&1 || true
  if ! apt-get install -y "linux-headers-$KVER" >/dev/null 2>&1; then
    # Fall back to the generic meta-package; still may not match a custom kernel.
    apt-get install -y linux-headers-generic >/dev/null 2>&1 || true
  fi
  if [ -d "/lib/modules/$KVER/build" ] || dpkg -s "linux-headers-$KVER" >/dev/null 2>&1; then
    log "kernel headers ready"
    return 0
  fi
  die "kernel headers for $KVER unavailable — cannot build module. Install linux-headers-$KVER on the host." 4
}

# ===========================================================================
# blackmagic — Desktop Video .deb (DKMS) + modprobe + restart desktopvideo
# ===========================================================================
install_blackmagic() {
  # Detect existing state first (idempotent skip).
  if lsmod 2>/dev/null | grep -q '^blackmagic' && [ -e /dev/blackmagic ]; then
    log "blackmagic module loaded and /dev/blackmagic present — already installed"
    if command -v dkms >/dev/null 2>&1; then dkms status 2>/dev/null | grep -i blackmagic || true; fi
    return 0
  fi

  local deb
  deb="$(newest_match "$VENDOR_DIR/desktopvideo_*_amd64.deb")"
  [ -n "$deb" ] && [ -f "$deb" ] || die \
    "no desktopvideo_*_amd64.deb in $VENDOR_DIR — download Desktop Video for Ubuntu 22.04 and drop the .deb there (see sdk/blackmagic/README.md)." 3
  log "using package: $(basename "$deb")"

  ensure_headers

  log "apt-get install (DKMS build) ..."
  apt-get update -y >/dev/null 2>&1 || true
  apt-get install -y "$deb" || die "apt-get install of $(basename "$deb") failed (DKMS build?)" 5

  log "depmod + modprobe blackmagic ..."
  depmod -a "$KVER" 2>/dev/null || true
  if ! modprobe blackmagic 2>/dev/null; then
    warn "modprobe blackmagic failed — a reboot may be required for the DKMS module to bind"
    REBOOT_REQUIRED=1
  fi

  # Restart the DesktopVideoHelper daemon if present.
  if command -v systemctl >/dev/null 2>&1; then
    systemctl restart desktopvideo 2>/dev/null \
      || systemctl restart DesktopVideoHelper 2>/dev/null || true
  fi

  if lsmod 2>/dev/null | grep -q '^blackmagic' || [ -e /dev/blackmagic ]; then
    log "blackmagic installed (module loaded / device present)"
  else
    warn "blackmagic installed but module not yet loaded — reboot required"
    REBOOT_REQUIRED=1
  fi
}

# ===========================================================================
# aja — build ntv2 kernel module from source archive + modprobe
# ===========================================================================
install_aja() {
  if lsmod 2>/dev/null | grep -q 'ajantv2'; then
    log "ajantv2 module already loaded — already installed"
    return 0
  fi

  local src
  src="$(newest_match "$VENDOR_DIR/ntv2sdk*.zip")"
  [ -n "$src" ] || src="$(newest_match "$VENDOR_DIR/libajantv2*.tar.gz")"
  [ -n "$src" ] && [ -f "$src" ] || die \
    "no ntv2sdk*.zip / libajantv2*.tar.gz in $VENDOR_DIR — download the AJA NTV2 Linux SDK and drop it there (see sdk/aja/README.md)." 3
  log "using source: $(basename "$src")"

  ensure_headers
  apt-get install -y build-essential unzip >/dev/null 2>&1 || true

  local work; work="$(mktemp -d)"
  log "extracting into $work ..."
  case "$src" in
    *.zip)    unzip -q -o "$src" -d "$work" || die "failed to unzip $(basename "$src")" 5 ;;
    *.tar.gz) tar -xzf "$src" -C "$work"    || die "failed to untar $(basename "$src")" 5 ;;
  esac

  # Find the linux driver dir (contains a Makefile producing ajantv2.ko).
  local drvdir
  drvdir="$(dirname "$(find "$work" -type d -path '*driver/linux' -print -quit 2>/dev/null || true)/." 2>/dev/null)"
  [ -d "$drvdir" ] || drvdir="$(dirname "$(find "$work" -name 'ajantv2.c' -print -quit 2>/dev/null || true)" 2>/dev/null)"
  [ -n "$drvdir" ] && [ -d "$drvdir" ] || die "could not locate AJA driver/linux source inside the archive" 5
  log "building module in $drvdir ..."
  make -C "$drvdir" >/dev/null 2>&1 || die "AJA module build failed" 5

  local ko
  ko="$(find "$drvdir" -name 'ajantv2.ko' -print -quit 2>/dev/null || true)"
  if [ -n "$ko" ]; then
    install -D -m 0644 "$ko" "/lib/modules/$KVER/extra/ajantv2.ko" 2>/dev/null || true
    depmod -a "$KVER" 2>/dev/null || true
  fi
  if ! modprobe ajantv2 2>/dev/null; then
    # Fall back to the SDK's own load script if shipped.
    local loader; loader="$(find "$work" -name 'load_ajantv2' -print -quit 2>/dev/null || true)"
    if [ -n "$loader" ]; then bash "$loader" 2>/dev/null || true; fi
  fi

  rm -rf "$work" 2>/dev/null || true

  if lsmod 2>/dev/null | grep -q 'ajantv2'; then
    log "ajantv2 installed and loaded"
  else
    warn "ajantv2 built but not loaded — a reboot may be required (old in-tree module wedged?)"
    REBOOT_REQUIRED=1
  fi
}

# ===========================================================================
# deltacast — VideoMaster installer + module load
# ===========================================================================
install_deltacast() {
  if lsmod 2>/dev/null | grep -q 'videomaster' || ls /dev/deltacast* >/dev/null 2>&1; then
    log "Deltacast module loaded / device present — already installed"
    install_deltacast_udev_rule
    return 0
  fi

  local pkg
  pkg="$(newest_match "$VENDOR_DIR/VideoMaster*.run")"
  [ -n "$pkg" ] || pkg="$(newest_match "$VENDOR_DIR/VideoMaster*.tar.gz")"
  [ -n "$pkg" ] && [ -f "$pkg" ] || die \
    "no VideoMaster*.run / VideoMaster*.tar.gz in $VENDOR_DIR — obtain the Deltacast VideoMaster Linux installer and drop it there (see sdk/deltacast/README.md)." 3
  log "using installer: $(basename "$pkg")"

  ensure_headers
  apt-get install -y build-essential dkms >/dev/null 2>&1 || true

  local work; work="$(mktemp -d)"
  case "$pkg" in
    *.run)
      log "extracting self-extractor ..."
      chmod +x "$pkg" 2>/dev/null || true
      "$pkg" --noexec --target "$work" >/dev/null 2>&1 \
        || cp "$pkg" "$work/installer.run"
      ;;
    *.tar.gz)
      tar -xzf "$pkg" -C "$work" || die "failed to untar $(basename "$pkg")" 5
      ;;
  esac

  local installer
  installer="$(find "$work" -name 'install.sh' -print -quit 2>/dev/null || true)"
  [ -n "$installer" ] || installer="$(find "$work" -name 'installer.run' -print -quit 2>/dev/null || true)"
  [ -n "$installer" ] && [ -f "$installer" ] || die "Deltacast installer (install.sh) not found inside the package" 5
  log "running vendor installer: $(basename "$installer") ..."
  chmod +x "$installer" 2>/dev/null || true
  ( cd "$(dirname "$installer")" && bash "$installer" ) || die "Deltacast VideoMaster installer failed" 5

  depmod -a "$KVER" 2>/dev/null || true
  modprobe videomasterhd 2>/dev/null || modprobe videomaster 2>/dev/null || true

  rm -rf "$work" 2>/dev/null || true

  if lsmod 2>/dev/null | grep -q 'videomaster' || ls /dev/deltacast* >/dev/null 2>&1; then
    log "Deltacast VideoMaster installed"
  fi

  # Install our own udev rule that creates 8 /dev/deltacast symlinks (ports 0-7)
  # pointing at the single real device node. Kept separate from the SDK's own
  # rule so a driver reinstall won't clobber it.
  install_deltacast_udev_rule

  # First-time VideoMaster installs lay down udev rules + firmware that need a reboot.
  warn "Deltacast: a REBOOT is recommended after a first-time VideoMaster install (udev + firmware)"
  REBOOT_REQUIRED=1
}

# Copy the repo's 99-wild-dragon-deltacast.rules into /etc/udev/rules.d/ and
# reload. Idempotent. Creates /dev/deltacast0..7 -> /dev/delta-x3700 so the
# node-agent advertises all 8 RX channels.
install_deltacast_udev_rule() {
  local rule_src="$REPO_DIR/deploy/udev/99-wild-dragon-deltacast.rules"
  local rule_dst="/etc/udev/rules.d/99-wild-dragon-deltacast.rules"
  if [ ! -f "$rule_src" ]; then
    warn "Deltacast: udev rule $rule_src not found in repo — skipping symlink rule install"
    return 0
  fi
  if [ -f "$rule_dst" ] && cmp -s "$rule_src" "$rule_dst"; then
    log "Deltacast: udev rule already up to date at $rule_dst"
  else
    log "installing Deltacast udev rule -> $rule_dst"
    install -D -m 0644 "$rule_src" "$rule_dst" 2>/dev/null \
      || { warn "Deltacast: failed to install udev rule (continuing)"; return 0; }
    udevadm control --reload-rules 2>/dev/null || true
    udevadm trigger --action=add /dev/delta-x3700 2>/dev/null || true
  fi
}

# ===========================================================================
# ndi — copy redistributable runtime libs to /usr/local/lib + ldconfig
# ===========================================================================
install_ndi() {
  local target="/opt/ndi-lib"
  local found=0
  # shellcheck disable=SC2231
  for f in "$VENDOR_DIR"/libndi*.so*; do
    [ -e "$f" ] || continue
    found=1
    break
  done
  [ "$found" = 1 ] || die \
    "no libndi*.so* in $VENDOR_DIR — drop the NDI runtime redistributable libs there (see sdk/ndi/README.md)." 3

  log "copying NDI runtime libs to $target ..."
  mkdir -p "$target"
  cp -av "$VENDOR_DIR"/libndi*.so* "$target"/ 2>/dev/null || die "failed copying NDI libs" 5

  # Recreate the libndi.so dev symlink if only versioned libs were shipped.
  if [ ! -e "$target/libndi.so" ]; then
    local versioned
    versioned="$(newest_match "$target/libndi.so.*")"
    if [ -n "$versioned" ]; then
      ln -sf "$(basename "$versioned")" "$target/libndi.so" 2>/dev/null || true
    fi
  fi

  echo "$target" > /etc/ld.so.conf.d/ndi.conf
  ldconfig 2>/dev/null || true

  if ldconfig -p 2>/dev/null | grep -q 'libndi'; then
    log "NDI runtime registered with the dynamic linker"
  else
    die "NDI libs copied but ldconfig did not resolve libndi" 5
  fi
  log "NDI: no kernel module and no reboot required."
  log "NDI: restart any process that already loaded an older libndi to pick up the new version."
}

# ---------------------------------------------------------------------------
case "$VENDOR" in
  blackmagic) install_blackmagic ;;
  aja)        install_aja ;;
  deltacast)  install_deltacast ;;
  ndi)        install_ndi ;;
esac

if [ "$REBOOT_REQUIRED" = 1 ]; then
  log "RESULT: $VENDOR install completed — REBOOT REQUIRED"
  echo "[install-driver] REBOOT_REQUIRED=1"
else
  log "RESULT: $VENDOR install completed — no reboot required"
  echo "[install-driver] REBOOT_REQUIRED=0"
fi
exit 0
