#!/bin/sh

# Let's set a sane environment
set -o errexit
set -o nounset

# Extract the extensions to be tested from commit messages
#
# Update: EXTENSIONS_TO_BE_TESTED
# Set: STOP_EXTENSIONS_FOUND
extractExtensionsFromCommits() {
	STOP_EXTENSIONS_FOUND=0
	IFS='
'
	for extractExtensionsFromCommits_hash in $(git -C "$CI_BUILD_DIR" log --pretty='format:%H' "$CI_COMMIT_RANGE"); do
		extractExtensionsFromCommits_firstLine=1
		extractExtensionsFromCommits_message="$(git -C "$CI_BUILD_DIR" log --pretty='format:%B' -n 1 "$extractExtensionsFromCommits_hash")"
		IFS='
'
		for extractExtensionsFromCommits_messageLine in $extractExtensionsFromCommits_message; do
			if test $extractExtensionsFromCommits_firstLine -eq 1; then
				extractExtensionsFromCommits_firstLine=0
			else
				extractExtensionsFromCommits_testList=
				case "$extractExtensionsFromCommits_messageLine" in
					Test:*)
						extractExtensionsFromCommits_testList=${extractExtensionsFromCommits_messageLine#Test:}
						;;
					TEST:*)
						extractExtensionsFromCommits_testList=${extractExtensionsFromCommits_messageLine#TEST:}
						;;
					test:*)
						extractExtensionsFromCommits_testList=${extractExtensionsFromCommits_messageLine#test:}
						;;
				esac
				if test -n "$extractExtensionsFromCommits_testList"; then
					IFS=' ,;'
					for extractExtensionsFromCommits_extension in $extractExtensionsFromCommits_testList; do
						if test $extractExtensionsFromCommits_extension = '-STOP-'; then
							STOP_EXTENSIONS_FOUND=1
							break 2
						fi
						if ! stringInList "$extractExtensionsFromCommits_extension" "$EXTENSIONS_TO_BE_TESTED"; then
							EXTENSIONS_TO_BE_TESTED="$EXTENSIONS_TO_BE_TESTED $extractExtensionsFromCommits_extension"
						fi
					done
				fi
			fi
		done
	done
	EXTENSIONS_TO_BE_TESTED="${EXTENSIONS_TO_BE_TESTED# }"
}

# Extract the extensions to be tested from changes in the data/supported-extensions file
#
# Update: EXTENSIONS_TO_BE_TESTED
extractExtensionsFromData() {
	IFS='
'
	extractExtensionsFromData_foundAt=
	for extractExtensionsFromData_line in $(git -C "$CI_BUILD_DIR" diff --no-indent-heuristic --minimal --no-color --word-diff=none -no-renames --unified=0 "$CI_COMMIT_RANGE" -- data/supported-extensions); do
		if test -n "$extractExtensionsFromData_line"; then
			if test -z "$extractExtensionsFromData_foundAt"; then
				if test -z "${extractExtensionsFromData_line##@@*}"; then
					extractExtensionsFromData_foundAt=y
				fi
			elif test -z "${extractExtensionsFromData_line##+*}"; then
				extractExtensionsFromData_extension="${extractExtensionsFromData_line%% *}"
				extractExtensionsFromData_extension="${extractExtensionsFromData_extension#+}"
				if ! stringInList "$extractExtensionsFromData_extension" "$EXTENSIONS_TO_BE_TESTED"; then
					EXTENSIONS_TO_BE_TESTED="$EXTENSIONS_TO_BE_TESTED $extractExtensionsFromData_extension"
				fi
			fi
		fi
	done
	EXTENSIONS_TO_BE_TESTED="${EXTENSIONS_TO_BE_TESTED# }"
}

# Remove from the EXTENSIONS_TO_BE_TESTED variable the extensions that are
# not supported in the distro specified in the data/special-requirements file

