#!/usr/bin/env sh
set -eu

# Make sure that PATH variable contains /usr/local/bin
PATH="/usr/local/bin:$PATH"

# Default installation paths
path_bin="/usr/local/bin"
path_bin_smp="$path_bin/smp-server"
path_bin_xftp="$path_bin/xftp-server"
path_bin_update="$path_bin/simplex-servers-update"
path_bin_uninstall="$path_bin/simplex-servers-uninstall"
path_bin_stopscript="$path_bin/simplex-servers-stopscript"

path_systemd="/etc/systemd/system"
path_systemd_smp="$path_systemd/smp-server.service"
path_systemd_xftp="$path_systemd/xftp-server.service"

# Temporary paths
path_tmp_bin="/tmp/simplex-servers"
path_tmp_bin_smp="$path_tmp_bin/smp-server"
path_tmp_bin_xftp="$path_tmp_bin/xftp-server"
path_tmp_bin_update="$path_tmp_bin/simplex-servers-update"
path_tmp_bin_uninstall="$path_tmp_bin/simplex-servers-uninstall"
path_tmp_bin_stopscript="$path_tmp_bin/simplex-servers-stopscript"
path_tmp_systemd_smp="$path_tmp_bin/smp-server.service"
path_tmp_systemd_xftp="$path_tmp_bin/xftp-server.service"

path_conf_etc='/etc/opt'
path_conf_info='/etc/opt/simplex-info'

GRN='\033[0;32m'
BLU='\033[1;36m'
YLW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
BLD='\033[1m'
UNDRL='\033[4m'

NL='
'

# Set VER globally and only once
VER="${VER:-latest}"

# Currently, XFTP default to v0.1.0, so it doesn't make sense to check its version

######################
### Misc functions ###
######################

# Checks "sanity" of downloaded thing, e.g. if it's really a script or binary
check_sanity() {
  path="$1"
  criteria="$2"

  case "$criteria" in
    string:*)
      pattern="$(printf '%s' "$criteria" | awk '{print $2}')"
      
      if grep -q "$pattern" "$path"; then
        sane=0
      else
        sane=1
      fi
      ;;
    file:*)
      pattern="$(printf '%s' "$criteria" | awk '{print $2}')"

      if file "$path" | grep -q "$pattern"; then
        sane=0
      else
        sane=1
      fi
      ;;
    *) printf 'Unknown criteria.\n'; sane=1 ;;
  esac

  unset path string

  return "$sane"
}

# Checks if old thing and new thing is different
change_check() {
  old="$1"
  new="$2"

  if [ -x "$new" ] || [ -f "$new" ]; then
    type="$(file $new)"
  else
    type='string'
  fi

  case "$type" in
    *script*|*text*)
      if diff -q "$old" "$new" > /dev/null 2>&1; then
        changed=1
      else
        changed=0
      fi
      ;;
    string)
      if [ "$old" = "$new" ]; then
        changed=1
      else
        changed=0
      fi
      ;;
  esac

  return "$changed"
}

##########################
### Misc functions END ###
##########################

#########################
### Support functions ###
#########################

