#!/bin/bash

# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA  02111-1307  USA



### Functions ###
temp=$(dirname "$0")
source "$temp/functions-common.sh"
source "$temp/functions-test.sh"
unset temp

section_is_enabled ()
{
  local regex section parent

  # all sections are enabled
  [ -z "$sections" ] && return 0

  # this section or at least one of its subsections is enabled
  [ "$1" = '--sub' ] && {
    regex=" $2(.[^ ]+)? "
    [[ $sections =~ $regex ]] && return 0
  }

  # this section is enabled (by itself or by one of its parents)
  parent=$1
  while [ "$section" != "$parent" ]; do
    section=$parent
    [[ $sections == *" $section "* ]] && return 0
    parent=${section%.*}
  done

  # this section is not enabled
  return 1
}

create_tempfiles ()
{
  local -i i n=${#tempfiles[@]}
  local    tempfile

  (($1 > 0)) || abort "${FUNCNAME[0]}(): Wrong argument '$1'"

  # create as many new temporary files as needed
  for ((i = $1 - n; i > 0; i--)); do
    tempfile=$(mktemp) || abort 'file' && tempfiles+=("$tempfile")
    base64 /dev/urandom | sed 's/[a-d]/ /g; s/[e-h]/\t/g; 11q' >"$tempfile"
  done
}



### Preamble ###

# Global variables
declare -r timeout='timeout -v -k 5 -- 20' script_name=${0##*/} \
           idle=$(echo "/org/xfce/mousepad: org.freedesktop.DBus.Properties.PropertiesChanged" \
                       "('org.gtk.Application', {'Busy': <false>}, @as [])")
declare -a ignored warnings errors tempfiles
declare -i quiet=0 n_warns=0 n_errs=0 n_cmds=0 session_restore=0 gdbus_pid
declare    mousepad logfile logdir temp_logfile sections session_backup pidwait

# Option parsing
temp=$(getopt -o 'hqc:l:s:i:' \
  -l 'help,quiet,command:,logfile:,section:,list-sections,ignore:' \
  -n "$0" -- "$@"
) || exit 1

eval set -- "$temp" && unset temp

section_list=(
  'non-gui'
  'simple-gui' 'simple-gui.no-file' 'simple-gui.one-file' 'simple-gui.multi-file'
               'simple-gui.session-restore'
  'gsettings' 'gsettings.no-file' 'gsettings.preferences' 'gsettings.one-file'
              'gsettings.multi-tab' 'gsettings.multi-window'
  'actions' 'actions.off-menu' 'actions.file' 'actions.edit' 'actions.search'
            'actions.view' 'actions.document' 'actions.help'
)

while [ "$1" != '--' ]; do
  case $1 in
  '-h'|'--help')
    cat <<EOF
Usage: $script_name [OPTION]...
  -h, --help		Display this help and exit
  -q, --quiet		Do not print any progress indication
  -c, --command=COMMAND	Path to the Mousepad executable to test (default: mousepad in \$PATH)
  -l, --logfile=LOGFILE	Path to the logfile for writing the test report (default: "\$PWD/$script_name.log")
  -s, --section=SECTION	Test section to run (can be used several times, default: all sections)
      --list-sections	List possible test sections to run
  -i, --ignore=PATTERN	Ignore lines containing the ERE PATTERN in Mousepad output on stderr (can be used several times)

Exit code:
  0 when everything is fine
  1 when aborted
  2 when some commands issued warnings
  3 when some commands failed
  5 when 2 and 3
EOF
    exit 0
  ;;
  '-q'|'--quiet')
    quiet=1
  ;;
  '-c'|'--command')
    shift
    mousepad=$1
  ;;
  '-l'|'--logfile')
    shift
    logfile=$1
  ;;
  '-s'|'--section')
    shift

    [[ " ${section_list[*]} " != *" $1 "* ]] && {
      printerr 'error' "Invalid section name '$1'"
      exit 1
    }

    sections+=" $1 "
  ;;
  '--list-sections')
    printf '%s\n' "${section_list[@]}"
    exit 0
  ;;
  '-i'|'--ignore')
    shift
    ignored+=('-e')
    ignored+=("$1")
  ;;
  *)
    printerr 'error' "Invalid option '$1'"
    exit 1
  ;;
  esac

  shift
done

unset section_list

# Checks
mousepad=${mousepad:-'mousepad'}
which "$mousepad" &>/dev/null || {
  printerr 'error' "Command not found '$mousepad'"
  exit 1
}
mousepad=$(realpath "$(which "$mousepad")")

