#!/bin/bash

# Copyright (c) 2019 Jan Ziak (http://atom-symbol.net). All rights reserved.
# Use of this file is governed by the BSD-3-Clause license.

# This script is an automated benchmarking tool for Middle-earth: Shadow of Mordor.
# It prints the locations of .csv files containing the benchmark results.
#
# The resolution and window/fullscreen settings should be configured before
# running this script.
#
# Tested with linux_vulkan_beta version of the game.

# Tunable parameters:
NUM_RUNS=3                # Number of times to run the benchmark, excluding the first warmup run
MENU_TIME=20              # Number of seconds it takes for the game to first enter the main menu
WINDOW_CLOSE_KEY="Alt+F4" # The key combination used to close a window
GAME_INSTALL_DIR="$HOME/.steam/root/steamapps/common/ShadowOfMordor"
# End of tunable parameters




# Cannot uncomment the following line because xdotool is unreliable
#set -e -o pipefail

LOG_FILE="$PWD/$(basename "$0").log"
HAVE_RENDER_CONFIG_BACKUP=false
GAME_IS_RUNNING=false

function err() {
	echo "error: "$@""
	echo "error: "$@"" &>> "$LOG_FILE"
	if $GAME_IS_RUNNING; then
		notify-send --urgency=critical "error: ""$@"
	fi
	wait # Wait until the game, which is running in background, terminates
	exit 1
}
function warn() {
	echo "warning: "$@""
	echo "warning: "$@"" &>> "$LOG_FILE"
	if $GAME_IS_RUNNING; then
		notify-send --urgency=critical "warning: ""$@"
	fi
}

{ echo; date; } &>> "$LOG_FILE" || err "failed to open $LOG_FILE"

if ! cd "$GAME_INSTALL_DIR"; then
	err "failed to change working directory to \"$GAME_INSTALL_DIR\""
fi

function cleanup() {
	if $HAVE_RENDER_CONFIG_BACKUP; then
		HAVE_RENDER_CONFIG_BACKUP=false
		if ! mv -f "$RENDER_CONFIG_BACKUP" "$BECHMARK_RESULTS_DIR/render.cfg"; then
			warn "failed to restore \"$BECHMARK_RESULTS_DIR/render.cfg\""
		fi
	fi
}
trap cleanup EXIT

function signal_handler() {
	cleanup
	exit 1
}
trap signal_handler SIGINT SIGTERM

function usage() {
	echo "Usage: "$(basename "$0")" [QUALITY]"
	echo
	echo "Where QUALITY is one of:"
	echo "  lowest"
	echo "  low"
	echo "  medium"
	echo "  high"
	echo "  veryhigh"
	echo "  ultra"
	echo
	echo "If QUALITY isn't specified then the current one is used."
}

CHANGE_RENDER_CONFIG=false
case $# in
	0)
		;;
	1)
		case ${1,,} in 
			lowest) CFG_FILE=render-0-lowest.cfg ;;
			low) CFG_FILE=render-1-low.cfg ;;
			medium) CFG_FILE=render-2-medium.cfg ;;
			high) CFG_FILE=render-3-high.cfg ;;
			veryhigh) CFG_FILE=render-4-veryhigh.cfg ;;
			ultra) CFG_FILE=render-5-ultra.cfg ;;
			*) echo "error: invalid arguments"; echo; usage; exit 1 ;;
		esac
		CHANGE_RENDER_CONFIG=true
		;;
	*)
		usage; exit 1
		;;
esac

BECHMARK_RESULTS_DIR="$HOME/.local/share/feral-interactive/ShadowOfMordor/AppData/WB Games/Shadow of Mordor"
if [ -h "$BECHMARK_RESULTS_DIR" ]; then
	BECHMARK_RESULTS_DIR="$(realpath "$BECHMARK_RESULTS_DIR")"
fi
if [ ! -e "$BECHMARK_RESULTS_DIR" ]; then
	err "\"$BECHMARK_RESULTS_DIR\" does not exist"
elif [ ! -d "$BECHMARK_RESULTS_DIR" ]; then
	err "\"$BECHMARK_RESULTS_DIR\" isn't a directory"
fi