# Sets local/remote versions and "apps" variables
check_versions() {
  # Sets:
  # - ver
  # - bin_url
  # - remote_version
  # - local_version
  # - apps

  case "$VER" in 
    latest)
      remote_version="$(curl --proto '=https' --tlsv1.2 -sSf -L https://api.github.com/repos/simplex-chat/simplexmq/releases/latest 2>/dev/null | grep -i "tag_name" | awk -F \" '{print $4}')"

      if [ -z "$remote_version" ]; then
        printf "${RED}Something went wrong when ${YLW}resolving the lastest version${NC}: either you don't have connection to Github or you're rate-limited.\n"
        exit 1
      fi
      ;;
    *)
      # Check if this version really exist
      ver_check="https://github.com/simplex-chat/simplexmq/releases/tag/${VER}"
      
      if curl -o /dev/null --proto '=https' --tlsv1.2 -sf -L "${ver_check}"; then
        remote_version="${VER}"
      else
        printf "Provided version ${BLU}%s${NC} ${RED}doesn't exist${NC}! Switching to ${BLU}latest${NC}.\n" "${VER}"
        VER='latest'

        # Re-execute check
        check_versions

        # Everything has been done, so return from the function
        return 0
      fi
      ;;
  esac

  # Links to scripts/configs
  bin_url="https://github.com/simplex-chat/simplexmq/releases/download/${remote_version}"
  scripts_url="https://raw.githubusercontent.com/simplex-chat/simplexmq/refs/tags/${remote_version}/scripts/main"
  scripts_url_systemd_smp="$scripts_url/smp-server.service"
  scripts_url_systemd_xftp="$scripts_url/xftp-server.service"
  scripts_url_update="$scripts_url/simplex-servers-update"
  scripts_url_uninstall="$scripts_url/simplex-servers-uninstall"
  scripts_url_stopscript="$scripts_url/simplex-servers-stopscript"

  set +u
  for i in smp xftp; do
    # Only check local directory where binaries are installed by the script
    if command -v "/usr/local/bin/$i-server" >/dev/null; then
      apps="$i $apps"
    fi
  done
  set -u

  if [ -z "$apps" ]; then
    printf "${RED}No simplex servers installed! Aborting.${NC}\n"
    exit 1
  fi

  for server in $apps; do
    # Check if info file is present
    if [ -f "$path_conf_info/release" ]; then
      # If present, source it
      . "$path_conf_info/release" 2>/dev/null

      # Check if line containing local version exists in file
      if grep -q "local_version_${server}" "$path_conf_info/release"; then
        # if exists, set the var
        eval "local_version=\$local_version_${server}"
      else
        # If it doesn't, append it to file
        printf "local_version_${server}=unset\n" >> "$path_conf_info/release"
        # And set it in script (so we don't have to re-source the file)
        eval "local_version_${server}=unset"
      fi
    else
      # If there isn't info file, populate it
      printf "local_version_${server}=unset\n" >> "$path_conf_info/release"
    fi
  done

  # Return 
  return 0
}

# Checks the distro and sets the urls variables
check_distro() {
  . /etc/os-release

  case "$VERSION_ID" in
    20.04|22.04) : ;;
    24.04) VERSION_ID='22.04' ;;
    *) printf "${RED}Unsupported Ubuntu version!${NC}\nPlease file Github issue with request to support Ubuntu %s: https://github.com/simplex-chat/simplexmq/issues/new\n" "$VERSION_ID" && exit 1 ;;
  esac

  version="$(printf '%s' "$VERSION_ID" | tr '.' '_')"
  arch="$(uname -p)"

  case "$arch" in
    x86_64) arch="$(printf '%s' "$arch" | tr '_' '-')" ;;
    *)  printf "${RED}Unsupported architecture!${NC}\nPlease file Github issue with request to support %s architecture: https://github.com/simplex-chat/simplexmq/issues/new" "$arch" && exit 1 ;;
  esac

  bin_url_smp="$bin_url/smp-server-ubuntu-${version}-${arch}"
  bin_url_xftp="$bin_url/xftp-server-ubuntu-${version}-${arch}"

  return 0
}

# General checks that must be performed on the initial execution of script
checks() {
  if [ "$(id -u)" -ne 0 ]; then
    printf "This script is intended to be run with root privileges. Please re-run script using sudo.\n"
    exit 1
  fi

  check_versions
  check_distro

  mkdir -p $path_conf_info $path_tmp_bin

  return 0
}

#############################
### Support functions END ###
#############################

######################
### Main functions ###
######################