# pwait was renamed to pidwait in procps-ng 4.0.0 (2022-03-22)
pidwait='pidwait'
which "$pidwait" &>/dev/null || {
  pidwait='pwait'
  which "$pidwait" &>/dev/null || {
    printerr 'error' "Command not found 'pidwait' or 'pwait'"
    exit 1
  }
}

logfile=${logfile:-"$PWD/$script_name.log"}
logdir=$(dirname "$logfile")
[ -d "$logfile" ] && {
  printerr 'error' "'$logfile' is a directory"
  exit 1
}
[ ! -w "$logdir" ] && {
  printerr 'error' "Directory '$logdir' is read-only"
  exit 1
}

[ -n "$(pgrep -x mousepad)" ] && {
  printerr 'error' 'Mousepad is running, all Mousepad instances should be terminated before running this script'
  exit 1
}

temp="$(dirname "$mousepad")/../lib/mousepad/plugins"
if [ -d "$temp" ]; then
  [ -f "$temp/libmousepad-plugin-test.so" ] || {
    printerr 'error' 'The Mousepad test plugin is not available, be sure to build Mousepad with `--enable-plugin-test`'
    exit 1
  }
else
  printerr 'warning' 'Unable to find the Mousepad plugin directory: the test plugin might not be available'
fi
unset temp

temp=$(gsettings get 'org.xfce.mousepad.state.application' 'enabled-plugins')
[[ $temp == *"'mousepad-plugin-test'"* ]] || {
  if [ "$temp" = '@as []' ]; then
    gsettings set 'org.xfce.mousepad.state.application' 'enabled-plugins' \
      "['mousepad-plugin-test']"
  else
    gsettings set 'org.xfce.mousepad.state.application' 'enabled-plugins' \
      "[${temp//[\[\]]/}, 'mousepad-plugin-test']"
  fi

  printerr 'message' 'The Mousepad test plugin has been automatically enabled'
}
unset temp

[ -z "$sections" ] && {
  read -s -n1 -p 'Are you sure you want to run all the test sections, this could take a while? ([y]es, [N]o) '
  echo $REPLY
  [[ $REPLY != [yY] ]] && exit 0 || echo
}

# From now on, we read by default from gdbus and write by default in the logfile
# NB: the '&' is to suppress the "Terminated" message after killing gdbus
exec 0< <(gdbus monitor --session --dest 'org.xfce.mousepad' &) 1>"$logfile"
gdbus_pid=$(pgrep -x -n gdbus)

# Try to be clean in case of a problem
trap 'abort "INT signal received"' INT
trap 'abort "Undetermined error"' ERR

# Set some global constants
readonly quiet mousepad logfile logdir sections ignored gdbus_pid pidwait



### Main ###

# Backup and adapt session settings if needed
shopt -s nullglob extglob
[ "$(gsettings get org.xfce.mousepad.preferences.file session-restore)" != "'never'" ] && {
  session_restore=1
  session_backup="$logdir/$script_name.gsettings.session.bak"
  : >"$session_backup" || abort 'file'

  # backup and reset session array
  gsettings get org.xfce.mousepad.state.application session >"$session_backup"
  gsettings reset org.xfce.mousepad.state.application session

  # backup and set autosave timer to 1 second, to trigger as many operations as possible
  gsettings get org.xfce.mousepad.preferences.file autosave-timer >>"$session_backup"
  gsettings set org.xfce.mousepad.preferences.file autosave-timer 1

  # rename autosave files if any, link them as backup files
  for f in "${XDG_DATA_HOME:-$HOME/.local/share}/Mousepad/autosave-"+([0-9]); do
    mv "$f" "$f.bak" && ln -s "$f.bak" "$logdir/${f##*/}.bak"
  done
}

# Non-GUI commands
section_is_enabled 'non-gui' && {
  echo '*** Non-GUI commands ***' | duperr
  test_non_gui --list-encodings
  test_non_gui --version
}

# Simple GUI commands (`mousepad --quit` is implicitly tested each time, except with
# `--disable-server`)
section_is_enabled --sub 'simple-gui' \
  && printf '\n%s\n' '*** Simple GUI commands ***' | duperr

section_is_enabled 'simple-gui.no-file' && {
  echo '- No file -' | duperr
  test_simple_gui
  test_simple_gui --preferences
  test_simple_gui --disable-server
}