if $CHANGE_RENDER_CONFIG; then
	if [ ! -e "./$CFG_FILE" ]; then
		err "./$CFG_FILE does not exist"
	fi
	if [ -e "$BECHMARK_RESULTS_DIR/render.cfg" ]; then
		RENDER_CONFIG_BACKUP="$(tempfile)"
		if ! mv -f "$BECHMARK_RESULTS_DIR/render.cfg" "$RENDER_CONFIG_BACKUP"; then
			err "failed to move \"$BECHMARK_RESULTS_DIR/render.cfg\""
		fi
		HAVE_RENDER_CONFIG_BACKUP=true
	fi
	if ! cp "./$CFG_FILE" "$BECHMARK_RESULTS_DIR/render.cfg"; then
		err "failed to replace \"$BECHMARK_RESULTS_DIR/render.cfg\""
	fi
fi

if ! pgrep steam > /dev/null; then
	err "Steam client isn't running"
fi
if ! which xdotool &> /dev/null; then
	err "xdotool isn't installed"
fi

# Start the game in background
EXECUTABLE=./bin/ShadowOfMordor
if [ ! -e "$EXECUTABLE" ]; then
	err "$EXECUTABLE does not exist"
elif [ ! -x "$EXECUTABLE" ]; then
	err "$EXECUTABLE isn't an executable"
fi
$EXECUTABLE &>> "$LOG_FILE" &
sleep 1s
if [[ "$(jobs)" == "" ]]; then
	err "failed to start $EXECUTABLE"
fi
GAME_IS_RUNNING=true

WINDOW_NAME0="^Middle-earth.*: Shadow of Mordor.* Options\$"
WINDOW_NAME1="^Middle-earth.*: Shadow of Mordor"

function send_keys() {
	local TUPLE0
	for TUPLE0 in "$@"; do
		local TUPLE1
		read -r -a TUPLE1 <<< "$TUPLE0"
		local COUNT="${TUPLE1[0]}"
		local KEY="${TUPLE1[1]}"
		local DELAY="${TUPLE1[2]}"
		local i
		for((i=0; i<$COUNT; i++)); do
			# Performing keyup twice resolves some issues
			xdotool search "$WINDOW_NAME" keyup "$KEY" &>> "$LOG_FILE"
			if ! xdotool search "$WINDOW_NAME" keydown "$KEY" &>> "$LOG_FILE"; then
				err "failed to send $KEY to \"$WINDOW_NAME\""
			fi
			sleep 0.2s
			xdotool search "$WINDOW_NAME" keyup "$KEY" &>> "$LOG_FILE"
			sleep "$DELAY"
		done
	done
}

# Send Return key to the options window
sleep 4s
WINDOW_NAME="$WINDOW_NAME0"
if xdotool search "$WINDOW_NAME" &>> "$LOG_FILE"; then
	send_keys "1 Return 0.5s"
fi

# Enter the main menu
sleep $MENU_TIME
WINDOW_NAME="$WINDOW_NAME1"
if ! xdotool search "$WINDOW_NAME" &>> "$LOG_FILE"; then
	sleep 1s
	if ! xdotool search "$WINDOW_NAME" &>> "$LOG_FILE"; then
		err "no such window: $WINDOW_NAME"
	fi
fi
send_keys "8 Escape 1s" # Skip intro screens, enter the main menu

# Benchmarking loop
WARMUP=1
for((i=0; i<$[$WARMUP+$NUM_RUNS]; i++)); do
	send_keys "2 Up 0.7s" "1 Return 0.7s" "1 Up 0.7s" # Menu navigation

	send_keys "1 Return 0s" # Start the benchmark

	# Wait for the appearance of benchmark result file Benchmark_*.csv
	RESULT="$(inotifywait --quiet --event create --timeout 120 "$BECHMARK_RESULTS_DIR")" ||
		err "failure while waiting for \"$BECHMARK_RESULTS_DIR\""
	read -r -a RESULT <<< "$RESULT"

	if [ $i -lt $WARMUP ]; then
		# Print the location of the benchmark result file
		echo "\"$BECHMARK_RESULTS_DIR/${RESULT[-1]}\""
	fi

	sleep 1s
	send_keys "1 Return 3s" # Return to main menu
done

# Initiate game exit by closing the window.
# The if-statement lowers the probability of sending Alt+F4 to an unrelated window by accident.
# Note: sending $WINDOW_CLOSE_KEY works better than "xdotool windowclose".
if xdotool search "$WINDOW_NAME" &>> "$LOG_FILE"; then
	xdotool search "$WINDOW_NAME" key "$WINDOW_CLOSE_KEY" &>> "$LOG_FILE"
fi

# Wait until the game terminates
wait
GAME_IS_RUNNING=false
