#!/usr/bin/env bash

export LC_ALL=C

# Screen width for help/usage text (for reproducible test results)
export COLUMNS=79
shopt -u checkwinsize # Prevent subsequent commands from resetting `COLUMNS`

# Game Boy release date, 1989-04-21T12:34:56Z (for reproducible test results)
export SOURCE_DATE_EPOCH=609165296

o="$(mktemp)"
gb="$(mktemp)"
input="$(mktemp)"
output="$(mktemp)"
errput="$(mktemp)"

# shellcheck disable=SC2064 # (Immediate expansion is the desired behavior.)
trap "rm -f ${o@Q} ${gb@Q} ${input@Q} ${output@Q} ${errput@Q}" EXIT

tests=0
failed=0
rc=0

bold="$(tput bold)"
resbold="$(tput sgr0)"
red="$(tput setaf 1)"
green="$(tput setaf 2)"
orange="$(tput setaf 3)"
rescolors="$(tput op)"

RGBASM=../../rgbasm
RGBLINK=../../rgblink

tryDiff () {
	if ! diff -au --strip-trailing-cr "$1" "$2"; then
		echo "${bold}${red}${i%.asm}${variant}.$3 mismatch!${rescolors}${resbold}"
		false
	fi
}

tryCmp () {
	if ! cmp "$1" "$2"; then
		../../contrib/gbdiff.bash "$1" "$2"
		echo "${bold}${red}${i%.asm}${variant}.$3 mismatch!${rescolors}${resbold}"
		false
	fi
}

# Add the version constants test, outputting the closest tag to the HEAD
if git -c safe.directory='*' describe --tags --abbrev=0 >version.out; then
	$RGBASM --version >>version.out
	cat >version.asm <<EOF
IF !DEF(__RGBDS_RC__)
	PRINTLN "v{d:__RGBDS_MAJOR__}.{d:__RGBDS_MINOR__}.{d:__RGBDS_PATCH__}"
ELSE
	PRINTLN "v{d:__RGBDS_MAJOR__}.{d:__RGBDS_MINOR__}.{d:__RGBDS_PATCH__}-rc{d:__RGBDS_RC__}"
ENDC
	PRINTLN "rgbasm {__RGBDS_VERSION__}"
EOF
else
	echo "${bold}${orange}Warning: cannot run version test!${rescolors}${resbold}"
	rm -f version.asm
fi

for i in *.asm notexist.asm; do
	RGBASMFLAGS=(-Weverything -Bcollapse)
	if [ -f "${i%.asm}.flags" ]; then
		RGBASMFLAGS+=("@${i%.asm}.flags")
	fi
	for variant in '' ' piped'; do
		(( tests++ ))
		echo "${bold}${green}${i%.asm}${variant}...${rescolors}${resbold}"
		if [ -e "${i%.asm}.out" ]; then
			desired_outname=${i%.asm}.out
		else
			desired_outname=/dev/null
		fi
		if [ -e "${i%.asm}.err" ]; then
			desired_errname=${i%.asm}.err
		else
			desired_errname=/dev/null
		fi
		if [ -z "$variant" ]; then
			"$RGBASM" "${RGBASMFLAGS[@]}" -o "$o" "$i" >"$output" 2>"$errput"
			desired_output=$desired_outname
			desired_errput=$desired_errname
		else
			# "include-recursion.asm" refers to its own name inside the test code.
			# "make-deps.asm" refers to its output filename in its desired output.
			# "notexist.asm" doesn't exist, so there's no point in trying to `cat` it.
			# Skip testing with stdin input for those files.
			if [[ "$i" = include-recursion.asm || "$i" = make-deps.asm || "$i" = notexist.asm ]]; then
				continue
			fi

			# shellcheck disable=SC2002 # (This use of `cat` intentionally makes the input an unseekable pipe.)
			cat "$i" | "$RGBASM" "${RGBASMFLAGS[@]}" -o "$o" - >"$output" 2>"$errput"

			# Use two otherwise unused files for temp storage
			desired_output="$input"
			desired_errput="$gb"
			# Escape regex metacharacters
			subst="$(printf '%s\n' "$i" | sed 's:[][\/.^$*]:\\&:g')"
			# Replace the file name with "<stdin>" to match changed output
			sed "s/$subst/<stdin>/g" "$desired_outname" >"$desired_output"
			sed "s/$subst/<stdin>/g" "$desired_errname" >"$desired_errput"
		fi

		tryDiff "$desired_output" "$output" out
		our_rc=$?
		tryDiff "$desired_errput" "$errput" err
		(( our_rc = our_rc || $? ))

		desired_binname=${i%.asm}.out.bin
		if [[ -f "$desired_binname" && $our_rc -eq 0 ]]; then
			# 'rgblink -x' implies '-t', so asm/*.out.bin tests cannot use ROMX past 1
			if ! "$RGBLINK" -x -o "$gb" "$o"; then
				echo "${bold}${red}\`$RGBLINK -x -o $gb $o\` failed!${rescolors}${resbold}"
			else
				tryCmp "$desired_binname" "$gb" gb
				(( our_rc = our_rc || $? ))
			fi
		fi

		(( rc = rc || our_rc ))
		if [[ $our_rc -ne 0 ]]; then
			(( failed++ ))
			break
		fi
	done
