#!/usr/bin/env bash

#=======================================================================#
# Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com>       #
#                                                                       #
# This file is part of KIAUH - Klipper Installation And Update Helper   #
# https://github.com/dw-0/kiauh                                         #
#                                                                       #
# This file may be distributed under the terms of the GNU GPLv3 license #
#=======================================================================#

set -e

#===================================================#
#================ INSTALL MOONRAKER ================#
#===================================================#

###
# this function detects all installed moonraker
# systemd instances and returns their absolute path
function moonraker_systemd() {
  local services
  local blacklist
  local ignore
  local match

  ###
  # any moonraker client that uses "moonraker" in its own name must be blacklisted using
  # this variable, otherwise they will be falsely recognized as moonraker instances
  blacklist="obico"

  ignore="${SYSTEMD}/moonraker-(${blacklist}).service"
  match="${SYSTEMD}/moonraker(-[0-9a-zA-Z]+)?.service"

  services=$(find "${SYSTEMD}" -maxdepth 1 -regextype awk ! -regex "${ignore}" -regex "${match}" | sort)
  echo "${services}"
}

function moonraker_setup_dialog() {
  status_msg "Initializing Moonraker installation ..."

  ### return early if python version check fails
  if [[ $(python3_check) == "false" ]]; then
    local error="Versioncheck failed! Python 3.7 or newer required!\n"
    error="${error} Please upgrade Python."
    print_error "${error}" && return
  fi

  ### return early if moonraker already exists
  local moonraker_services
  moonraker_services=$(moonraker_systemd)
  if [[ -n ${moonraker_services} ]]; then
    local error="At least one Moonraker service is already installed:"
    for s in ${moonraker_services}; do
      log_info "Found Moonraker service: ${s}"
      error="${error}\n ➔ ${s}"
    done
    print_error "${error}" && return
  fi

  ### return early if klipper is not installed
  local klipper_services
  klipper_services=$(klipper_systemd)
  if [[ -z ${klipper_services} ]]; then
    local error="Klipper not installed! Please install Klipper first!"
    log_error "Moonraker setup started without Klipper being installed. Aborting setup."
    print_error "${error}" && return
  fi

  local klipper_count user_input=() klipper_names=()
  klipper_count=$(echo "${klipper_services}" | wc -w )
  for service in ${klipper_services}; do
    klipper_names+=( "$(get_instance_name "${service}")" )
  done

  local moonraker_count
  if (( klipper_count == 1 )); then
    ok_msg "Klipper installation found!\n"
    moonraker_count=1
  elif (( klipper_count > 1 )); then
    top_border
    printf "|${green}%-55s${white}|\n" " ${klipper_count} Klipper instances found!"
    for name in "${klipper_names[@]}"; do
      printf "|${cyan}%-57s${white}|\n" " ● klipper-${name}"
    done
    blank_line
    echo -e "| The setup will apply the same names to Moonraker!     |"
    blank_line
    echo -e "| Please select the number of Moonraker instances to    |"
    echo -e "| install. Usually one Moonraker instance per Klipper   |"
    echo -e "| instance is required, but you may not install more    |"
    echo -e "| Moonraker instances than available Klipper instances. |"
    bottom_border

    ### ask for amount of instances
    local re="^[1-9][0-9]*$"
    while [[ ! ${moonraker_count} =~ ${re} || ${moonraker_count} -gt ${klipper_count} ]]; do
      read -p "${cyan}###### Number of Moonraker instances to set up:${white} " -i "${klipper_count}" -e moonraker_count
      ### break if input is valid
      [[ ${moonraker_count} =~ ${re} && ${moonraker_count} -le ${klipper_count} ]] && break
      ### conditional error messages
      [[ ! ${moonraker_count} =~ ${re} ]] && error_msg "Input not a number"
      (( moonraker_count > klipper_count )) && error_msg "Number of Moonraker instances larger than installed Klipper instances"
    done && select_msg "${moonraker_count}"
  else
    log_error "Internal error. klipper_count of '${klipper_count}' not equal or grather than one!"
    return 1
  fi

  user_input+=("${moonraker_count}")

  ### confirm instance amount
  local yn
  while true; do
    (( moonraker_count == 1 )) && local question="Install Moonraker?"
    (( moonraker_count > 1 )) && local question="Install ${moonraker_count} Moonraker instances?"
    read -p "${cyan}###### ${question} (Y/n):${white} " yn
    case "${yn}" in
      Y|y|Yes|yes|"")
        select_msg "Yes"
        break;;
      N|n|No|no)
        select_msg "No"
        abort_msg "Exiting Moonraker setup ...\n"
        return;;
      *)
        error_msg "Invalid Input!";;
    esac
  done

  ### write existing klipper names into user_input array to use them as names for moonraker
  if (( klipper_count > 1 )); then
    for name in "${klipper_names[@]}"; do
      user_input+=("${name}")
    done
  fi

  (( moonraker_count > 1 )) && status_msg "Installing ${moonraker_count} Moonraker instances ..."
  (( moonraker_count == 1 )) && status_msg "Installing Moonraker ..."
  moonraker_setup "${user_input[@]}"
}

