#!/usr/bin/env bash

set -eu
set -o pipefail 2> /dev/null || true

variety=""
args=""
pre_command=""
post_command=""
sep="%"
stdin_pipe=""

function usage
{
	retval="$1"
    (
	echo "$0: --variety var --args args --pre-command cmd --separator % --stdin-pipe cmd"
    echo "legal varieties:"
    echo '   fast'
    echo '   all-cpythons'
    echo '   all'
    echo '   minimal-a'
    echo '   minimal-b'
    echo '   just-a-cython'
    ) 1>&2
	exit "$retval"
}

while [ "$#" -ge 1 ]
do
	case "$1" in
		--separator)
			sep="$2"
			shift
			;;
		--variety)
			variety="$2"
			shift
			;;
		--args)
			shift
			args="$*"
			break
			;;
		--stdin-pipe)
			stdin_pipe="$2"
			shift
			;;
		--pre-command)
			pre_command="$2"
			shift
			;;
		--post-command)
			post_command="$2"
			shift
			;;
		*)
			echo "$0: Illegal option: $1" 1>&2
			usage 1
			;;
	esac
	shift
done

if [ "$variety" = "" ]
then
    usage 0
fi

# About IronPython:
# 		IronPython 2.6 Beta 2 doesn't work with backshift.  It's possible FePy
# 		would - it's supposed to have a patched standard library to facilitate
# 		compatibility with the python standard library.
#
# 		ipy (IronPython), at least on Ubuntu 11.04, seems to just use the
# 		system's CPython standard library, which results in tracebacks.
#
# 		I see no FePy package for Ubuntu, although there was an IronPython
# 		package for Ubuntu 11.04.

function list_interpreter
{
	report_any=False
	report_all=False
	interpreters=""
	expected_major=""
	require_rcpyxm=False

	while [ "$#" -ge 1 ]
	do
		case "$1" in
#			--require-rcpyxm)
#				require_rcm=True
#				;;
			--any)
				report_any=True
				;;
			--all)
				report_all=True
				;;
			--major)
				expected_major="$2"
				shift
				;;
			--interpreters)
				shift
				interpreters="$*"
				# And eat the arguments still in argv
				while [ "$#" -ge 1 ]
				do
					shift
				done
				;;
			*)
				echo "$0: list_interprer: Valid options include --any, --all, --interpreters, but not $1" 1>&2
				exit 1
				;;
		esac
		shift
	done

	if [ "$report_any" = True ]
	then
		if [ "$report_all" = True ]
		then
			echo "$0: list_interprer: --any and -all are mutually exclusive" 1>&2
			exit 1
		elif [ "$report_all" = False ]
		then
			: Good
		else
			echo "$0: list_interprer: report_any is True, but report_all is $report_all" 1>&2
			exit 1
		fi
	elif [ "$report_any" = False ]
	then
		if [ "$report_all" = True ]
		then
			: Good
		elif [ "$report_all" = False ]
		then
			echo "$0: list_interprer: You must give exactly one of --any and -all" 1>&2
			exit 1
		else
			echo "$0: list_interprer: report_any is False, but report_all is $report_all" 1>&2
			exit 1
		fi
	else
		echo "$0: list_interprer: report_any should be True or False, but is $report_any" 1>&2
		exit 1
	fi

	if [ "$interpreters" = "" ]
	then
		echo "$0: list_interprer: --interpreters is a required option" 1>&2
		exit 1
	fi

	if [ "$expected_major" = "" ]
	then
		echo "$0: list_interprer: --major is a required option" 1>&2
		exit 1
	fi

	at_least_one_found=False

	for record in $interpreters
	do
		basename=$(set -eu; echo "$record" | awk -F"$sep" ' { print $1 }')
		interp=$(set -eu; echo "$record" | awk -F"$sep" ' { print $2 }')
		if [ -e "$interp" ]
		then
			actual_major=$(set -eu; "$interp" -c "import sys; print(sys.version_info[0])")
			actual_minor=$(set -eu; "$interp" -c "import sys; print(sys.version_info[1])")
			case "$actual_major" in
				2)
					if [ "$actual_minor" -lt 5 ]
					then
						# We only support 2.5 and up.  2.4 is too old.
						continue
					fi
					;;
				3)
					;;
				*)
					# Could be too old, could be too new
					echo "$0: list_interprer: actual_major is not 2 or 3: $actual_major" 1>&2
					exit 1
					;;
			esac
			#echo "actual_major is $actual_major, expected_major is $expected_major" 1>&2
			if [ "$actual_major" != "$expected_major" ]
			then
				continue
			fi
			if [ "$require_rcpyxm" = True ]
			then
				if ! "$interp" -c 'import rolling_checksum_pyx_mod' > /dev/null 2>&1
				then
					continue
				fi
			fi
			echo "$basename""$sep""$interp"
			at_least_one_found=True
			if [ "$report_any" = True ]
			then
				return 0
			fi
		fi
	done

	if [ "$at_least_one_found" = True ]
	then
		return 0
	elif [ "$at_least_one_found" = False ]
	then
		return 1
	else
		echo "$0: list_interprer: at_least_one_found is neither True nor False: $at_least_one_found" 1>&2
		exit 1
	fi
}