# Update: EXTENSIONS_TO_BE_TESTED
filterUnsupportedExensionsForDistro() {
	if test -z "$EXTENSIONS_TO_BE_TESTED"; then
		return
	fi
	filterUnsupportedExensionsForDistro_reqs="$CI_BUILD_DIR/data/special-requirements"
	if ! test -f "$filterUnsupportedExensionsForDistro_reqs"; then
		return
	fi
	filterUnsupportedExensionsForDistro_filtered=''
	for filterUnsupportedExensionsForDistro_extension in "$EXTENSIONS_TO_BE_TESTED"; do
		if stringInList "!$DOCKER_DISTRO" "$(cat "$filterUnsupportedExensionsForDistro_reqs" | grep -E "^$filterUnsupportedExensionsForDistro_extension[ \t]")"; then
			printf 'Note: extension "%s" is not supported for distro "%s"\n' "$filterUnsupportedExensionsForDistro_extension" "$DOCKER_DISTRO"
		else
			filterUnsupportedExensionsForDistro_filtered="$filterUnsupportedExensionsForDistro_filtered $filterUnsupportedExensionsForDistro_extension"
		fi
	done
	EXTENSIONS_TO_BE_TESTED="${filterUnsupportedExensionsForDistro_filtered# }"
}

# Get the docker image ID for a PHP extension and a PHP version
#
# Arguments:
# $1: space-separated list with the names of the PHP extensions
# $2: the PHP version
#
# Outputs:
# the full docker image ID
getDockerImageName() {
	case "$2" in
		8.0)
			getDockerImageName_version="$2-rc"
			;;
		*)
			getDockerImageName_version="$2"
			;;
	esac
	getDockerImageName_suffix='cli'
	getDockerImageName_reqs="$CI_BUILD_DIR/data/special-requirements"
	if test -f "$getDockerImageName_reqs"; then
		IFS=' '
		for getDockerImageName_testExtension in $1; do
			if test -n "$(cat "$getDockerImageName_reqs" | grep -E "^$getDockerImageName_testExtension[ \t]+zts[ \t]*$")"; then
				getDockerImageName_suffix='zts'
			fi
		done
	fi
	getDockerImageName_imageName="$(printf 'php:%s-%s-%s' "$getDockerImageName_version" "$getDockerImageName_suffix" "$DOCKER_DISTRO")"
	case "$getDockerImageName_imageName" in
		php:5.5-cli-jessie)
			getDockerImageName_imageName='php:5.5-cli'
			;;
		php:5.5-zts-jessie)
			getDockerImageName_imageName='php:5.5-zts'
			;;
	esac
	if test -z "$(docker images -q "$getDockerImageName_imageName" 2>/dev/null)"; then
		getDockerImageName_log="$(docker pull "$getDockerImageName_imageName" 2>&1 || true)"
		if test -z "$(docker images -q "$getDockerImageName_imageName" 2>/dev/null)"; then
			if test "${getDockerImageName_log#*manifest unknown}" != "$getDockerImageName_log" || test "${getDockerImageName_log#*manifest for * not found}" != "$getDockerImageName_log"; then
				true
				return
			fi
			printf '%s\n' "$getDockerImageName_log" >&2
			exit 1
		fi
	fi
	printf '%s' "$getDockerImageName_imageName"
}