# Downloads thing to directory and checks its sanity
download_thing() {
  thing="$1"
  path="$2"
  check_pattern="$3"
  err_msg="$4"
  
  if ! curl --proto '=https' --tlsv1.2 -sSf -L "$thing" -o "$path"; then
    printf "${RED}Something went wrong when downloading ${YLW}%s${NC}: either you don't have connection to Github or you're rate-limited.\n" "$err_msg"
    exit 1
  fi

  type="$(file "$path")"

  case "$type" in
    *script*|*executable*) chmod +x "$path" ;;
  esac

  if ! check_sanity "$path" "$check_pattern"; then
    printf "${RED}Something went wrong with downloaded ${YLW}%s${NC}: file is corrupted.\n" "$err_msg"
    exit 1
  fi

  return 0
}

# Downloads all necessary files to temp dir and set update messages for the menu
download_all() {
  download_thing "$scripts_url_update" "$path_tmp_bin_update" 'string: /usr/bin/env' 'Update script'
  if change_check "$path_tmp_bin_update" "$path_bin_update"; then
    msg_scripts="${msg_scripts+$msg_scripts, }${YLW}simplex-servers-update${NC}"
    msg_scripts_raw="${msg_scripts_raw+$msg_scripts_raw/}update"
  fi

  download_thing "$scripts_url_stopscript" "$path_tmp_bin_stopscript" 'string: /usr/bin/env' 'Stop script'
  if change_check "$path_tmp_bin_stopscript" "$path_bin_stopscript"; then
    msg_scripts="${msg_scripts+$msg_scripts, }${YLW}simplex-servers-stopscript${NC}"
    msg_scripts_raw="${msg_scripts_raw+$msg_scripts_raw/}stop"
  fi

  download_thing "$scripts_url_uninstall" "$path_tmp_bin_uninstall" 'string: /usr/bin/env' 'Uninstall script'
  if change_check "$path_tmp_bin_uninstall" "$path_bin_uninstall"; then
    msg_scripts="${msg_scripts+$msg_scripts, }${YLW}simplex-servers-uninstall${NC}"
    msg_scripts_raw="${msg_scripts_raw+$msg_scripts_raw/}uninstall"
  fi

  for i in $apps; do
    service="${i}-server"
    eval "scripts_url_systemd_final=\$scripts_url_systemd_${i}"
    eval "path_tmp_systemd_final=\$path_tmp_systemd_${i}"
    eval "path_systemd_final=\$path_systemd_${i}"
   
    download_thing "$scripts_url_systemd_final" "$path_tmp_systemd_final" 'string: [Unit]' "$service systemd service"
    if change_check "$path_tmp_systemd_final" "$path_systemd_final"; then
      msg_services="${msg_services+$msg_services, }${YLW}$service.service${NC}"
      msg_services_raw="${msg_services_raw+$msg_services_raw/}$service"
    fi
  done

  for i in $apps; do
    service="${i}-server"
    eval "local_version=\$local_version_${i}"

    if change_check "$local_version" "$remote_version"; then
      msg_bins="${msg_bins+$msg_bins$NL}  - ${YLW}$service${NC}: from ${BLU}$local_version${NC} to ${BLU}$remote_version${NC}"
      msg_bins_alt="${msg_bins_alt+$msg_bins_alt, }${YLW}$service${NC}"
      msg_bins_raw="${msg_bins_raw+$msg_bins_raw/}$service"
    fi
  done

  return 0
}

# Updates systemd and scripts. This function depends om variables from "download_all"
update_misc() {
  OLD_IFS="$IFS"

  IFS='/'
  for script in ${msg_scripts_raw:-}; do
    case "$script" in
      update)
        printf -- "- Updating update script..."
        mv "$path_tmp_bin_update" "$path_bin_update"
        printf "${GRN}Done!${NC}\n"
        printf -- "- Re-executing Update script..."
        exec env UPDATE_SCRIPT_DONE=1 VER="$remote_version" "$path_bin_update" "${selection}"
        ;;
      stop)
        printf -- "- Updating stopscript script..."
        mv "$path_tmp_bin_stopscript" "$path_bin_stopscript"
        printf "${GRN}Done!${NC}\n"
        ;;
      uninstall)
        printf -- "- Updating uninstall script..."
        mv "$path_tmp_bin_uninstall" "$path_bin_uninstall"
        printf "${GRN}Done!${NC}\n"
        ;;
    esac
  done

  for service in ${msg_services_raw:-}; do
    app="${service%%-*}"
    eval "path_systemd=\$path_systemd_${app}"
    eval "path_tmp_systemd=\$path_tmp_systemd_${app}"

    printf -- "- Updating %s service..." "$service"
    mv "$path_tmp_systemd" "$path_systemd"
    systemctl daemon-reload
    printf "${GRN}Done!${NC}\n"
  done

  IFS="$OLD_IFS"
  return 0
}

