#!/usr/bin/env nix-shell
#!nix-shell -i bash -p findutils -p nix-update

NIX_FILES_TOP=/home/colin/nixos

usage() {
  echo "update: update rev/hash for one or more packages"
  echo "usage: update [options] [attr-path]"
  echo ""
  echo "options:"
  echo "- --dry-run"
  echo "- --verbose"
  echo ""
  echo "examples:"
  echo "- update nixpkgs: update only the nixpkgs input"
  echo "- update sane: update every package under the 'sane' attribute"
  echo "- update: update everything i know how to update"
  exit 1
}

warn() {
  echo "$@"
}
info() {
  echo "$@"
}
debug() {
  if [ -n "$verbose" ]; then
    echo "$@"
  fi
}

hasEffect() {
  if [ -n "$dryRun" ]; then
    echo "dry-run: skip $@"
  else
    eval "$@"
  fi
}

# usage: getPkgs outVar prefix
getPkgs() {
  local -n attrsArr="$1"
  local attrPrefix="$2"
  if [ -z "$attrPrefix" ]; then
    attrPrefix=sane
  fi

  # nix-env doesn't seem to build anything when evaluating queries,
  # but since i use Import From Derivation along paths which i also want to query,
  # then i need to ensure those derivations are available for import.
  debug "creating requisite .drv store paths"
  nix-instantiate -A nix "$NIX_FILES_TOP"
  nix-instantiate -A nixpkgs "$NIX_FILES_TOP"
  debug "querying packages to update as part of '$attrPrefix'"
  local attrs=$(nix eval --raw -f "$NIX_FILES_TOP" 'updateTargets."'"$attrPrefix"'"' --apply 'builtins.concatStringsSep " "')
  debug "got: $attrs"
  attrsArr+=($attrs)
}

updateOnePkg() {
  local attrPath="$1"

  local updateScript=$(nix eval --raw -f "$NIX_FILES_TOP" 'updateScripts."'"$attrPath"'"')
  if [ -z "$updateScript" ]; then
    warn "don't know how to update '$attrPath'"
    return
  fi

  # make sure everything needed to invoke the update script exists in-store
  local context=$(nix eval --raw -f "$NIX_FILES_TOP" 'updateScripts."'"$attrPath"'"' --apply 's: builtins.concatStringsSep " " (builtins.attrNames (builtins.getContext s))')
  for c in $context; do
    debug "realizing updateScript requisite: $context"
    nix-store --realize "$c" || true
  done

  info "updating: '$attrPath'"
  debug "$updateScript"
  "$updateScript"
}

updatePkgsInParallel() {
  debug "updating packages in parallel using xargs"
  debug "- $@"
  debug "- xargs -n 1 -P 4 $0 ${scriptFlags[*]}"
  echo "$@" | xargs -n 1 -P 4 "$0" "${scriptFlags[@]}"
}

scriptFlags=()
dryRun=
toplevelsToUpdate=()
verbose=
parseArgs() {
  while [ "$#" -ne 0 ]; do
    local arg=$1
    shift
    case "$arg" in
      (--help)
        usage
        ;;
      (--dry-run)
        scriptFlags+=(--dry-run)
        dryRun=1
        ;;
      (--verbose)
        scriptFlags+=(--verbose)
        verbose=1
        ;;
      (*)
        toplevelsToUpdate+=("$arg")
        ;;
    esac
  done
  if [ "${#toplevelsToUpdate[@]}" -eq 0 ]; then
    toplevelsToUpdate=(sane)
  fi
}

parseArgs "$@"
pkgsToUpdate=()
for t in "${toplevelsToUpdate[@]}"; do
  getPkgs pkgsToUpdate "$t"
done

case "${#pkgsToUpdate[@]}" in
  (0)
    echo "nothing to do"
    ;;
  (1)
    hasEffect updateOnePkg "${pkgsToUpdate[0]}"
    ;;
  (*)
    hasEffect updatePkgsInParallel "${pkgsToUpdate[@]}"
    ;;
esac