# Get the list of all supported PHP versions
#
# Arguments:
# $1: space-separated list with the names of the PHP extensions to be tested
#
# Outputs:
# the space-separated list of supported PHP versions
getAllPHPVersionsForExtensions() {
	if test -n "${PHP_VERSION_TO_TEST:-}"; then
		echo "$PHP_VERSION_TO_TEST"
		return
	fi
	getAllPHPVersionsForExtensions_result=''
	IFS=' '
	for getAllPHPVersionsForExtensions_extension in $1; do
		getAllPHPVersionsForExtensions_this="$(getAllPHPVersionsForExtension "$getAllPHPVersionsForExtensions_extension")"
		if test -z "$getAllPHPVersionsForExtensions_this"; then
			return
		fi
		if test -z "$getAllPHPVersionsForExtensions_result"; then
			getAllPHPVersionsForExtensions_result="$getAllPHPVersionsForExtensions_this"
		else
			getAllPHPVersionsForExtensions_tmp=''
			for getAllPHPVersionsForExtensions_php1 in $getAllPHPVersionsForExtensions_this; do
				if stringInList "$getAllPHPVersionsForExtensions_php1" "$getAllPHPVersionsForExtensions_result"; then
					getAllPHPVersionsForExtensions_tmp="$getAllPHPVersionsForExtensions_tmp $getAllPHPVersionsForExtensions_php1"
				fi
			done
			getAllPHPVersionsForExtensions_result="${getAllPHPVersionsForExtensions_tmp# }"
		fi
	done
	printf '%s' "$getAllPHPVersionsForExtensions_result"
}

# Get the list of all supported PHP versions
#
# Arguments:
# $1: the names of a PHP extension to be tested
#
# Outputs:
# the space-separated list of supported PHP versions
getAllPHPVersionsForExtension() {
	getAllPHPVersionsForExtension_result=''
	while IFS= read -r getAllPHPVersionsForExtension_line; do
		getAllPHPVersionsForExtension_ok=
		IFS=' '
		for getAllPHPVersionsForExtension_chunk in $getAllPHPVersionsForExtension_line; do
			if test -z "$getAllPHPVersionsForExtension_ok"; then
				if test "$getAllPHPVersionsForExtension_chunk" = "$1"; then
					getAllPHPVersionsForExtension_ok=y
				else
					getAllPHPVersionsForExtension_ok=n
				fi
			else
				if test $getAllPHPVersionsForExtension_ok = 'y'; then
					if test -z "$getAllPHPVersionsForExtension_result"; then
						getAllPHPVersionsForExtension_result="$getAllPHPVersionsForExtension_chunk"
					else
						if ! stringInList "$getAllPHPVersionsForExtension_chunk" "$getAllPHPVersionsForExtension_result"; then
							getAllPHPVersionsForExtension_result="$getAllPHPVersionsForExtension_result $getAllPHPVersionsForExtension_chunk"
						fi
					fi
				fi
			fi
		done
	done <"$CI_BUILD_DIR/data/supported-extensions"

	filterUnsupportedExensionsForDistro_reqs="$CI_BUILD_DIR/data/special-requirements"
	if test -f "$filterUnsupportedExensionsForDistro_reqs"; then
		getAllPHPVersionsForExtension_filtered_result=''
		for getAllPHPVersionsForExtension_result_filter in $getAllPHPVersionsForExtension_result; do
			if stringInList "!$getAllPHPVersionsForExtension_result_filter-$DOCKER_DISTRO" "$(cat "$filterUnsupportedExensionsForDistro_reqs" | grep -E "^$1[ \t]")"; then
				printf 'Note: extension "%s" is not supported for distro "%s" using php "%s"\n' "$1" "$DOCKER_DISTRO" "$getAllPHPVersionsForExtension_result_filter" >/dev/stderr
			else
				getAllPHPVersionsForExtension_filtered_result="$getAllPHPVersionsForExtension_filtered_result $getAllPHPVersionsForExtension_result_filter"
			fi
		done
	else
		getAllPHPVersionsForExtension_filtered_result="$getAllPHPVersionsForExtension_result"
	fi

	printf '%s' "${getAllPHPVersionsForExtension_filtered_result# }"
}

# Test extensions
#
# Arguments:
# $1: space-separated list with the names of the PHP extensions to be tested
#
# Return:
# 0 (true): if test passes
# 1 (false): if test fails
testExtension() {
	testExtensionsFromMessage_result=0
	IFS=' '
	for testExtension_extension in $1; do
		testExtension_extension="$(printf '%s' "$testExtension_extension" | sed -E 's/\+/ /g')"
		printf '### TESTING EXTENSION(S) %s ###\n' "$testExtension_extension"
		for testExtension_phpVersion in $(getAllPHPVersionsForExtensions "$testExtension_extension"); do
			if ! testExtensionFor "$testExtension_extension" "$testExtension_phpVersion"; then
				testExtensionsFromMessage_result=1
			fi
		done
	done
	resetIFS
	return $testExtensionsFromMessage_result
}

