#!/usr/bin/sh
# SPDX-License-Identifier: MIT
set -e

GLOBAL_CONF=/etc/10mt.conf
LOCAL_CONF="${LOCAL_DATADIR}/10mt.conf"

LOGFILE=/var/log/10mtctl.log
DEBUG="${DEBUG:-}"

log() {
  echo "${1}" >&2
  touch "${LOGFILE}" 2>/dev/null ||:
  [ -w "${LOGFILE}" ] || return 0
  echo "[$(date)] ${1}" >> "${LOGFILE}"
}

die() {
  log "ERROR: ${1}" >&2
  exit 1
}

on_exit() {
  [ -n "${DEBUG}" ] && return 0
  [ -d "${_cwd}" ] && rm -rf "${_cwd}"
}


[ -r "${GLOBAL_CONF}" ] || die "Global configuration (${GLOBAL_CONF}) not available or not accessible"
. "${GLOBAL_CONF}"
[ -r "${LOCAL_CONF}" ] && . "${LOCAL_CONF}"

case "$TERM" in
*xterm*|*color*) ICON_ON="🟢"; ICON_OFF="🔴" ;;
*)               ICON_ON="+"; ICON_OFF="-" ;;
esac

_is_running() {
  _vm_path="${1}"
  _is_running="${ICON_OFF}"

  if [ ! -e "${_vm_path}/id" ]; then
    echo "${_is_running}"
    return 0
  fi

  _running=" "$(virsh --connect qemu:///system list --state-running --name \
                | tr '\n' ' ' \
                | sed -e 's/  / /g')

  _vm_id="$(cat "${_vm_path}/id" 2>/dev/null)"
  if printf '%s' "${_running}" | grep -q " ${_vm_id} "; then
    _is_running="${ICON_ON}"
  fi
  echo "${_is_running}"
}

_get_vm_start_time() {
  _vm_path="${1}"

  if [ ! -e "${_vm_path}/id" ]; then
    echo "0"
    return 0
  fi

  _vm_id="$(cat "${_vm_path}/id" 2>/dev/null)"
  if ! virsh --connect qemu:///system list --state-running --name 2>/dev/null | grep "^${_vm_id}$" >/dev/null 2>&1; then
    echo "0"
    return 0
  fi

  _start_time=$(ps -eo pid,lstart,cmd | grep "[q]emu.*${_vm_id}" | head -1 | awk '{print $2" "$3" "$4" "$5" "$6}' | xargs -I {} date -d "{}" +%s 2>/dev/null || echo "0")
  echo "${_start_time}"
}

_format_runtime() {
  _start_time="${1}"
  _current_time=$(date +%s)

  if [ "${_start_time}" = "0" ] || [ "${_start_time}" -eq 0 ]; then
    echo "unknown"
    return 0
  fi

  _runtime_seconds=$((_current_time - _start_time))

  if [ "${_runtime_seconds}" -lt 60 ]; then
    echo "${_runtime_seconds}s"
  elif [ "${_runtime_seconds}" -lt 3600 ]; then
    _minutes=$((_runtime_seconds / 60))
    echo "${_minutes}m"
  elif [ "${_runtime_seconds}" -lt 86400 ]; then
    _hours=$((_runtime_seconds / 3600))
    _minutes=$(((_runtime_seconds % 3600) / 60))
    if [ "${_minutes}" -gt 0 ]; then
      echo "${_hours}h ${_minutes}m"
    else
      echo "${_hours}h"
    fi
  else
    _days=$((_runtime_seconds / 86400))
    _hours=$(((_runtime_seconds % 86400) / 3600))
    if [ "${_hours}" -gt 0 ]; then
      echo "${_days}d ${_hours}h"
    else
      echo "${_days}d"
    fi
  fi
}

list_vms() {
  [ -d  "${WORKDIR}" ] || return 0

  _tmpdir=$(mktemp -d)
  _running_vms="${_tmpdir}/running"
  _stopped_vms="${_tmpdir}/stopped"

  for _vm in $(find "${WORKDIR}" -maxdepth 1 -type d -printf '%T@ %p\n' \
               | sort -k 1nr \
               | sed 's/^[^ ]* //'); do
    [ "$(basename "${_vm}")" = "workspace" ] && continue

    _vm_name="$(basename "${_vm}")"
    _status="$(_is_running "${_vm}")"
    _creation_time=$(stat -c %Y "${_vm}")

    if [ "${_status}" = "${ICON_ON}" ]; then
      _start_time="$(_get_vm_start_time "${_vm}")"
      _runtime_display="$(_format_runtime "${_start_time}")"
      printf "%010d %s %s %s\n" "${_start_time}" "${_status}" "${_vm_name}" "${_runtime_display}" >> "${_running_vms}"
    else
      printf "%010d %s %s\n" "${_creation_time}" "${_status}" "${_vm_name}" >> "${_stopped_vms}"
    fi
  done

  if [ -f "${_running_vms}" ]; then
    echo "Running VMs:"
    sort -k1,1nr "${_running_vms}" | while read _start_time _status _vm_name _runtime_display; do
      printf "  %s %-50s (up: %s)\n" "${_status}" "${_vm_name}" "${_runtime_display}"
    done
    echo
  fi

  if [ -f "${_stopped_vms}" ]; then
    echo "Stopped VMs:"
    sort -k1,1nr "${_stopped_vms}" | while read _creation_time _status _vm_name; do
      printf "  %s %s\n" "${_status}" "${_vm_name}"
    done
  fi

  rm -rf "${_tmpdir}"
}

