#!/bin/bash
#Nifty: Library for use in other scripts

if [ -f "/etc/os-release" ]; then
  . /etc/os-release
fi

# Define some useful environment variables for OS recognition
case "$ID" in
  centos|almalinux|cloudlinux) OS_FAMILY="rhel" ;;
  debian|ubuntu) OS_FAMILY="debian" ;;
  *) OS_FAMILY="unknown"
esac

OS_DISTRO="$ID"
OS_VERSION="$(cut -d'.' -f1 <<< "$VERSION_ID")"

detect_os() {
  if [ -z "${VERSION_ID:-}" ]; then
    echo "unknown"
    return
  fi

  case "${ID:-}" in
    centos|almalinux|cloudlinux)
      MAJOR_VERSION="${VERSION_ID/.*/}"
      if compare_versions "${MAJOR_VERSION}" ">=" "8"; then
        echo "alma$MAJOR_VERSION"
      else
        echo "centos$MAJOR_VERSION"
      fi
    ;;
    ubuntu) echo "ubuntu${VERSION_ID/./}" ;;
    debian) echo "debian$VERSION_ID" ;;
    *) echo "unknown" ;;
  esac
}

get_os_name() {
  [ -f "/etc/os-release" ] || return 1
  . /etc/os-release
  echo "$VERSION_CODENAME"
}

get_os_version() {
  [ -f "/etc/os-release" ] || return 1
  . /etc/os-release
  echo "$VERSION_ID"
}

require_os() {
  local OS
  OS="$(detect_os)"

  for SUPPORTED_OS in "$@"; do
    if ! args_contain "$OS" "$@"; then
      err "Detected OS $OS."
      err "This script only supports the following operating systems: $*"
      exit 1
    fi

    [ "$SUPPORTED_OS" == "$OS" ] && return
  done

  err "Detected OS $OS."
  err "This script only supports the following operating systems: $*"
  exit 1
}

require_os_family() {
  if ! args_contain "$OS_FAMILY" "$@"; then
    err "Detected OS family $OS_FAMILY."
    err "This script only supports the following OS families: $*"
    exit 1
  fi
}

require_os_distro() {
  if ! args_contain "$OS_DISTRO" "$@"; then
    err "Detected OS distribution $OS_DISTRO"
    err "This script only supports the following OS distributions: $*"
    exit 1
  fi
}

require_os_version() {
  if ! args_contain "$OS_VERSION" "$@"; then
    err "Detected OS version $OS_VERSION"
    err "This script only supports the following OS versions: $*"
    exit 1
  fi
}

require_root() {
  if [ "$(id -u)" != "0" ]; then
     err "This script must be run as root"
     exit 1
  fi
}

require_64bit() {
if [ "$(getconf LONG_BIT)" != "64" ]; then
  err "Script requires 64-bit operating system. This server is not"
  exit 1
fi
}

has_cmd() {
  command -v "$1" > /dev/null 2> /dev/null
}

require_cmd() {
  local ERR PARAM

  for PARAM in "$@"; do
    if ! has_cmd "$PARAM"; then
      err "Command $PARAM was not found in path. Please make sure that the prerequisite package is installed."
      ERR=1
    fi
  done

  if [ -n "${ERR:-}" ]; then
    exit 1
  fi
}

has_package() {
  if has_cmd dnf; then
    test -n "$(dnf repoquery --installed "$1")"
  elif has_cmd repoquery; then
    test -n "$(repoquery --installed "$1")"
  elif has_cmd dpkg; then
    dpkg-query -s "$1" &> /dev/null
  else
    return 2
  fi
}

is_interactive() {
  test -t 0
}

prompt_continue() {
   local PROMPT="$1"
   [ -z "$PROMPT" ] && PROMPT="Press enter to continue..."
   echo "$(tput bold)$PROMPT$(tput sgr0)"
   read -r
}

prompt_confirm() {
  case "$(prompt_select "$*" "y" "yes" "n" "no")" in
    y|yes) return 0 ;;
    n|no) return 1 ;;
  esac
}

prompt_character() {
  local PROMPT="$1"
  local OPTIONS="$2"
  local OPTION
  local -a OPTION_ARRAY

  while read -rn1 OPTION; do
    OPTION_ARRAY+=("$OPTION")
  done <<< "$OPTIONS"

  prompt_select "$PROMPT" "${OPTION_ARRAY[@]}"
}