function list_compiler
{
	report_any=False
	report_all=False
	compilers=""

	while [ "$#" -ge 1 ]
	do
		case "$1" in
			--any)
				report_any=True
				;;
			--all)
				report_all=True
				;;
			--compilers)
				shift
				compilers="$*"
				while [ "$#" -ge 1 ]
				do
					shift
				done
				break
				;;
			*)
				echo "$0: list_compiler: Unrecognized option: $1" 1>&2
				exit 1
				;;
		esac
		shift
	done
	if [ "$report_any" = True ] && [ "$report_all" = True ]
	then
		echo "$0: list_compiler: --any and --all are mutually exclusive" 1>&2
		exit 1
	fi
	if [ "$report_any" = False ] && [ "$report_all" = False ]
	then
		echo "$0: list_compiler: You must specify one of --any and --all" 1>&2
		exit 1
	fi
	if [ "$compilers" = "" ]
	then
		echo "$0: list_compiler: You must specify --compilers" 1>&2
		exit 1
	fi
	at_least_one_found=False
	for record in $compilers
	do
		basename=$(echo "$record" | awk -F"$sep" ' { print $1 }')
		file=$(echo "$record" | awk -F"$sep" ' { print $2 }')
		if [ -e "$file" ]
		then
			at_least_one_found=True
			echo "$basename$sep$file"
			if [ "$report_any" = True ]
			then
				break
			fi
		fi
	done
	if [ "$at_least_one_found" = True ]
	then
		return 0
	elif [ "$at_least_one_found" = False ]
	then
		return 1
	else
		echo "$0: list_compiler: \$at_least_one_found has a strange value: $at_least_one_found" 1>&2
		exit 1
	fi
}

function list_cython2
{
	if [ "$1" = --any ]
	then
		quantifier="--any"
	elif [ "$1" = --all ]
	then
		quantifier="--all"
	else
		echo "$0: list_cython2: \$1 is not --any or --all" 1>&2
		exit 1
	fi
	list_compiler "$quantifier" --compilers \
		cython2%/usr/local/cpython-2.7/bin/cython
}

function list_cython3
{
	if [ "$1" = --any ]
	then
		quantifier="--any"
	elif [ "$1" = --all ]
	then
		quantifier="--all"
	else
		echo "$0: list_cython3: \$1 is not --any or --all" 1>&2
		exit 1
	fi
	list_compiler "$quantifier" --compilers \
		cython3%/usr/local/cpython-3.8/bin/cython \
		cython3%/usr/local/cpython-3.9/bin/cython \
		cython3%/usr/local/cpython-3.10/bin/cython
}

function list_pypy3
{
	if [ "$1" = --any ]
	then
		quantifier="--any"
	elif [ "$1" = --all ]
	then
		quantifier="--all"
	else
		echo "$0: list_pypy3: \$1 is not --any or --all" 1>&2
		exit 1
	fi
	# 1.6 was missing os.stat().st_rdev, but pypy-trunk-2011-08-18, which is almost immediately after 1.6's release,
	# has it again.
    # This 5.8.0 isn't really 5.8.0 - it's really a trunk with a necessary os.utime() bugfix.
	list_interpreter "$quantifier" --major 3 --interpreters \
        pypy3-7.0.0"$sep"/usr/local/pypy3-7.0.0/bin/pypy3 \
        pypy3-6.0.0"$sep"/usr/local/pypy3-6.0.0/bin/pypy3 \
        pypy3-5.10.0"$sep"/usr/local/pypy3-5.10.0/bin/pypy3 \
        pypy3-5.9.0"$sep"/usr/local/pypy3-5.9.0/bin/pypy3
}

