#!/usr/bin/env bash

# Wrapper script to run a single TAP subtest
# Usage: run-single-tap-test.sh <fy_tool_path> <test_suite_name> <test_id>

test_suite="$1"
test_id="$2"

if [ x"$FY_TOOL" = x -o x"$TEST_DIR" = x -o x"$test_suite" = x -o x"$test_id" = x ]; then
    echo "Error: FY_TOOL, TEST_DIR, test_suite, test_id must exist"
fi

function is_windows_bash() {
    case "$OSTYPE" in
        msys*|cygwin*) return 0 ;;
        *) return 1 ;;
    esac
}

# convert to proper posix paths
if is_windows_bash; then
    if [ -n "$FY_TOOL" ]; then
        FY_TOOL=`cygpath $FY_TOOL`
    fi
    if [ -n "$LIBFYAML_TEST" ]; then
        FY_TOOL=`cygpath $FY_TOOL`
    fi
    if [ -n "$TEST_DIR" ]; then
        TEST_DIR=`cygpath $TEST_DIR`
    fi
    if [ -n "$YAML_TEST_SUITE" ]; then
        YAML_TEST_SUITE=`cygpath $YAML_TEST_SUITE`
    fi
    if [ -n "$JSON_TEST_SUITE" ]; then
        JSON_TEST_SUITE=`cygpath $JSON_TEST_SUITE`
    fi
fi

# if not given in the environment try to adjust
if [ x"$YAML_TEST_SUITE" = x ]; then
	YAML_TEST_SUITE="${TEST_DIR}/test-suite-data"
fi

if [ x"$JSON_TEST_SUITE" = x ]; then
	JSON_TEST_SUITE="${TEST_DIR}/json-test-suite-data"
fi

# run_tool: invoke a target executable, prepending wine when cross-running
# Windows binaries on Linux.  When WINE_EXECUTABLE is unset this is a no-op
# wrapper so all call-sites are identical for native and wine runs.
function run_tool() {
    if [ -n "$WINE_EXECUTABLE" ]; then
        "$WINE_EXECUTABLE" "$@"
    else
        "$@"
    fi
}

function emitter_subtest() {
    local dump_args="$1"
    local parse_args="$2"
    local f="${TEST_DIR}/emitter-examples/${test_id}"
    local t1
    local t2
    local res
    local pass_parse

    t1=$(mktemp)
    t2=$(mktemp)

    res="not ok"
    pass_parse=0

    # Intentionally expand the argument variables as shell words.
    run_tool "${FY_TOOL}" ${parse_args} "$f" >"$t1"
    if [ $? -eq 0 ]; then
        run_tool "${FY_TOOL}" ${dump_args} "$f" | \
            run_tool "${FY_TOOL}" ${parse_args} - >"$t2"
        if [ $? -eq 0 ]; then
            pass_parse=1
        fi
    fi

    if [ "$pass_parse" == "1" ]; then
        diff -u "$t1" "$t2"
        if [ $? -eq 0 ]; then
            res="ok"
        else
            res="not ok"
        fi
    fi

    rm -f "$t1" "$t2"

    echo "$res 1 - $test_id"

    if [ "$res" == "ok" ]; then
        exit 0
    else
        exit 1
    fi
}

# Python pytest suite: handled before FY_TOOL validation (no C tool needed)
case "$test_suite" in
    python)
        if [ -z "$PYTHON3_EXECUTABLE" ]; then
            echo "Error: PYTHON3_EXECUTABLE not set" >&2
            exit 1
        fi
        if [ ! -x "$PYTHON3_EXECUTABLE" ]; then
            echo "Error: Python interpreter not found or not executable: $PYTHON3_EXECUTABLE" >&2
            exit 1
        fi
        exec "${PYTHON3_EXECUTABLE}" -m pytest "${test_id}" -v --tb=short
        ;;
esac

# Validate tool exists.  When running Windows cross-compiled binaries under
# wine the .exe is not marked executable on the host, so we only require the
# +x bit when wine is not in use.
if [ -n "$WINE_EXECUTABLE" ]; then
    if [ ! -f "$FY_TOOL" ]; then
        echo "Error: fy-tool not found: $FY_TOOL" >&2
        exit 1
    fi
else
    if [ ! -x "$FY_TOOL" ]; then
        echo "Error: fy-tool not found or not executable: $FY_TOOL" >&2
        exit 1
    fi
fi

# Validate TEST_DIR exists
if [ ! -d "$TEST_DIR" ]; then
    echo "Error: TEST_DIR not found: $TEST_DIR" >&2
    exit 1
fi

