#!/bin/bash

## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

set -o errexit
set -o nounset
set -o errtrace
set -o pipefail

# shellcheck source=../libexec/helper-scripts/get_colors.sh
source /usr/libexec/helper-scripts/get_colors.sh

# shellcheck source=../libexec/helper-scripts/log_run_die.sh
source /usr/libexec/helper-scripts/log_run_die.sh

# shellcheck source=../libexec/helper-scripts/as_root.sh
source /usr/libexec/helper-scripts/as_root.sh

log_level=info

log info "Start"

my_base_name="$(basename -- "$0")"

config_dir='/etc/wlr-resize-watcher.d'
target_config_file="${config_dir}/45_configure-dynamic-resolution.conf"

default_resolution="1920x1080"

## Collect user-friendly summary messages here and print them later (near the bottom).
declare -a summary_notice_messages=()

add_summary_notice() {
  local msg
  msg="${1:-}"
  summary_notice_messages+=("${msg}")
}

generated_file_header() {
  cat <<EOF
## Auto-generated file (do not edit)
##
## This file was created by: $0
##
## Please do NOT edit this file by hand.
## This tool may rewrite/replace it at any time.
## Also, even a small typo here can stop the settings from working.
##
## Want to change something?
## Run this tool again:
##   sudo $my_base_name
##
## Deleting this file is OK.
##   sudo safe-rm -- /etc/wlr-resize-watcher.d/45_configure-dynamic-resolution.conf
##
## A new clean file will be created the next time you run this tool.
##
## Want to add your own custom settings?
## Put them here (this file is meant for humans):
##   /etc/wlr-resize-watcher.d/50_user.conf
##
## Default settings (for reference):
##   /etc/wlr-resize-watcher.d/30_default.conf
##
## Help / documentation:
##   https://www.whonix.org/wiki/resize
EOF
}

validate_bool_opt() {
  local orig_bool_opt bool_opt_lowercase

  orig_bool_opt="${1:-}"
  bool_opt_lowercase="${orig_bool_opt,,}"
  case "${bool_opt_lowercase}" in
    'true'|'t'|'y'|'yes'|'on'|'1')
      printf '%s\n' "true"
      ;;
    'false'|'f'|'n'|'no'|'off'|'0')
      printf '%s\n' "false"
      ;;
    *)
      true
      ;;
  esac
}

validate_resolution_opt() {
  local orig_resolution_opt resolution_opt_lowercase

  orig_resolution_opt="${1:-}"
  resolution_opt_lowercase="${orig_resolution_opt,,}"
  if [[ "${resolution_opt_lowercase}" =~ ^[0-9]+x[0-9]+$ ]]; then
    printf '%s\n' "${resolution_opt_lowercase}"
  fi
  ## Don't print anything if resolution_opt_lowercase fails the
  ## validation regex.
}

write_config_value() {
  local opt_key opt_val config_file_contents

  opt_key="${1:-}"
  opt_val="${2:-}"
  if [ -f "${target_config_file}" ]; then
    config_file_contents="$(stcatn "${target_config_file}")"
  else
    config_file_contents=""
  fi

  ## Ensure the file always starts with the generated header comment.
  ## If the file exists and already has the header, keep it; otherwise add it.
  if ! grep -qF "## This file was created by:" <<< "${config_file_contents}"; then
    config_file_contents="$(generated_file_header)"$'\n\n'"${config_file_contents}"
  fi

  ## Remove any existing line for this key (only real config lines, not comments).
  config_file_contents="$(sed "/^${opt_key}=/d" <<< "${config_file_contents}")"

  ## Bash strips the file's trailing newline, so we have to put it back, but
  ## only if we loaded a non-empty file to begin with
  if [ -n "${config_file_contents}" ]; then
    config_file_contents+=$'\n'
  fi

  ## Append the config option and a new trailing newline.
  config_file_contents+="${opt_key}=${opt_val}"$'\n'

  overwrite "${target_config_file}" "${config_file_contents}" >/dev/null
}

set_bool_opt() {
  local config_option_id orig_bool_opt bool_opt

  config_option_id="${1:-}"
  orig_bool_opt="${2:-}"

  bool_opt="$(validate_bool_opt "${orig_bool_opt}")"
  if [ -z "${bool_opt}" ]; then
    log error "Option '${orig_bool_opt}' is invalid. Please use: yes/no, true/false, on/off, or 1/0."
    printf '%s\n' ""
    return 1
  fi
  write_config_value "${config_option_id}" "${bool_opt}"
  log info "Saved: ${config_option_id}=${bool_opt}"
  return 0
}