function install_moonraker_dependencies() {
  local packages log_name="Moonraker"
  local package_json="${MOONRAKER_DIR}/scripts/system-dependencies.json"

  ### read PKGLIST from official install-script
  status_msg "Reading dependencies..."
  # shellcheck disable=SC2016
  packages=$(python3 - << EOF
from __future__ import annotations
import shlex
import re
import pathlib
import logging
import json

from typing import Tuple, Dict, List, Any

def _get_distro_info() -> Dict[str, Any]:
    release_file = pathlib.Path("/etc/os-release")
    release_info: Dict[str, str] = {}
    with release_file.open("r") as f:
        lexer = shlex.shlex(f, posix=True)
        lexer.whitespace_split = True
        for item in list(lexer):
            if "=" in item:
                key, val = item.split("=", maxsplit=1)
                release_info[key] = val
    return dict(
        distro_id=release_info.get("ID", ""),
        distro_version=release_info.get("VERSION_ID", ""),
        aliases=release_info.get("ID_LIKE", "").split()
    )

def _convert_version(version: str) -> Tuple[str | int, ...]:
    version = version.strip()
    ver_match = re.match(r"\d+(\.\d+)*((?:-|\.).+)?", version)
    if ver_match is not None:
        return tuple([
            int(part) if part.isdigit() else part
            for part in re.split(r"\.|-", version)
        ])
    return (version,)

class SysDepsParser:
    def __init__(self, distro_info: Dict[str, Any] | None = None) -> None:
        if distro_info is None:
            distro_info = _get_distro_info()
        self.distro_id: str = distro_info.get("distro_id", "")
        self.aliases: List[str] = distro_info.get("aliases", [])
        self.distro_version: Tuple[int | str, ...] = tuple()
        version = distro_info.get("distro_version")
        if version:
            self.distro_version = _convert_version(version)

    def _parse_spec(self, full_spec: str) -> str | None:
        parts = full_spec.split(";", maxsplit=1)
        if len(parts) == 1:
            return full_spec
        pkg_name = parts[0].strip()
        expressions = re.split(r"( and | or )", parts[1].strip())
        if not len(expressions) & 1:
            logging.info(
                f"Requirement specifier is missing an expression "
                f"between logical operators : {full_spec}"
            )
            return None
        last_result: bool = True
        last_logical_op: str | None = "and"
        for idx, exp in enumerate(expressions):
            if idx & 1:
                if last_logical_op is not None:
                    logging.info(
                        "Requirement specifier contains sequential logical "
                        f"operators: {full_spec}"
                    )
                    return None
                logical_op = exp.strip()
                if logical_op not in ("and", "or"):
                    logging.info(
                        f"Invalid logical operator {logical_op} in requirement "
                        f"specifier: {full_spec}")
                    return None
                last_logical_op = logical_op
                continue
            elif last_logical_op is None:
                logging.info(
                    f"Requirement specifier contains two seqential expressions "
                    f"without a logical operator: {full_spec}")
                return None
            dep_parts = re.split(r"(==|!=|<=|>=|<|>)", exp.strip())
            req_var = dep_parts[0].strip().lower()
            if len(dep_parts) != 3:
                logging.info(f"Invalid comparison, must be 3 parts: {full_spec}")
                return None
            elif req_var == "distro_id":
                left_op: str | Tuple[int | str, ...] = self.distro_id
                right_op = dep_parts[2].strip().strip("\"'")
            elif req_var == "distro_version":
                if not self.distro_version:
                    logging.info(
                        "Distro Version not detected, cannot satisfy requirement: "
                        f"{full_spec}"
                    )
                    return None
                left_op = self.distro_version
                right_op = _convert_version(dep_parts[2].strip().strip("\"'"))
            else:
                logging.info(f"Invalid requirement specifier: {full_spec}")
                return None
            operator = dep_parts[1].strip()
            try:
                compfunc = {
                    "<": lambda x, y: x < y,
                    ">": lambda x, y: x > y,
                    "==": lambda x, y: x == y,
                    "!=": lambda x, y: x != y,
                    ">=": lambda x, y: x >= y,
                    "<=": lambda x, y: x <= y
                }.get(operator, lambda x, y: False)
                result = compfunc(left_op, right_op)
                if last_logical_op == "and":
                    last_result &= result
                else:
                    last_result |= result
                last_logical_op = None
            except Exception:
                logging.exception(f"Error comparing requirements: {full_spec}")
                return None
        if last_result:
            return pkg_name
        return None

    def parse_dependencies(self, sys_deps: Dict[str, List[str]]) -> List[str]:
        if not self.distro_id:
            logging.info(
                "Failed to detect current distro ID, cannot parse dependencies"
            )
            return []
        all_ids = [self.distro_id] + self.aliases
        for distro_id in all_ids:
            if distro_id in sys_deps:
                if not sys_deps[distro_id]:
                    logging.info(
                        f"Dependency data contains an empty package definition "
                        f"for linux distro '{distro_id}'"
                    )
                    continue
                processed_deps: List[str] = []
                for dep in sys_deps[distro_id]:
                    parsed_dep = self._parse_spec(dep)
                    if parsed_dep is not None:
                        processed_deps.append(parsed_dep)
                return processed_deps
        else:
            logging.info(
                f"Dependency data has no package definition for linux "
                f"distro '{self.distro_id}'"
            )
        return []
# *** SYSTEM DEPENDENCIES START ***
system_deps = {
    "debian": [
        "python3-virtualenv", "python3-dev", "libopenjp2-7", "libsodium-dev",
        "zlib1g-dev", "libjpeg-dev", "packagekit",
        "wireless-tools; distro_id != 'ubuntu' or distro_version <= '24.04'",
        "iw; distro_id == 'ubuntu' and distro_version >= '24.10'", "curl",
        "build-essential"
    ],
}
system_deps_json = pathlib.Path("$package_json")
system_deps = json.loads(system_deps_json.read_bytes())
parser = SysDepsParser()
pkgs = parser.parse_dependencies(system_deps)
if pkgs:
    print(' '.join(pkgs), end="")
exit(0)
EOF
)

  echo "${cyan}${packages}${white}" | tr '[:space:]' '\n'
  read -r -a packages <<< "${packages}"

  ### Update system package lists if stale
  update_system_package_lists

  ### Install required packages
  install_system_packages "${log_name}" "packages[@]"
}