section_is_enabled 'simple-gui.one-file' && {
  echo '- One file -' | duperr
  create_tempfiles 1
  test_simple_gui "${tempfiles[0]}"
  test_simple_gui --encoding -- "${tempfiles[0]}"
  test_simple_gui --encoding=ISO-8859-15 -- "${tempfiles[0]}"
  test_simple_gui --line=2 --column=3 -- "${tempfiles[0]}"
}

section_is_enabled 'simple-gui.multi-file' && {
  echo '- Multi-file -' | duperr
  create_tempfiles 2
  test_simple_gui --opening-mode=tab -- "${tempfiles[@]}"
  test_simple_gui --opening-mode=window -- "${tempfiles[@]}"
}

section_is_enabled 'simple-gui.session-restore' && ((session_restore)) && {
  echo '- Session restore -' | duperr

  # restore three files in two windows and open another file in a third window
  create_tempfiles 4
  gsettings set org.xfce.mousepad.state.application session \
    "['1;;file://${tempfiles[0]}', '1;;+file://${tempfiles[1]}', '2;;+file://${tempfiles[2]}']"
  test_simple_gui --opening-mode=window -- "${tempfiles[3]}"
}

# GSettings (setting change from cli when mousepad is running)
section_is_enabled --sub 'gsettings' \
  && printf '\n%s\n' '*** GSettings commands ***' | duperr

section_is_enabled 'gsettings.no-file' && {
  echo '- No file -' | duperr
  test_gsettings
}

section_is_enabled 'gsettings.preferences' && {
  echo '- Preferences -' | duperr
  test_gsettings --preferences
}

section_is_enabled 'gsettings.one-file' && {
  echo '- One file -' | duperr
  create_tempfiles 1
  test_gsettings "${tempfiles[0]}"
}

section_is_enabled 'gsettings.multi-tab' && {
  echo '- Multi-tab -' | duperr
  create_tempfiles 2
  test_gsettings --opening-mode=tab -- "${tempfiles[@]}"
}

section_is_enabled 'gsettings.multi-window' && {
  echo '- Multi-window -' | duperr
  create_tempfiles 2
  test_gsettings --opening-mode=window -- "${tempfiles[@]}"
}

# Actions (for the moment (Mousepad 0.5.5) only window actions are relevant: application actions
# are tested via gsettings or command line options above)
section_is_enabled --sub 'actions' && {
  create_tempfiles 1
  printf '\n%s\n' '*** Action commands ***' | duperr
}

section_is_enabled 'actions.off-menu' && {
  echo '- Off-menu -' | duperr
  test_actions --off-menu "${tempfiles[0]}"
}

section_is_enabled 'actions.file' && {
  echo '- File menu -' | duperr
  test_actions --file "${tempfiles[0]}"
}

section_is_enabled 'actions.edit' && {
  echo '- Edit menu -' | duperr
  test_actions --edit "${tempfiles[0]}"
}

section_is_enabled 'actions.search' && {
  echo '- Search menu -' | duperr
  test_actions --search "${tempfiles[0]}"
}

section_is_enabled 'actions.view' && {
  echo '- View menu -' | duperr
  test_actions --view "${tempfiles[0]}"
}

section_is_enabled 'actions.document' && {
  echo '- Document menu -' | duperr
  test_actions --document "${tempfiles[0]}"
}

section_is_enabled 'actions.help' && {
  echo '- Help menu -' | duperr
  test_actions --help "${tempfiles[0]}"
}

# Restore session settings if needed
[ -n "$session_backup" ] && {
  exec 3<"$session_backup"

  read -r -u 3
  gsettings set org.xfce.mousepad.state.application session "$REPLY"
  read -r -u 3
  gsettings set org.xfce.mousepad.preferences.file autosave-timer "$REPLY"

  exec 3<&-
  rm "$session_backup"

  for f in "${XDG_DATA_HOME:-$HOME/.local/share}/Mousepad/autosave-"+([0-9]).bak; do
    mv "$f" "${f%.bak}" && rm "$logdir/${f##*/}"
  done
}

# Outcome
{
  printf '\n%s\n' '*** Outcome ***'
  ((n_warns > 0)) && echo "Some commands issued warnings: numbers ${warnings[*]}"
  ((n_errs > 0)) && echo "Some commands failed: numbers ${errors[*]}"
  ((n_warns == 0 && n_errs == 0)) && echo 'Nothing wrong to report'
} | duperr

# wait a bit until the above has been written on stderr
sleep 0.1
printerr "Full test report written in $logfile"

cleanup

exit $(( 2 * (n_warns > 0) + 3 * (n_errs > 0) ))