set_default_resolution_both() {
  ## Writes BOTH keys, because users are either Xen or non-Xen.
  ## No need to ask them twice.
  local orig_resolution_opt resolution_opt

  orig_resolution_opt="${1:-}"

  resolution_opt="$(validate_resolution_opt "${orig_resolution_opt}")"
  if [ -z "${resolution_opt}" ]; then
    log error "'${orig_resolution_opt}' does not look like a screen resolution. Please use something like 1920x1080."
    printf '%s\n' ""
    return 1
  fi

  write_config_value "standard_default_resolution" "\"${resolution_opt}\""
  write_config_value "small_default_resolution" "\"${resolution_opt}\""

  log info "Saved: standard_default_resolution=${resolution_opt}"
  log info "Saved: small_default_resolution=${resolution_opt}"
  return 0
}

announce_change() {
  local config_option_id normalized_value on_text off_text res_text msg

  config_option_id="${1:-}"
  normalized_value="${2:-}"

  on_text="${green}${bold}ON${nocolor}"
  off_text="${red}${bold}OFF${nocolor}"
  res_text="${cyan}${bold}${normalized_value}${nocolor}"

  case "${config_option_id}" in
    'enable_dynamic_resolution')
      if [ "${normalized_value}" = "true" ]; then
        msg="Dynamic resolution has been turned ${on_text}."
      else
        msg="Dynamic resolution has been turned ${off_text}."
      fi
      add_summary_notice "${msg}"
      ;;
    'warn_on_dynamic_resolution_refuse')
      if [ "${normalized_value}" = "true" ]; then
        msg="Warning messages have been turned ${on_text}."
      else
        msg="Warning messages have been turned ${off_text}."
      fi
      add_summary_notice "${msg}"
      ;;
    'default_resolution')
      add_summary_notice "Your default screen resolution has been set to ${res_text}."
      ;;
    *)
      add_summary_notice "Settings have been updated."
      ;;
  esac
}