function create_moonraker_virtualenv() {
  status_msg "Installing python virtual environment..."

  ### always create a clean virtualenv
  [[ -d ${MOONRAKER_ENV} ]] && rm -rf "${MOONRAKER_ENV}"

  if virtualenv -p /usr/bin/python3 "${MOONRAKER_ENV}"; then
    "${MOONRAKER_ENV}"/bin/pip install -U pip
    "${MOONRAKER_ENV}"/bin/pip install -r "${MOONRAKER_DIR}/scripts/moonraker-requirements.txt"
  else
    log_error "failure while creating python3 moonraker-env"
    error_msg "Creation of Moonraker virtualenv failed!"
    exit 1
  fi
}

function moonraker_setup() {
  local instance_arr=("${@}")
  ### checking dependencies
  local dep=(git wget curl unzip dfu-util virtualenv)
  ### additional required dependencies on armbian
  dep+=(libjpeg-dev zlib1g-dev)
  dependency_check "${dep[@]}"

  ### step 1: clone moonraker
  clone_moonraker "${MOONRAKER_REPO}"

  ### step 2: install moonraker dependencies and create python virtualenv
  status_msg "Installing dependencies ..."
  install_moonraker_dependencies
  create_moonraker_virtualenv

  ### step 3: create moonraker.conf
  create_moonraker_conf "${instance_arr[@]}"

  ### step 4: create moonraker instances
  configure_moonraker_service "${instance_arr[@]}"

  ### step 5: create polkit rules for moonraker
  install_moonraker_polkit || true

  ### step 6: enable and start all instances
  do_action_service "enable" "moonraker"
  do_action_service "start" "moonraker"

  ### confirm message
  local confirm=""
  (( instance_arr[0] == 1 )) && confirm="Moonraker has been set up!"
  (( instance_arr[0] > 1 )) && confirm="${instance_arr[0]} Moonraker instances have been set up!"
  print_confirm "${confirm}" && print_mr_ip_list "${instance_arr[0]}" && return
}