prompt_string() {
  local RESPONSE PROMPT="$*"
  read -er -p "$(tput bold)$PROMPT$(tput sgr0) > " RESPONSE
  echo "$RESPONSE"
}

# NOTE: Case insensitive (for ease of use/convenience)!
prompt_select() {
  local PROMPT="$1"
  local OPTIONS=("${@:2}")

  while :; do
    RESPONSE="$(prompt_string "$PROMPT")"
    for OPTION in "${OPTIONS[@]}"; do
      if [ -n "$RESPONSE" ] && [ "${RESPONSE,,}" = "${OPTION,,}" ]; then
        echo "$RESPONSE"
        return
      fi
    done
  done
}

# Present enumerated options, returning the selected option's ID
prompt_select_num() {
  local PROMPT="$1"
  local OPTIONS=("${@:2}")
  local I CHOICE

  echo "$(tput bold)$PROMPT$(tput sgr0)" >&2

  I=1
  for OPTION in "${OPTIONS[@]}"; do
    echo "  $((I++))) $OPTION" >&2
  done

  echo >&2
  CHOICE="$(prompt_select "Choice" $(seq 1 "$I"))"
  echo "$CHOICE"
}

# Present enumerated options, returning the selected option's value
prompt_select_num_value() {
  local CHOICE OPTIONS=("${@:2}")
  CHOICE="$(prompt_select_num "$@")"
  echo "${OPTIONS[$((CHOICE-1))]}"
}

with_umask() {
  local OLD_UMASK UMASK="$1"
  shift

  OLD_UMASK="$(umask)"
  umask "$UMASK"
  "$@"
  umask "$OLD_UMASK"
}

args_contain() {
  local REQUIRED_VALUE
  REQUIRED_VALUE="$1"
  shift

  for VALUE in "$@"; do
    [ "$REQUIRED_VALUE" = "$VALUE" ] && return 0
  done

  return 1
}

random_string() {
  local LENGTH
  LENGTH="$1"
  strings /dev/urandom | grep -o '[[:alnum:]]' | head -n "$LENGTH" | tr -d '\n'
}

get_total_memory() {
  local TOTAL_MEMORY_KIB
  TOTAL_MEMORY_KIB="$(awk '$1 == "MemTotal:" { print $2 }' '/proc/meminfo')"
  echo "$((TOTAL_MEMORY_KIB * 1024))"
}

get_cpu_count() {
  grep -c 'cpu MHz' '/proc/cpuinfo'
}

compare_versions() {
  local VER1 OPERATOR VER2
  VER1="$1"
  OPERATOR="$2"
  VER2="$3"

  if [ "$OPERATOR" = "=" ]; then
    [ "$VER1" = "$VER2" ] && return 0
    return 1;
  fi

  if [ "$OPERATOR" = ">=" ]; then
    if test "$VER1" = "$VER2" || compare_versions "$VER1" ">" "$VER2"; then
      return 0
    fi
    return 1
  fi

  if [ "$OPERATOR" = "<=" ]; then
    if test "$VER1" = "$VER2" || compare_versions "$VER1" "<" "$VER2"; then
      return 0
    fi
    return 1
  fi

  LARGEST_VERSION="$(sort -rV <<< "$VER1"$'\n'"$VER2" | head -n1)"
  if [ "$OPERATOR" = ">" ]; then
    if [ "$VER1" != "$VER2" ] && [ "$LARGEST_VERSION" = "$VER1" ]; then
      return 0
    fi
    return 1
  fi

  if [ "$OPERATOR" = "<" ]; then
    if [ "$VER1" != "$VER2" ] && [ "$LARGEST_VERSION" = "$VER2" ]; then
      return 0
    fi
    return 1
  fi

  err "SYNTAX: Invalid operator \"$OPERATOR\" specified for compare_versions()"
  exit 20
}

implode() {
  local SEPARATOR="$1" ELEMENT="$2"
  shift 2 || shift $(($#))
  # shellcheck disable=SC2145
  printf "%s" "$ELEMENT${@/#/$SEPARATOR}"
}

# Colored Echo
ce() {
  echo "$(tput setaf 6)$*$(tput sgr0)"
}

# Error output
err() {
  echo "$(tput setaf 1)ERROR $*$(tput sgr0)"
}