# Test extensions with specific PHP versions
#
# Arguments:
# $1: space-separated list with the names of the PHP extensions to be tested
# $2: the PHP version
#
# Return:
# 0 (true): if test passes
# 1 (false): if test fails
testExtensionFor() {
	printf 'PHP version: %s\n' "$2"
	if test -n "$(printf '%s' "$2" | sed -E 's/^[0-9]+\.[0-9]+$//')"; then
		printf '  INVALID PHP VERSION: %s\n' "$2" >&2
		return 1
	fi
	testExtensionFor_Image="$(getDockerImageName "$1" "$2")"
	if test $? -ne 0; then
		exit 1
	fi
	if test -z "$testExtensionFor_Image"; then
		printf ' - Docker image not available\n'
		return 0
	fi
	printf ' - Docker image: %s\n' "$testExtensionFor_Image"
	testExtensionFor_out="$(mktemp)"
	testExtensionFor_start=$(date +%s)
	if $(docker run --rm --volume "$CI_BUILD_DIR:/app" --workdir /app "$testExtensionFor_Image" sh -c "./install-php-extensions $1 && php ./scripts/check-installed-extension.php $1" >"$testExtensionFor_out" 2>&1); then
		testExtensionFor_end=$(date +%s)
		testExtensionFor_delta=$(expr $testExtensionFor_end - $testExtensionFor_start)
		rm -rf "$testExtensionFor_out"
		printf ' - Passed in %s seconds\n' $testExtensionFor_delta
		return 0
	fi
	printf ' - FAILED!\n' >&2
	cat "$testExtensionFor_out" >&2
	rm -rf "$testExtensionFor_out"
	return 1
}

echo 'Checking environment'
if test -z "${GITHUB_WORKSPACE:-}"; then
	echo 'Not in a CI environment' >&2
	exit 1
fi
CI_BUILD_DIR="$GITHUB_WORKSPACE"
if test -z "${DOCKER_DISTRO:-}"; then
	echo 'DOCKER_DISTRO environment variable not set' >&2
	exit 1
fi

. "$CI_BUILD_DIR/scripts/common"

case "${1:-}" in
	from-commits)
		if test -z "${2:-}"; then
			echo 'Missing commit range of the push event' >&2
			exit 1
		fi
		CI_COMMIT_RANGE="$2"
		STOP_EXTENSIONS_FOUND=0
		EXTENSIONS_TO_BE_TESTED=''
		extractExtensionsFromCommits
		if test $STOP_EXTENSIONS_FOUND -eq 0; then
			extractExtensionsFromData
		fi
		;;
	from-list)
		EXTENSIONS_TO_BE_TESTED="${2:-}"
		;;
	*)
		if test -z "${1:-}"; then
			printf 'Missing source of extensions to be tested\n' >&2
		else
			printf '"%s" is an unknown source of extensions to be tested\n' "$1" >&2
		fi
		exit 1
		;;
esac

filterUnsupportedExensionsForDistro

if test -z "$EXTENSIONS_TO_BE_TESTED"; then
	echo 'No extensions to be tested.'
	exit 0
fi

printf '### EXTENSIONS TO BE TESTED: %s\n' "$EXTENSIONS_TO_BE_TESTED"
SOME_TEST_FAILED=0
IFS='
'

for EXTENSION_TO_BE_TESTED in "$EXTENSIONS_TO_BE_TESTED"; do
	testExtension "$EXTENSION_TO_BE_TESTED" || SOME_TEST_FAILED=1
done

if test $SOME_TEST_FAILED -ne 0; then
	exit 1
fi