function clone_moonraker() {
  local repo=${1}

  status_msg "Cloning Moonraker from ${repo} ..."

  ### force remove existing moonraker dir and clone into fresh moonraker dir
  [[ -d ${MOONRAKER_DIR} ]] && rm -rf "${MOONRAKER_DIR}"

  cd "${HOME}" || exit 1
  if ! git clone "${MOONRAKER_REPO}" "${MOONRAKER_DIR}"; then
    print_error "Cloning Moonraker from\n ${repo}\n failed!"
    exit 1
  fi
}

function create_moonraker_conf() {
  local input=("${@}")
  local moonraker_count=${input[0]} && unset "input[0]"
  local names=("${input[@]}") && unset "input[@]"
  local port lan printer_data cfg_dir cfg uds

  port=7125
  lan="$(hostname -I | cut -d" " -f1 | cut -d"." -f1-2).0.0/16"

  if (( moonraker_count == 1 )); then
    printer_data="${HOME}/printer_data"
    cfg_dir="${printer_data}/config"
    cfg="${cfg_dir}/moonraker.conf"
    uds="${printer_data}/comms/klippy.sock"

    ### write single instance config
    write_moonraker_conf "${cfg_dir}" "${cfg}" "${port}" "${uds}" "${lan}"

  elif (( moonraker_count > 1 )); then
    local j=0 re="^[1-9][0-9]*$"

    for (( i=1; i <= moonraker_count; i++ )); do
      ### overwrite config folder if name is only a number
      if [[ ${names[j]} =~ ${re} ]]; then
        printer_data="${HOME}/printer_${names[${j}]}_data"
      else
        printer_data="${HOME}/${names[${j}]}_data"
      fi

      cfg_dir="${printer_data}/config"
      cfg="${cfg_dir}/moonraker.conf"
      uds="${printer_data}/comms/klippy.sock"

      ### write multi instance config
      write_moonraker_conf "${cfg_dir}" "${cfg}" "${port}" "${uds}" "${lan}"
      port=$(( port + 1 ))
      j=$(( j + 1 ))
    done && unset j

  else
    return 1
  fi
}

function write_moonraker_conf() {
  local cfg_dir=${1} cfg=${2} port=${3} uds=${4} lan=${5}
  local conf_template="${KIAUH_SRCDIR}/resources/moonraker.conf"

  [[ ! -d ${cfg_dir} ]] && mkdir -p "${cfg_dir}"

  if [[ ! -f ${cfg} ]]; then
    status_msg "Creating moonraker.conf in ${cfg_dir} ..."
    cp "${conf_template}" "${cfg}"
    sed -i "s|%USER%|${USER}|g; s|%PORT%|${port}|; s|%UDS%|${uds}|" "${cfg}"
    # if host ip is not in the default ip ranges replace placeholder,
    # otherwise remove placeholder from config
    if ! grep -q "${lan}" "${cfg}"; then
      sed -i "s|%LAN%|${lan}|" "${cfg}"
    else
      sed -i "/%LAN%/d" "${cfg}"
    fi
    ok_msg "moonraker.conf created!"
  else
    status_msg "File '${cfg_dir}/moonraker.conf' already exists!\nSkipping..."
  fi
}