configure_dynamic_resolution() {
  local config_option_list config_option_id_list config_option \
    config_option_id config_option_idx selected_idx opt_arg bool_opt resolution_opt did_change normalized_value

  as_root

  if ! [ -d "${config_dir}" ]; then
    mkdir --parents -- "${config_dir}" || {
      log error "Could not create the configuration folder '${config_dir}'."
      return 1
    }
  fi
  if ! [ -f "${target_config_file}" ] && [ -e "${target_config_file}" ]; then
    log error "'${target_config_file}' exists but is not a regular file."
    return 1
  fi

  config_option_list=(
    'Turn dynamic resolution on or off'
    'Turn warning messages on or off (when dynamic resolution is refused)'
    'Set the default screen resolution'
    'Exit'
  )

  ## NOTE: small_default_resolution is deprecated from the UI.
  ## The tool still writes it in the config file together with standard_default_resolution.
  config_option_id_list=(
    'enable_dynamic_resolution'
    'warn_on_dynamic_resolution_refuse'
    'default_resolution'
    'exit'
  )

  printf '%s\n' ""

  while true; do
    printf '%s\n' 'What do you want to change?'
    printf '%s\n' ""
    config_option_idx=0
    for config_option in "${config_option_list[@]}"; do
      (( config_option_idx++ )) || true
      printf '%s\n' "  ${config_option_idx}: ${config_option}"
    done
    printf '%s\n' ""
    log question "Type a number and press Enter. Tip: just press Enter to turn ON dynamic resolution."
    read -r selected_idx opt_arg

    ## Default: pressing Enter does the most common thing (enable dynamic resolution).
    if [ -z "${selected_idx}" ]; then
      selected_idx="1"
      opt_arg="true"
    fi

    if ! [[ "${selected_idx}" =~ ^[0-9]+$ ]]; then
      log error "Please type a number (1 to ${#config_option_list[@]})."
      printf '%s\n' ""
      continue
    fi

    ## selected_idx starts at 1, not 0, so we have to adjust for that
    if (( selected_idx < 1 )) \
      || (( (selected_idx - 1) >= ${#config_option_list[@]} )); then
      log error "That number is out of range. Please choose 1 to ${#config_option_list[@]}."
      printf '%s\n' ""
      continue
    fi

    config_option_id="${config_option_id_list[selected_idx - 1]}"
    did_change=false

    case "${config_option_id}" in
      'enable_dynamic_resolution')
        if [ -n "${opt_arg:-}" ]; then
          bool_opt="${opt_arg}"
        else
          log question "Turn ON dynamic resolution? [Y/n] (Press Enter for Yes)"
          read -r bool_opt
          bool_opt="${bool_opt:-y}"
        fi
        normalized_value="$(validate_bool_opt "${bool_opt}")"
        if [ -z "${normalized_value}" ]; then
          log error "Option '${bool_opt}' is invalid. Please use: yes/no, true/false, on/off, or 1/0."
          printf '%s\n' ""
          continue
        fi
        if ! set_bool_opt "enable_dynamic_resolution" "${normalized_value}"; then
          continue
        fi
        announce_change "enable_dynamic_resolution" "${normalized_value}"
        did_change=true
        ;;

      'warn_on_dynamic_resolution_refuse')
        if [ -n "${opt_arg:-}" ]; then
          bool_opt="${opt_arg}"
        else
          log question "Show warning messages if dynamic resolution is refused? [y/N] (Press Enter for No)"
          read -r bool_opt
          bool_opt="${bool_opt:-n}"
        fi
        normalized_value="$(validate_bool_opt "${bool_opt}")"
        if [ -z "${normalized_value}" ]; then
          log error "Option '${bool_opt}' is invalid. Please use: yes/no, true/false, on/off, or 1/0."
          printf '%s\n' ""
          continue
        fi
        if ! set_bool_opt "warn_on_dynamic_resolution_refuse" "${normalized_value}"; then
          continue
        fi
        announce_change "warn_on_dynamic_resolution_refuse" "${normalized_value}"
        did_change=true
        ;;

      'default_resolution')
        if [ -n "${opt_arg:-}" ]; then
          resolution_opt="${opt_arg}"
        else
          log question "Enter a resolution like 1920x1080. Press Enter to use ${default_resolution}."
          read -r resolution_opt
          resolution_opt="${resolution_opt:-$default_resolution}"
        fi

        normalized_value="$(validate_resolution_opt "${resolution_opt}")"
        if [ -z "${normalized_value}" ]; then
          log error "'${resolution_opt}' does not look like a screen resolution. Please use something like 1920x1080."
          printf '%s\n' ""
          continue
        fi

        if ! set_default_resolution_both "${normalized_value}"; then
          continue
        fi
        announce_change "default_resolution" "${normalized_value}"
        did_change=true
        ;;

      'exit')
        log notice "No action taken. Exiting."
        exit 0
        ;;

      *)
        ## This should be unreachable.
        log error "Internal error. Unreachable code hit!"
        exit 1
        ;;
    esac

    if [ "${did_change}" = true ]; then
      add_summary_notice "All set. If you want to change something else, just start this configuration tool again."
      printf '%s\n' ""
      return 0
    fi
  done
}

command -v id >/dev/null
command -v stcatn >/dev/null
command -v sed >/dev/null
command -v overwrite >/dev/null
command -v grep >/dev/null

log info "User documentation:
https://www.whonix.org/wiki/resize"

configure_dynamic_resolution

log info "Saved settings file '${target_config_file}':"
printf '%s\n' "########################################"
stcatn "${target_config_file}"
printf '%s\n' "########################################"
printf '%s\n' ""

## Print the important, user-friendly summary here (near the bottom).
if (( ${#summary_notice_messages[@]} > 0 )); then
  for msg in "${summary_notice_messages[@]}"; do
    log notice "${msg}"
  done
fi

if [ -n "${SUDO_USER-}" ]; then
  log info "Restarting 'wlr-resize-watcher.service'..."
  xdg_runtime_dir="/run/user/$(id --user -- "$SUDO_USER")"
  log_run info sudo --user="$SUDO_USER" -- env XDG_RUNTIME_DIR="$xdg_runtime_dir" systemctl --user restart wlr-resize-watcher.service
else
  log notice "Cannot automatically restart the wlr-resize-watcher.service because the environment variable SUDO_USER is unavailable."
  log warn "To apply changes: reboot, or run this (without sudo) as your normal user:"
  log warn "systemctl --user restart wlr-resize-watcher.service"
fi

log notice "Success."