# Updates binaries. This function depends on variables from "download_all"
update_bins() {
  OLD_IFS="$IFS"

  IFS='/'
  for service in ${msg_bins_raw:-}; do
    app="${service%%-*}"
    eval "local_version=\$local_version_${app}"
    eval "bin_url_final=\$bin_url_${app}"
    eval "path_tmp_bin_final=\$path_tmp_bin_${app}"
    eval "path_bin_final=\$path_bin_${app}"

    # If systemd service is active
    if systemctl is-active --quiet "$service"; then
      printf -- "- Stopping %s service..." "$service"
      systemctl stop "$service"
      printf "${GRN}Done!${NC}\n"

      printf -- "- Updating ${YLW}%s${NC} from ${BLU}%s${NC} to ${BLU}%s${NC}..." "$service" "$local_version" "$remote_version"
      download_thing "$bin_url_final" "$path_tmp_bin_final" 'file: ELF' "$service"
      mv "$path_tmp_bin_final" "$path_bin_final"
      printf "${GRN}Done!${NC}\n"
      
      printf -- "- Starting %s service..." "$service"
      systemctl start "$service"
      printf "${GRN}Done!${NC}\n"
    else
      # If systemd service is NOT active
      printf -- "- Updating ${YLW}%s${NC} from ${BLU}%s${NC} to ${BLU}%s${NC}..." "$service" "$local_version" "$remote_version"
      download_thing "$bin_url_final" "$path_tmp_bin_final" 'file: ELF' "$service"
      mv "$path_tmp_bin_final" "$path_bin_final"
      printf "${GRN}Done!${NC}\n"
    fi

    # Don't forget to set version
    sed -i -- "s|local_version_${app}=.*|local_version_${app}='${remote_version}'|" "$path_conf_info/release"
  done

  IFS="$OLD_IFS"
  return 0
}

# Just download binaries
download_bins() {
  OLD_IFS="$IFS"

  IFS='/'
  for service in ${msg_bins_raw:-}; do
    app="${service%%-*}"
    eval "local_version=\$local_version_${app}"
    eval "bin_url_final=\$bin_url_${app}"
    eval "path_tmp_bin_final=\$path_tmp_bin_${app}"
    eval "path_bin_final=\$path_bin_${app}"

    printf -- "- Downloading ${YLW}%s${NC} binary..." "$service"
    download_thing "$bin_url_final" "$path_tmp_bin_final" 'file: ELF' "$service"
    printf "${GRN}Done!${NC}\n"
  done

  IFS="$OLD_IFS"
  return 0
}

menu_init_help() {
  menu_help="Update script for SimpleX servers and scripts.${NL}${NL}"
  menu_help="${menu_help}${BLD}${UNDRL}Usage:${NC} [<VARIABLE>] ${BLD}simplex-servers-update${NC}${NL}       [<VARIABLE>] ${BLD}simplex-servers-update${NC} [<SUBCOMMAND>]${NL}${NL}"
  menu_help="${menu_help}${BLD}${UNDRL}Subcommands:${NC}${NL}"
  menu_help_sub="  ${BLD}[a]ll${NC}		Update everything without confirmation${NL}"
  menu_help_sub="${menu_help_sub}  ${BLD}[b]inaries${NC}	Update binaries only without confirmation${NL}"
  menu_help_sub="${menu_help_sub}  ${BLD}[d]ownload${NC}	Download everything without updating${NL}"
  menu_help_sub="${menu_help_sub}  ${BLD}[h]elp${NC}	Print this message${NL}${NL}"
  menu_help="${menu_help}${menu_help_sub}"
  menu_help="${menu_help}${BLD}${UNDRL}Variables:${NC}${NL}"
  menu_help="${menu_help}  ${BLD}VER=v3.2.1-beta.0${NC}	Update binaries to specified version${NL}"

  return 0
}