function configure_moonraker_service() {
  local input=("${@}")
  local moonraker_count=${input[0]} && unset "input[0]"
  local names=("${input[@]}") && unset "input[@]"
  local printer_data cfg_dir service env_file

  if (( moonraker_count == 1 )) && [[ ${#names[@]} -eq 0 ]]; then
    i=""
    printer_data="${HOME}/printer_data"
    cfg_dir="${printer_data}/config"
    service="${SYSTEMD}/moonraker.service"
    env_file="${printer_data}/systemd/moonraker.env"

    ### create required folder structure
    create_required_folders "${printer_data}"

    ### write single instance service
    write_moonraker_service "" "${printer_data}" "${service}" "${env_file}"
    ok_msg "Moonraker instance created!"

  elif (( moonraker_count > 1 )) && [[ ${#names[@]} -gt 0 ]]; then
    local j=0 re="^[1-9][0-9]*$"

    for (( i=1; i <= moonraker_count; i++ )); do
      ### overwrite config folder if name is only a number
      if [[ ${names[j]} =~ ${re} ]]; then
        printer_data="${HOME}/printer_${names[${j}]}_data"
      else
        printer_data="${HOME}/${names[${j}]}_data"
      fi

      cfg_dir="${printer_data}/config"
      service="${SYSTEMD}/moonraker-${names[${j}]}.service"
      env_file="${printer_data}/systemd/moonraker.env"

      ### create required folder structure
      create_required_folders "${printer_data}"

      ### write multi instance service
      write_moonraker_service "${names[${j}]}" "${printer_data}" "${service}" "${env_file}"
      ok_msg "Moonraker instance 'moonraker-${names[${j}]}' created!"
      j=$(( j + 1 ))
    done && unset i

    ### enable mainsails remoteMode if mainsail is found
    if [[ -d ${MAINSAIL_DIR} ]]; then
      enable_mainsail_remotemode
    fi

  else
    return 1
  fi
}

function write_moonraker_service() {
  local i=${1} printer_data=${2} service=${3} env_file=${4}
  local service_template="${KIAUH_SRCDIR}/resources/moonraker.service"
  local env_template="${KIAUH_SRCDIR}/resources/moonraker.env"

  ### replace all placeholders
  if [[ ! -f ${service} ]]; then
    status_msg "Creating Moonraker Service ${i} ..."
    sudo cp "${service_template}" "${service}"
    sudo cp "${env_template}" "${env_file}"

    [[ -z ${i} ]] && sudo sed -i "s| %INST%||" "${service}"
    [[ -n ${i} ]] && sudo sed -i "s|%INST%|${i}|" "${service}"
    sudo sed -i "s|%USER%|${USER}|g; s|%MOONRAKER_DIR%|${MOONRAKER_DIR}|; s|%ENV%|${MOONRAKER_ENV}|; s|%ENV_FILE%|${env_file}|" "${service}"
    sudo sed -i "s|%USER%|${USER}|; s|%MOONRAKER_DIR%|${MOONRAKER_DIR}|; s|%PRINTER_DATA%|${printer_data}|" "${env_file}"
  fi
}

function print_mr_ip_list() {
  local ip count=${1} port=7125
  ip=$(hostname -I | cut -d" " -f1)

  for (( i=1; i <= count; i++ )); do
    echo -e "   ${cyan}● Instance ${i}:${white} ${ip}:${port}"
    port=$(( port + 1 ))
  done && echo
}

### introduced due to
### https://github.com/Arksine/moonraker/issues/349
### https://github.com/Arksine/moonraker/pull/346
function install_moonraker_polkit() {
  local POLKIT_LEGACY_FILE="/etc/polkit-1/localauthority/50-local.d/10-moonraker.pkla"
  local POLKIT_FILE="/etc/polkit-1/rules.d/moonraker.rules"
  local POLKIT_USR_FILE="/usr/share/polkit-1/rules.d/moonraker.rules"
  local legacy_file_exists
  local file_exists
  local usr_file_exists

  local has_sup
  local require_daemon_reload="false"

  legacy_file_exists=$(sudo find "${POLKIT_LEGACY_FILE}" 2> /dev/null)
  file_exists=$(sudo find "${POLKIT_FILE}" 2> /dev/null)
  usr_file_exists=$(sudo find "${POLKIT_USR_FILE}" 2> /dev/null)

  ### check for required SupplementaryGroups entry in service files
  ### write it to the service if it doesn't exist
  for service in $(moonraker_systemd); do
    has_sup="$(grep "SupplementaryGroups=moonraker-admin" "${service}")"
    if [[ -z ${has_sup} ]]; then
      status_msg "Adding moonraker-admin supplementary group to ${service} ..."
      sudo sed -i "/^Type=simple$/a SupplementaryGroups=moonraker-admin" "${service}"
      require_daemon_reload="true"
      ok_msg "Adding moonraker-admin supplementary group successfull!"
    fi
  done

  if [[ ${require_daemon_reload} == "true" ]]; then
    status_msg "Reloading unit files ..."
    sudo systemctl daemon-reload
    ok_msg "Unit files reloaded!"
  fi

  ### execute moonrakers policykit-rules script only if rule files do not already exist
  if [[ -z ${legacy_file_exists} && ( -z ${file_exists} || -z ${usr_file_exists} ) ]]; then
    status_msg "Installing Moonraker policykit rules ..."
    "${HOME}"/moonraker/scripts/set-policykit-rules.sh
    ok_msg "Moonraker policykit rules installed!"
  fi

  return
}

#==================================================#
#================ REMOVE MOONRAKER ================#
#==================================================#

function remove_moonraker_sysvinit() {
  [[ ! -e "${INITD}/moonraker" ]] && return

  status_msg "Removing Moonraker SysVinit service ..."
  sudo systemctl stop moonraker
  sudo update-rc.d -f moonraker remove
  sudo rm -f "${INITD}/moonraker" "${ETCDEF}/moonraker"
  ok_msg "Moonraker SysVinit service removed!"
}

function remove_moonraker_systemd() {
  [[ -z $(moonraker_systemd) ]] && return

  status_msg "Removing Moonraker Systemd Services ..."

  for service in $(moonraker_systemd | cut -d"/" -f5); do
    status_msg "Removing ${service} ..."
    sudo systemctl stop "${service}"
    sudo systemctl disable "${service}"
    sudo rm -f "${SYSTEMD}/${service}"
    ok_msg "Done!"
  done

  ### reloading units
  sudo systemctl daemon-reload
  sudo systemctl reset-failed
  ok_msg "Moonraker Services removed!"
}

function remove_moonraker_env_file() {
  local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/systemd\/moonraker\.env"
  files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | sort)

  if [[ -n ${files} ]]; then
    for file in ${files}; do
      status_msg "Removing ${file} ..."
      rm -f "${file}"
      ok_msg "${file} removed!"
    done
  fi
}

function remove_moonraker_logs() {
  local files regex="${HOME//\//\\/}\/([A-Za-z0-9_]+)\/logs\/moonraker\.log.*"
  files=$(find "${HOME}" -maxdepth 3 -regextype posix-extended -regex "${regex}" | sort)

  if [[ -n ${files} ]]; then
    for file in ${files}; do
      status_msg "Removing ${file} ..."
      rm -f "${file}"
      ok_msg "${file} removed!"
    done
  fi
}

function remove_legacy_moonraker_logs() {
  local files regex="moonraker(-[0-9a-zA-Z]+)?\.log(.*)?"
  files=$(find "${HOME}/klipper_logs" -maxdepth 1 -regextype posix-extended -regex "${HOME}/klipper_logs/${regex}" 2> /dev/null | sort)

  if [[ -n ${files} ]]; then
    for file in ${files}; do
      status_msg "Removing ${file} ..."
      rm -f "${file}"
      ok_msg "${file} removed!"
    done
  fi
}

function remove_moonraker_api_key() {
  ### remove legacy api key
  if [[ -e "${HOME}/.klippy_api_key" ]]; then
    status_msg "Removing legacy API Key ..."
    rm "${HOME}/.klippy_api_key"
    ok_msg "Done!"
  fi

  ### remove api key
  if [[ -e "${HOME}/.moonraker_api_key" ]]; then
    status_msg "Removing API Key ..."
    rm "${HOME}/.moonraker_api_key"
    ok_msg "Done!"
  fi
}

function remove_moonraker_dir() {
  [[ ! -d ${MOONRAKER_DIR} ]] && return

  status_msg "Removing Moonraker directory ..."
  rm -rf "${MOONRAKER_DIR}"
  ok_msg "Directory removed!"
}

function remove_moonraker_env() {
  [[ ! -d ${MOONRAKER_ENV} ]] && return

  status_msg "Removing moonraker-env directory ..."
  rm -rf "${MOONRAKER_ENV}"
  ok_msg "Directory removed!"
}

function remove_moonraker_polkit() {
  [[ ! -d ${MOONRAKER_DIR} ]] && return

  status_msg "Removing all Moonraker PolicyKit rules ..."
  "${MOONRAKER_DIR}"/scripts/set-policykit-rules.sh --clear
  ok_msg "Done!"
}

function remove_moonraker() {
  remove_moonraker_sysvinit
  remove_moonraker_systemd
  remove_moonraker_env_file
  remove_moonraker_logs
  remove_legacy_moonraker_logs
  remove_moonraker_api_key
  remove_moonraker_polkit
  remove_moonraker_dir
  remove_moonraker_env

  print_confirm "Moonraker was successfully removed!"
  return
}

#==================================================#
#================ UPDATE MOONRAKER ================#
#==================================================#

function update_moonraker() {
  do_action_service "stop" "moonraker"

  if [[ ! -d ${MOONRAKER_DIR} ]]; then
    clone_moonraker "${MOONRAKER_REPO}"
  else
    backup_before_update "moonraker"
    status_msg "Updating Moonraker ..."
    cd "${MOONRAKER_DIR}" && git pull
    ### read PKGLIST and install possible new dependencies
    install_moonraker_dependencies
    ### install possible new python dependencies
    "${MOONRAKER_ENV}"/bin/pip install -r "${MOONRAKER_DIR}/scripts/moonraker-requirements.txt"
  fi

  ### required due to https://github.com/Arksine/moonraker/issues/349
  install_moonraker_polkit || true

  ok_msg "Update complete!"
  do_action_service "restart" "moonraker"
}

#==================================================#
#================ MOONRAKER STATUS ================#
#==================================================#

function get_moonraker_status() {
  local sf_count status
  sf_count="$(moonraker_systemd | wc -w)"

  ### remove the "SERVICE" entry from the data array if a moonraker service is installed
  local data_arr=(SERVICE "${MOONRAKER_DIR}" "${MOONRAKER_ENV}")
  (( sf_count > 0 )) && unset "data_arr[0]"

  ### count+1 for each found data-item from array
  local filecount=0
  for data in "${data_arr[@]}"; do
    [[ -e ${data} ]] && filecount=$(( filecount + 1 ))
  done

  if (( filecount == ${#data_arr[*]} )); then
    status="Installed: ${sf_count}"
  elif (( filecount == 0 )); then
    status="Not installed!"
  else
    status="Incomplete!"
  fi

  echo "${status}"
}

function get_local_moonraker_commit() {
  [[ ! -d ${MOONRAKER_DIR} || ! -d "${MOONRAKER_DIR}/.git" ]] && return

  local commit
  cd "${MOONRAKER_DIR}"
  commit="$(git describe HEAD --always --tags | cut -d "-" -f 1,2)"
  echo "${commit}"
}

function get_remote_moonraker_commit() {
  [[ ! -d ${MOONRAKER_DIR} || ! -d "${MOONRAKER_DIR}/.git" ]] && return

  local commit
  cd "${MOONRAKER_DIR}" && git fetch origin -q
  commit=$(git describe origin/master --always --tags | cut -d "-" -f 1,2)
  echo "${commit}"
}

function compare_moonraker_versions() {
  local versions local_ver remote_ver
  local_ver="$(get_local_moonraker_commit)"
  remote_ver="$(get_remote_moonraker_commit)"

  if [[ ${local_ver} != "${remote_ver}" ]]; then
    versions="${yellow}$(printf " %-14s" "${local_ver}")${white}"
    versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
    # add moonraker to application_updates_available in kiauh.ini
    add_to_application_updates "moonraker"
  else
    versions="${green}$(printf " %-14s" "${local_ver}")${white}"
    versions+="|${green}$(printf " %-13s" "${remote_ver}")${white}"
  fi

  echo "${versions}"
}