#!/usr/bin/env bash
set -euo pipefail

root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
state_dir=${UNITCTL_STATE_DIR:-${TMPDIR:-/tmp}/catalog-unitctl-${UID}}
mkdir -p "$state_dir" "$state_dir/output"

usage() {
  printf 'usage: unitctl {start|restart|is-active|starts|status} UNIT\n' >&2
  exit 64
}

unit_path() {
  local unit=$1
  if [[ ! -f $root/units/$unit ]]; then
    printf 'unitctl: unknown unit: %s\n' "$unit" >&2
    return 5
  fi
  printf '%s\n' "$root/units/$unit"
}

property() {
  local unit=$1 key=$2 path
  path=$(unit_path "$unit") || return
  awk -v key="$key" '
    index($0, key "=") == 1 {
      sub(/^[^=]*=/, "")
      print
      exit
    }
  ' "$path"
}

state_file() {
  printf '%s/%s.state\n' "$state_dir" "$1"
}

starts_file() {
  printf '%s/%s.starts\n' "$state_dir" "$1"
}

read_state() {
  local path
  path=$(state_file "$1")
  if [[ -f $path ]]; then
    sed -n '1p' "$path"
  else
    printf 'inactive\n'
  fi
}

read_starts() {
  local path
  path=$(starts_file "$1")
  if [[ -f $path ]]; then
    sed -n '1p' "$path"
  else
    printf '0\n'
  fi
}

write_state() {
  printf '%s\n' "$2" >"$(state_file "$1")"
}

mark_active() {
  local unit=$1 starts=$2
  printf '%s\n' "$starts" >"$(starts_file "$unit")"
  write_state "$unit" active
}

expand_exec_start() {
  local value=$1
  value=${value//'${CATALOG_IMPORT_CONFIG}'/${CATALOG_IMPORT_CONFIG-}}
  printf '%s\n' "$value"
}

start_unit() {
  local unit=$1 relation dependencies dependency env_file exec_start expanded
  local starts next_start executable rc
  local -a command

  unit_path "$unit" >/dev/null || return
  if [[ $(read_state "$unit") == active ]]; then
    return 0
  fi

  for relation in Requires Wants; do
    dependencies=$(property "$unit" "$relation")
    for dependency in $dependencies; do
      if ! start_unit "$dependency"; then
        write_state "$unit" failed
        return 1
      fi
    done
  done

  if [[ $unit == *.target ]]; then
    starts=$(read_starts "$unit")
    mark_active "$unit" "$((starts + 1))"
    return 0
  fi

  env_file=$(property "$unit" EnvironmentFile)
  if [[ -n $env_file ]]; then
    if [[ ! -r $root/$env_file ]]; then
      printf 'unitctl: EnvironmentFile is not readable: %s\n' "$env_file" >&2
      write_state "$unit" failed
      return 78
    fi
    set -a
    # shellcheck disable=SC1090
    source "$root/$env_file"
    set +a
  fi

  exec_start=$(property "$unit" ExecStart)
  if [[ -z $exec_start ]]; then
    printf 'unitctl: %s has no ExecStart\n' "$unit" >&2
    write_state "$unit" failed
    return 78
  fi
  expanded=$(expand_exec_start "$exec_start")
  read -r -a command <<<"$expanded"
  executable=$root/${command[0]}
  starts=$(read_starts "$unit")
  next_start=$((starts + 1))

  if UNIT_OUTPUT_DIR=$state_dir/output UNIT_START_NUMBER=$next_start \
      "$executable" "${command[@]:1}"; then
    mark_active "$unit" "$next_start"
  else
    rc=$?
    write_state "$unit" failed
    printf 'unitctl: %s exited with status %s\n' "$unit" "$rc" >&2
    return "$rc"
  fi
}

(($# == 2)) || usage
action=$1
unit=$2

case $action in
  start)
    start_unit "$unit"
    ;;
  restart)
    unit_path "$unit" >/dev/null || exit
    write_state "$unit" inactive
    start_unit "$unit"
    ;;
  is-active)
    state=$(read_state "$unit")
    printf '%s\n' "$state"
    [[ $state == active ]]
    ;;
  starts)
    read_starts "$unit"
    ;;
  status)
    printf '%s state=%s starts=%s\n' "$unit" "$(read_state "$unit")" "$(read_starts "$unit")"
    ;;
  *)
    usage
    ;;
esac
