#!/bin/bash

set -eu
set -o pipefail

cmd=""
file=""
stdin=/dev/null
give_python_version=True

function usage
{
    retval="$1"

    case "$retval" in
        0)
            ;;
        *)
            exec 1>&2
            ;;
    esac

    echo "Usage: $0 --command python-command --file python-file --ellide-python-version --stdin filename"
    echo
    echo "--stdin allows you to specify to redirect into each interpreter. It defaults to /dev/null"
    (
    echo "--give-python-version instructs this command to output the 'version of python' implemented - nice for Pypy, Jython,"
    echo "micropython"
    ) | fmt

    exit "$retval"
}

while [ "$#" -ge 1 ]
do
    case "$1" in
        --ellide-python-version)
            give_python_version=False
            ;;
        --command)
            cmd="$2"
            shift
            ;;
        --stdin)
            stdin="$2"
            shift
            ;;
        --file)
            file="$2"
            shift
            ;;
        -h|--help)
            usage 0
            ;;
        *)
            echo "$0: Unrecognized option: $1" 1>&2
            usage 1
            ;;
    esac
    shift
done

specified=0
if [ "$cmd" != "" ]
then
    specified=$((specified+1))
fi
if [ "$file" != "" ]
then
    specified=$((specified+1))
fi
case "$specified" in
    0)
        echo "$0: You must specify exactly one of --command and --file" 1>&2
        usage 1
        ;;
    1)
        # Good, we got exactly one, fall through
        ;;
    2)
        echo "$0: You must not specify both --command and --file" 1>&2
        usage 1
        ;;
    *)
        echo "$0: Internal error" 1>&2
        exit 1
        ;;
esac

function run
{
    interpreter="$1"
    if [ "$cmd" = "" ]
    then
        if [ "$file" = "" ]
        then
            echo "$0: Internal error 2" 1>&2
        else
            "$interpreter" "$file" < "$stdin"
        fi
    else
        if [ "$file" = "" ]
        then
            if ! dash_c_understood "$interpreter"
            then
                echo skipped
                return 0
            fi
            "$interpreter" -c "$cmd" < "$stdin"
        else
            echo "$0: Internal error 3" 1>&2
        fi
    fi
}

function dash_c_understood
{
    interp="$1"
    if ! output=$(set -eu; "$interp" -c '1' 2>&1)
    then
        # 0.9 does not understand -c
        return 1
    fi
    return 0
}

function get_interp_vers
{
    interp="$1"
    if ! dash_c_understood "$interp"
    then
        echo unknown
        return 1
    fi
    "$interp" -c '
via_platform = 0
check_sys = 0
via_sys_version_info = 0
via_sys_version = 0
test_sys = 0
try:
    import platform
except (ImportError, NameError):
    # We have no platform module - try to get the info via the sys module
    check_sys = 1

if not check_sys:
    if hasattr(platform, "python_version"):
        via_platform = 1
    else:
        check_sys = 1

if check_sys:
    try:
        import sys
        test_sys = 1
    except (ImportError, NameError):
        # just let via_sys_version_info and via_sys_version remain False - we have no sys module
        pass

if test_sys:
    if hasattr(sys, "version_info"):
        via_sys_version_info = 1
    elif hasattr(sys, "version"):
        via_sys_version = 1
    else:
        # just let via_sys remain False
        pass

if via_platform:
    # This gives pretty good info, but is not available in older interpreters.  Also, micropython has a
    # platform module that does not really contain anything.
    print(platform.python_version())
elif via_sys_version_info:
    # This is compatible with some older interpreters, but does not give quite as much info.
    print("%s.%s.%s" % sys.version_info[:3])
elif via_sys_version:
    import string
    # This is compatible with some older interpreters, but does not give quite as much info.
    verbose_version = sys.version
    version_list = string.split(verbose_version)
    print(version_list[0])
else:
    print("unknown")
'
}

function format
{
    # shellcheck disable=SC2046
    if [ $(echo "$output" | wc -l) -gt 1 ]
    then
        echo
        # shellcheck disable=SC2001
        echo "$output" | sed 's/^/    /'
    else
        echo "$output"
    fi
}

function sort_by_version
{
    python3 -c '
import sys
import decimal
interps=[]
for line in (line.rstrip("\n") for line in sys.stdin):
    # line EG: /usr/local/cpython-3.10/bin/python
    fields = line.split("-")
    vers_and_interp=fields[1]
    fields2 = vers_and_interp.split("/")
    vers = fields2[0]
    fields3 = vers.partition(".")
    major_vers = int(fields3[0])
    minor_vers = decimal.Decimal(fields3[2])
    interps.append((major_vers, minor_vers, line))
interps.sort()
for major_vers, minor_vers, line in interps:
    print(line)
'
}

function interpreters
(
    find /usr/local/cpython-*/bin/python -print 2> /dev/null | sort_by_version
    find /usr/local/jython-*/bin/jython -print 2> /dev/null
    find /usr/local/pypy-*/bin/pypy -print 2> /dev/null | sort_by_version
    find /usr/local/pypy3-*/bin/pypy3 -print 2> /dev/null | sort_by_version
    find /usr/local/micropython-*/bin/micropython -print 2> /dev/null
    find /usr/local/tauthon-*/bin/tauthon -print 2> /dev/null
)

summary_file="/tmp/pythons.$$"
# shellcheck disable=SC2064
trap "rm -f \"$summary_file\"" 0 1 15

for interpreter in $(interpreters)
do
    case "$give_python_version" in
        True)
            # platform.python_version() is nice for this, but it doesn't go as far back in Python history as sys.version_info
            if vers=$(get_interp_vers "$interpreter")
            then
                vers_string=" ($vers)"
            else
                vers_string=" (unknown)"
            fi
            ;;
        False)
            vers_string=""
            ;;
        *)
            echo "$0: Internal error: \$give_python_version not True or False: $give_python_version" 1>&2
            exit 1
            ;;
    esac

    if output=$(run "$interpreter" -c "$cmd" 2>&1)
    then
        if [ "$output" = skipped ]
        then
            echo "$interpreter""$vers_string" skipped "$(format "$output")"
            echo skipped >> "$summary_file"
        else
            echo "$interpreter""$vers_string" good "$(format "$output")"
            echo good >> "$summary_file"
        fi
    else
        echo "$interpreter""$vers_string" 'bad ' "$(format "$output")"
        echo bad >> "$summary_file"
    fi
done

echo
histogram < "$summary_file"