done

for i in cli/*.flags; do
	(( tests++ ))
	echo "${bold}${green}${i%.flags}...${rescolors}${resbold}"
	if [ -e "${i%.flags}.out" ]; then
		desired_output=${i%.flags}.out
	else
		desired_output=/dev/null
	fi
	if [ -e "${i%.flags}.err" ]; then
		desired_errput=${i%.flags}.err
	else
		desired_errput=/dev/null
	fi
	"$RGBASM" "@$i" >"$output" 2>"$errput"

	tryDiff "$desired_output" "$output" out
	our_rc=$?
	tryDiff "$desired_errput" "$errput" err
	(( our_rc = our_rc || $? ))

	(( rc = rc || our_rc ))
	if [[ $our_rc -ne 0 ]]; then
		(( failed++ ))
		break
	fi
done

# These tests do their own thing

i="invalid-source-date-epoch"
RGBASMFLAGS=(-Weverything -Bcollapse)
(( tests++ ))
echo "${bold}${green}${i}...${rescolors}${resbold}"
SOURCE_DATE_EPOCH=0x1234 "$RGBASM" "${RGBASMFLAGS[@]}" /dev/null >"$output" 2>"$errput"
tryDiff /dev/null "$output" out
our_rc=$?
tryDiff invalid-source-date-epoch.err "$errput" err
(( our_rc = our_rc || $? ))
(( rc = rc || our_rc ))
if [[ $our_rc -ne 0 ]]; then
	(( failed++ ))
fi

evaluateDepTest () {
	i="$1"
	RGBASMFLAGS=(-Weverything -Bcollapse -M -)
	if [ -f "$i/a.flags" ]; then
		RGBASMFLAGS+=("@$i/a.flags")
	fi
	# Piping the .asm file to rgbasm would not make sense for dependency generation,
	# so just test the normal variant
	(( tests++ ))
	echo "${bold}${green}${i}...${rescolors}${resbold}"
	"$RGBASM" "${RGBASMFLAGS[@]}" -o "$o" "$i"/a.asm >"$output" 2>"$errput"

	fixed_output="$input"
	if type -t cygpath >/dev/null; then
		# MinGW needs the Windows path substituted but with forward slash separators;
		# Cygwin has `cygpath` but just needs the original path substituted.
		subst1="$(printf '%s\n' "$o" | sed 's:[][\/.^$*]:\\&:g')"
		subst2="$(printf '%s\n' "$(cygpath -w "$o")" | sed -e 's:\\:/:g' -e 's:[][\/.^$*]:\\&:g')"
		sed -e "s/$subst1/a.o/g" -e "s/$subst2/a.o/g" "$output" >"$fixed_output"
	else
		subst="$(printf '%s\n' "$o" | sed 's:[][\/.^$*]:\\&:g')"
		sed "s/$subst/a.o/g" "$output" >"$fixed_output"
	fi

	tryDiff "$i"/a.out "$fixed_output" out
	our_rc=$?
	tryDiff "$i"/a.err "$errput" err
	(( our_rc = our_rc || $? ))

	(( rc = rc || our_rc ))
	if [[ $our_rc -ne 0 ]]; then
		(( failed++ ))
	fi
}
evaluateDepTest "continues-after-missing-include"
evaluateDepTest "exits-after-missing-include"
evaluateDepTest "continues-after-missing-preinclude"
evaluateDepTest "exits-after-missing-preinclude"

i="state-file"
if type -t cygpath >/dev/null; then
	# MinGW translates path names before passing them as command-line arguments,
	# but does not do so when they are prefixed, so we have to do it ourselves.
	state_outname="$(cygpath -w "$o")"
else
	state_outname="$o"
fi
state_features="  all  " # Test trimming whitespace
RGBASMFLAGS=(-Weverything -Bcollapse)
for variant in '' '.pipe'; do
	(( tests++ ))
	echo "${bold}${green}${i%.asm}${variant}...${rescolors}${resbold}"
	if [ -z "$variant" ]; then
		"$RGBASM" "${RGBASMFLAGS[@]}" -s "$state_features:$state_outname" "$i"/a.asm >"$output" 2>"$errput"
	else
		# shellcheck disable=SC2002 # (This use of `cat` intentionally makes the input an unseekable pipe.)
		cat "$i"/a.asm | "$RGBASM" "${RGBASMFLAGS[@]}" -s "$state_features:$state_outname" - >"$output" 2>"$errput"
	fi

	tryDiff /dev/null "$output" out
	our_rc=$?
	tryDiff /dev/null "$errput" err
	(( our_rc = our_rc || $? ))
	tryDiff "$i"/a.dump.asm "$o" err
	(( our_rc = our_rc || $? ))

	(( rc = rc || our_rc ))
	if [[ $our_rc -ne 0 ]]; then
		(( failed++ ))
		break
	fi
done

if [[ "$failed" -eq 0 ]]; then
	echo "${bold}${green}All ${tests} tests passed!${rescolors}${resbold}"
else
	echo "${bold}${red}${failed} of the tests failed!${rescolors}${resbold}"
fi

exit $rc