case "$test_suite" in
    libfyaml)
	run_tool "${LIBFYAML_TEST}" "${test_id}"
	exit $?
	;;

    testerrors)
        dir="${TEST_DIR}/test-errors/${test_id}"
        desctxt=$(cat 2>/dev/null "$dir/===")

        t=$(mktemp)
        res="not ok"

        pass_yaml=0
        run_tool "${FY_TOOL}" --dump -r --no-streaming "$dir/in.yaml" >"$t" 2>&1
        if [ $? -eq 0 ]; then
            pass_yaml=1
        fi

        errmsg=$(cat "$t" | head -n1 | sed -e 's/^.*\(:[0-9]\{1,\}:[0-9]\{1,\}:\)/\1/')
        echo "errmsg: $errmsg" >&2

        # Replace with error message
        echo "$errmsg" >"$t"

        # All error tests are expected to fail
        if [ "$pass_yaml" == "0" ]; then
            res="ok"

            # diff is pointless under valgrind
            if [ "x$USE_VALGRIND" == "x" ]; then
                diff_err=0
                diff -uw "$dir/test.error" "$t"
                if [ $? -eq 0 ]; then
                    res="ok"
                else
                    res="not ok"
                fi
            fi
        else
            res="not ok"
        fi

        rm -f "$t"

        echo "$res 1 $test_id - $desctxt"

        if [ "$res" == "ok" ]; then
            exit 0
        else
            exit 1
        fi
        ;;

    testemitter)
        emitter_subtest "${EMITTER_DUMP_ARGS:---dump} ${EXTRA_DUMP_ARGS}" \
            "${EMITTER_PARSE_ARGS:---testsuite --disable-flow-markers}"
        ;;

    testemitter-streaming)
        emitter_subtest "${EMITTER_DUMP_ARGS:---dump} --streaming ${EXTRA_DUMP_ARGS}" \
            "${EMITTER_PARSE_ARGS:---testsuite --disable-flow-markers}"
        ;;

    testemitter-restreaming)
        emitter_subtest "${EMITTER_DUMP_ARGS:---dump} --streaming --recreating ${EXTRA_DUMP_ARGS}" \
            "${EMITTER_PARSE_ARGS:---testsuite --disable-flow-markers}"
        ;;

    testsuite|testsuite-json|testsuite-resolution)
        tst="${YAML_TEST_SUITE}/${test_id}"
        desctxt=$(cat 2>/dev/null "$tst/===")

        t=$(mktemp)

        res="not ok"

        pass_yaml=0
        run_tool "${FY_TOOL}" --testsuite "$tst/in.yaml" >"$t"
        if [ $? -eq 0 ]; then
            pass_yaml=1
        fi

        if [ -e "$tst/error" ]; then
            # Test is expected to fail
            if [ $pass_yaml == "0" ]; then
                res="ok"
            else
                res="not ok"
            fi
        else
            # Test is expected to pass
            if [ $pass_yaml == "1" ]; then
                diff -u "$tst/test.event" "$t"
                if [ $? -eq 0 ]; then
                    res="ok"
                else
                    res="not ok"
                fi
            else
                res="not ok"
            fi
        fi

        rm -f "$t"

        echo "$res 1 $test_id - $desctxt"

        if [ "$res" == "ok" ]; then
            exit 0
        else
            exit 1
        fi
        ;;

    testsuite-generic)
        tst="${YAML_TEST_SUITE}/${test_id}"
        desctxt=$(cat 2>/dev/null "$tst/===")

        t_output=$(mktemp)

        res="not ok"

        pass_yaml=0
        run_tool "${FY_TOOL}" --generic-testsuite --keep-style \
            --schema yaml1.2-failsafe "$tst/in.yaml" >"$t_output" 2>/dev/null
        if [ $? -eq 0 ]; then
            pass_yaml=1
        fi

        if [ -e "$tst/error" ]; then
            if [ $pass_yaml == "0" ]; then
                res="ok"
            else
                res="not ok"
            fi
        else
            if [ $pass_yaml == "1" ]; then
                diff -u "$tst/test.event" "$t_output"
                if [ $? -eq 0 ]; then
                    res="ok"
                else
                    res="not ok"
                fi
            else
                res="not ok"
            fi
        fi

        rm -f "$t_output"

        echo "$res 1 $test_id - $desctxt"

        if [ "$res" == "ok" ]; then
            exit 0
        else
            exit 1
        fi
        ;;

    testsuite-evstream)
        tst="${YAML_TEST_SUITE}/${test_id}"
        desctxt=$(cat 2>/dev/null "$tst/===")

        # Skip tests that are expected to fail
        if [ -e "$tst/error" ]; then
            # echo "ok 1 $test_id - $desctxt # SKIP expected to fail"
            echo "skip 1 $test_id - $desctxt # SKIP expected to fail"
            exit 0
        fi

	# xfails
	case "${test_id}" in
            2JQS)
                echo "skip 1 $test_id - $desctxt # SKIP expected to fail"
		exit 0
		;;
        esac

        t=$(mktemp)

        res="not ok"

	# run the test using document-event-stream
	run_tool "${FY_TOOL}" --testsuite --document-event-stream "$tst/in.yaml" >"$t" 2>/dev/null
	if [ $? -eq 0 ]; then
	    diff -u "$tst/test.event" "$t"
	    if [ $? -eq 0 ]; then
	        res="ok"
	    fi
	fi

        rm -f "$t"

        echo "$res 1 $test_id - $desctxt"

        if [ "$res" == "ok" ]; then
            exit 0
        else
            exit 1
        fi
        ;;

    jsontestsuite)
        tf="$test_id"
        f="${JSON_TEST_SUITE}/test_parsing/${tf}"

        # Determine expected result based on prefix
        case "$tf" in
            y_*)
                # Expected to pass
                run_tool "${FY_TOOL}" --testsuite --streaming "$f" >/dev/null 2>&1
                if [ $? -eq 0 ]; then
                    res="ok"
                else
                    res="not ok"
                fi
                ;;
            n_*)
                # Expected to fail
                run_tool "${FY_TOOL}" --testsuite --streaming "$f" >/dev/null 2>&1
                if [ $? -eq 0 ]; then
                    res="not ok"
                else
                    res="ok"
                fi
                ;;
            i_*)
                # Implementation defined - we'll accept either result as ok
                run_tool "${FY_TOOL}" --testsuite --streaming "$f" >/dev/null 2>&1
                res="ok"
                ;;
            *)
                res="not ok"
                ;;
        esac

        echo "$res 1 - $tf"

        if [ "$res" == "ok" ]; then
            exit 0
        else
            exit 1
        fi
        ;;

    testreflection)
        tst="${TEST_DIR}/reflection-data/${test_id}"
        desctxt=$(cat 2>/dev/null "$tst/===")
        def="$tst/definition.h"
        meta="$tst/meta"
        entry=$(cat 2>/dev/null "$tst/entry")
        in_file="$tst/in.yaml"

        t=$(mktemp)
        t2=$(mktemp)

        res="not ok"

        meta_args=()
        def_args=()
        if [ -f "$def" ]; then
            def_args=("--import-c-file" "$def")
        fi
        if [ -f "$meta" ]; then
            metaval=$(cat "$meta" | head -n 1)
            meta_args=("--entry-meta" "${metaval}")
        fi

        pass_yaml=0
        run_tool "${FY_TOOL}" --reflect "${def_args[@]}" "${meta_args[@]}" --entry-type "${entry}" "${in_file}" >"$t"
        if [ $? -eq 0 ]; then
            # generate test.event out of the file
            run_tool "${FY_TOOL}" --testsuite --disable-flow-markers --disable-doc-markers --disable-scalar-styles "$t" >"$t2"
            if [ $? -eq 0 ]; then
                pass_yaml=1
            fi
        fi

        if [ -e "$tst/error" ]; then
            # test is expected to fail
            if [ $pass_yaml == "0" ]; then
                res="ok"
            else
                res="not ok"
            fi
        else
            # test is expected to pass
            if [ $pass_yaml == "1" ]; then
                diff -u "$tst/test.event" "$t2"
                if [ $? -eq 0 ]; then
                    res="ok"
                else
                    res="not ok"
                fi
            else
                res="not ok"
            fi
        fi

        rm -f "$t"
	rm -f "$t2"

        echo "$res 1 $test_id - $desctxt"

        if [ "$res" == "ok" ]; then
            exit 0
        else
            exit 1
        fi
        ;;


    testreflection-packed)
        tst="${TEST_DIR}/reflection-data/${test_id}"
        desctxt=$(cat 2>/dev/null "$tst/===")
        def="$tst/definition.h"
        meta="$tst/meta"
        entry=$(cat 2>/dev/null "$tst/entry")
        in_file="$tst/in.yaml"

        t=$(mktemp)
        t2=$(mktemp)
        blob=$(mktemp)

        res="not ok"

        meta_args=()
        if [ -f "$meta" ]; then
            metaval=$(cat "$meta" | head -n 1)
            meta_args=("--entry-meta" "${metaval}")
        fi

        # step 1: generate packed blob from C header
        pass_blob=0
        run_tool "${FY_TOOL}" --reflect --import-c-file "$def" --dry-run --generate-blob "$blob" 2>/dev/null
        if [ $? -eq 0 ]; then
            pass_blob=1
        fi

        pass_yaml=0
        if [ $pass_blob -eq 1 ]; then
            # step 2: use packed blob instead of C header
            run_tool "${FY_TOOL}" --reflect --import-blob "$blob" "${meta_args[@]}" --entry-type "${entry}" "${in_file}" >"$t" 2>/dev/null
            if [ $? -eq 0 ]; then
                run_tool "${FY_TOOL}" --testsuite --disable-flow-markers --disable-doc-markers --disable-scalar-styles "$t" >"$t2" 2>/dev/null
                if [ $? -eq 0 ]; then
                    pass_yaml=1
                fi
            fi
        fi

        if [ -e "$tst/error" ]; then
            if [ $pass_yaml == "0" ]; then
                res="ok"
            else
                res="not ok"
            fi
        else
            if [ $pass_yaml == "1" ]; then
                diff -u "$tst/test.event" "$t2"
                if [ $? -eq 0 ]; then
                    res="ok"
                else
                    res="not ok"
                fi
            else
                res="not ok"
            fi
        fi

        rm -f "$t" "$t2" "$blob"

        echo "$res 1 $test_id - $desctxt (packed)"

        if [ "$res" == "ok" ]; then
            exit 0
        else
            exit 1
        fi
        ;;
    *)
        echo "Unknown test suite: $test_suite" >&2
        exit 1
        ;;
esac