function list_cpython3
{
	if [ "$1" = --any ]
	then
		quantifier="--any"
	elif [ "$1" = --all ]
	then
		quantifier="--all"
	else
		echo "$0: list_cpython3: \$1 is not --any or --all" 1>&2
		exit 1
	fi
    # shellcheck disable=SC2086
	list_interpreter "$quantifier" --major 3 --interpreters \
		cpython-"$sep"/usr/local/cpython-3.12/bin/python3 \
		usr-local-bin-python3.12"$sep"/usr/local/bin/python3.12 \
		cpython-3.11"$sep"/usr/local/cpython-3.11/bin/python3 \
		usr-local-bin-python3.11"$sep"/usr/local/bin/python3.11 \
		cpython-3.10"$sep"/usr/local/cpython-3.10/bin/python3 \
		usr-local-bin-python3.10"$sep"/usr/local/bin/python3.10 \
		cpython-3.9"$sep"/usr/local/cpython-3.9/bin/python3 \
		usr-local-bin-python3.9"$sep"/usr/local/bin/python3.9 \
		cpython-3.8"$sep"/usr/local/cpython-3.8/bin/python3 \
		usr-local-bin-python3.8"$sep"/usr/local/bin/python3.8 \
		cpython-3.7"$sep"/usr/local/cpython-3.7/bin/python3 \
		usr-local-bin-python3.7"$sep"/usr/local/bin/python3.7 \
		cpython-3.6"$sep"/usr/local/cpython-3.6/bin/python3 \
		usr-local-bin-python3.6"$sep"/usr/local/bin/python3.6
}

function list_relevant
{
	case "$variety" in
		all)
			list_pypy3 --all
			list_cpython3 --all
			;;
		all-cpythons)
			list_cpython3 --all
			;;
		fast)
			# Each of these has a side effect of outputting the interpreter path when something is found
			if list_pypy3 --any
			then
				# PyPy3's probably fast, though it needs some tuning before GA I'm told
				:
			elif list_cpython3 --any --require-rcpyxm
			then
				# CPython 3 with Cython is next best
				:
			elif list_cpython3 --any
			then
				# CPython 3 is next best
				:
			else
				echo "$0: Wow, no python?" 1>&2
				exit 1
			fi
			;;
		minimal-a)
			if list_cpython3 --any
			then
				:
			elif list_pypy3 --any
			then
				:
			else
				echo "$0: no minimal-a?" 1>&2
				exit 1
			fi
			;;
		minimal-b)
			if list_pypy3 --any
			then
				:
			elif list_cpython3 --any
			then
				:
			else
				echo "$0: no minimal-b" 1>&2
				exit 1
			fi
			;;
		just-a-cython)
			if list_cython2 --any
			then
				:
			elif list_cython3 --any
			then
				:
			else
				echo "$0: No cython's found" 1>&2
				exit 1
			fi
			;;
		*)
			echo "$0: unrecognized variety: $variety" 1>&2
			exit 1
			;;
	esac
}

if [ "$args" = "" ]
then
	# just list the possibilities
	list_relevant || true
else
	# Execute
	relevant=$(set -eu; list_relevant || true)
	if [ "$relevant" = "" ]
	then
		echo "$0: No relevant interpreters found" 1>&2
		exit 1
	else
		for record in $relevant
		do
            basename=$(set -eu; echo "$record" | awk -F"$sep" ' { print $1 }')
			export basename
			interp=$(set -eu; echo "$record" | awk -F"$sep" ' { print $2 }')
			if [ "$pre_command" != "" ]
			then
				eval "$pre_command"
			fi
			echo "$0: Running with $interp" 1>&2
			if [ "$stdin_pipe" = "" ]
			then
                # shellcheck disable=SC2046
				eval "$interp" $(set -eu; eval echo "$args")
			else
                # shellcheck disable=SC2046,SC2116
				eval $(echo "$stdin_pipe") | eval "$interp" $(set -eu; eval echo "$args")
			fi
			if [ "$post_command" != "" ]
			then
				eval "$post_command"
			fi
		done
	fi
fi