menu_init() {
  menu_end="${RED}x${NC}) Exit${NL}${NL}Selection: "
  menu_option_download="${GRN}d${NC}) Download files only${NL}"

  if [ -n "${msg_scripts:-}" ]; then
    menu_option_misc_raw="${menu_option_misc_raw+${menu_option_misc_raw}${NL}}  - script(s): ${msg_scripts}"
  fi

  if [ -n "${msg_services:-}" ]; then
    menu_option_misc_raw="${menu_option_misc_raw+${menu_option_misc_raw}${NL}}  - systemd service file(s): ${msg_services}"
  fi

  menu_option_all="${GRN}a${NC}) Update all: ${BLU}(recommended)${NC}${NL}${menu_option_misc_raw+${menu_option_misc_raw}${NL}}${msg_bins+${msg_bins}${NL}}"

  if [ -n "${msg_bins:-}" ]; then
    menu_option_bins="${GRN}b${NC}) Update server binaries: ${msg_bins_alt}${NL}"
  fi

  # Abort early if there's neither update binaries, nor update scripts options
  if [ -z "${menu_option_bins:-}" ] && [ -z "${menu_option_misc_raw:-}" ]; then
    printf "${YLW}Everything is up-to-date${NC}.\n"
    exit 0
  fi

  menu="${menu_option_all}${menu_option_bins:-}${menu_option_download}${menu_end}"

  return 0
}

options_parse() {
  selection="$1"
  
  case "$selection" in
    a|all)
      check=0
      if [ -z "${menu_option_misc_raw:-}" ] && [ -z "${menu_option_bins:-}" ]; then
        printf "${YLW}Everything is up-to-date${NC}.\n"
      else
        if [ -n "${menu_option_misc_raw:-}" ]; then
          update_misc
        fi
        if [ -n "${menu_option_bins:-}" ]; then
          update_bins
        fi
      fi
      ;;
    b|binaries)
      check=0
      if [ -n "${menu_option_bins:-}" ]; then
        update_bins
      else
        printf "${YLW}Binaries is up-to-date${NC}.\n"
      fi
      ;;
    d|download)
      check=0
      if [ -n "${menu_option_bins:-}" ]; then
        download_bins
      fi

      printf "\n${YLW}Scripts${NC}/${YLW}services${NC}/${YLW}binaries${NC} has been downloaded to ${BLU}%s${NC}\n" "$path_tmp_bin"
      ;;
    x)
      check=0
      ;;
    *)
      check=1
      ;;
  esac

  return "$check"
}

##########################
### Main functions END ###
##########################

############
### Init ###
############

main() {
  # Early hook to print Done after script re-execution
  if [ -n "${UPDATE_SCRIPT_DONE:-}" ]; then
    printf "${GRN}Done!${NC}\n"
  fi

  # Early help menu
  menu_init_help

  case "${1:-}" in
    h|help)
      printf '%b' "$menu_help"
      exit 0
      ;;
  esac

  checks
  download_all
  menu_init

  onetime=0
  while true; do
    if [ "$onetime" = 0 ]; then
      onetime=1

      if [ -n "${1:-}" ]; then
        selection="$1"
      else
        printf '%b' "$menu"
        read selection
      fi
    else
      read selection
    fi
    
    if options_parse "$selection"; then
      break
    else
      # Rerender whole menu if the first non-interactive option was bogus
      if [ -n "${1:-}" ]; then
        onetime=0
        shift 1
      else
        # Erase last line
        printf '\e[A\e[K'
        # Only rerended selection
        printf 'Selection: '
      fi
    fi
  done
}

main "$@"