info_vm() {
  _vm="${1:-}"
  [ -z "${_vm}" ] && die "Please specify a VM to get info for"
  [ -e "${WORKDIR}/${_vm}"/log ] || die "No information found for ${_vm}"
  cat "${WORKDIR}/${_vm}"/log
}

name_vm() {
  _vm="${1:-}"
  [ -z "${_vm}" ] && die "Please specify a VM to get the name for"
  [ -e "${WORKDIR}/${_vm}"/id ] || die "No name information found for ${_vm}"
  cat "${WORKDIR}/${_vm}"/id
}

vm_script() {
  _vm="${1:-}"
  _cmd="${2:-}"
  _novmerr="${3:-}"
  _noscripterr="${4:-}"
  [ -z "${_vm}" ] && die "${_novmerr}"
  [ -d "${WORKDIR}/${_vm}" ] || die "VM ${_vm} does not seem to exist"
  [ -e "${WORKDIR}/${_vm}/${_cmd}" ] || die "${_noscripterr}"
  sh "${WORKDIR}/${_vm}/${_cmd}"
}

provision_vm() {
  vm_script "${1}" \
    "provision" \
    "Please specify a VM to be provisioned" \
    "No provisioning script found for ${1}"
}

console_vm() {
  vm_script "${1}" \
    "console" \
    "Please specify a VM to be access the console for" \
    "No console script found for ${1}"
}

nuke_vm() {
  _vm="${1:-}"
  [ -z "${_vm}" ] && die "${_novmerr}"
  [ -d "${WORKDIR}/${_vm}" ] || return 0
  [ -e "${WORKDIR}/${_vm}"/cleanup ] && vm_script "${_vm}" \
      "cleanup" \
      "Please specify a VM to be nuked" \
      "No cleanup/nuke script found for ${vm}" ||:

  rm -rf "${WORKDIR}/${_vm}"
}

cleanup_vm() {
  vm_script "${1}" \
    "cleanup" \
    "Please specify a VM to be clean up" \
    "No cleanup script found for ${1}"
}

start_vm() {
  vm_script "${1}" \
    "start" \
    "Please specify a VM to start up" \
    "No start script found for ${1}"
}

stop_vm() {
  vm_script "${1}" \
    "stop" \
    "Please specify a VM to stop" \
    "No stop script found for ${1}"
}

snapshot_vm() {
  vm_script "${1}" \
    "snapshot" \
    "Please specify a VM to create a snapshot of" \
    "No snapshot script found for ${1}"
}

restore_snapshot_vm() {
  vm_script "${1}" \
    "snapshot-restore" \
    "Please specify a VM to restore a snapshot of" \
    "No snapshot restore script found for ${1}"
}

compose_vm() {
  _vm="${1:-}"
  [ -z "${_vm}" ] && die "Please specify a VM to get info for"
  [ -e "${WORKDIR}/${_vm}"/compose ] || die "No compose information avaialble for ${_vm}"
  cat "${WORKDIR}/${_vm}"/compose
}

usage() {
  _ret="${1:-1}"
  exec >&2
  printf "Usage: 10mtctl [list | info <VM> | provision <VM> | start <VM> | console <VM>"
  printf " | compose <VM> | stop <VM> | cleanup <VM> | nuke <VM> | snapshot <VM> | restore-snapshot <VM>]\n"
  exit "${_ret}"
}

ARGS="${*}"

ACTION="${1}"
OPT="${2}"

#echo "${ACTION} ${OPT}" >&2

case "${ACTION}" in
list)
  list_vms;;
info)
  info_vm "${OPT}";;
name)
  name_vm "${OPT}";;
cleanup)
  cleanup_vm "${OPT}";;
nuke)
  nuke_vm "${OPT}";;
compose)
  compose_vm "${OPT}";;
console)
  console_vm "${OPT}";;
provision)
  if ! _cwd="$(mktemp -d "${TMPDIR:-/tmp}/10mtctl-provision-XXXXX")"; then
    die "Unable to create a temporary dir to store the provisionin log"
  fi

  export _cwd
  _plog="${_cwd}"/log

  _max_retries=5
  _attempt=1

  while [ "${_attempt}" -le "${_max_retries}" ]; do
    export LANG=C
    if ! provision_vm "${OPT}" 2>"${_plog}"; then
      cat "${_plog}" >&2
      if grep -qE 'internal error: pool .* has asynchronous jobs running' \
                  "${_plog}"; then
        log "(#${_attempt}) error attempting to provision ${OPT}; we may retry..."
        _attempt=$((_attempt+1))
        cleanup_vm "${OPT}" >"${_plog}" 2>"${_plog}"
	export DEBUG=1
        continue
      fi
    fi
    break
  done
  ;;
start)
  start_vm "${OPT}";;
stop)
  stop_vm "${OPT}";;
snapshot)
  snapshot_vm "${OPT}";;
restore-snapshot)
  restore_snapshot_vm "${OPT}";;
*)
  [ -z "${ACTION}" ] && usage
  log "Unsupported action (${ACTION})"
  exit 1
  ;;
esac

# vim:set ts=2 sw=2 et:
