#!/usr/bin/env bash
set -e

source argparse.bash
mgmt_dir=; def_arg mgmt_dir d value default "${XDG_CONFIG_HOME:-"$HOME/.config"}/mgmt"
positional=(); parse_args "$@"

if [ -d "$mgmt_dir/fns" ]; then
  while IFS= read -rd '' fnfile; do
    source "$fnfile"
  done < <(find "$mgmt_dir/fns" -type f -print0)
fi

PREFIX=""
grey() { printf "\33[37;2m%s\33[0;m" "$*"; }
red() { printf "\33[31;1m%s\33[0;m" "$*"; }
diag() {
  >&2 printf "\33[2K\r%s" "$(grey "$PREFIX")"
  >&2 echo "$@"
}

join_by() { local IFS="$1"; shift; echo "$*"; }
filter_by_args() { grep -E "$(join_by '|' "${positional[@]}")"; }

#####
# Gather dependencies of (filtered) modules
#####
cd "$mgmt_dir"
declare -a found_modules
mapfile -t filtered_mods < <(find mod -type f | filter_by_args)
set -- "${filtered_mods[@]}"
while [ $# -gt 0 ]; do
  unset deps; declare -a deps
  for mod; do
    if echo "${found_modules[@]}" | grep -q "$mod"; then
      continue
    fi
    found_modules+=( "$mod" )
    unset DEPENDS
    source "$mod"
    for dep in "${DEPENDS[@]}"; do
      if echo "${found_modules[@]}" | grep -qv "$dep"; then
        if echo "${deps[@]}" | grep -qv "$dep"; then
          deps+=( "$dep" )
        fi
      fi
    done
  done
  set -- "${deps[@]}"
done

#####
# Resolve dependency ordering
#####
declare -a ordered_modules
set -- "${found_modules[@]}"
while [ $# -gt 0 ]; do
  unset remaining; declare -a remaining
  for mod; do
    unset DEPENDS
    source "$mod"
    if [[ -n "$(comm -23 <(printf -- '%s\n' "${DEPENDS[@]}" | sort) <(printf -- '%s\n' "${ordered_modules[@]}" | sort))" ]]; then
      remaining+=( "$mod" )
    else
      ordered_modules+=( "$mod" )
    fi
  done
  if [ $# -eq ${#remaining[@]} ]; then
    diag "$(red "Error: dependency loop involving ${remaining[*]}")"
    exit 1
  fi
  set -- "${remaining[@]}"
done

#####
# Apply modules
#####
command -v sudo >/dev/null && sudo -v
set -- "${ordered_modules[@]}"
for mod; do
  diag -e "\nApplying $mod"
  PREFIX="[$mod] "
  unset check_setup setup check_apply apply
  source "$mod"
  test "$(declare -F check_setup)" || { check_setup() { false ; } }
  test "$(declare -F check_apply)" || { check_apply() { false ; } }
  test "$(declare -F setup)"       || { setup() { true; } }
  test "$(declare -F apply)"       || { apply() { true; } }
  check_setup || setup
  check_apply || apply
  PREFIX=""
done
