#! /usr/bin/env bash
set -e

msg() {
    if [ "$#" -ne 0 ]; then
        echo "$@" | tee --append "$__TEST_EVAL_LOG_FILE" >&2
    else
        cat | tee --append "$__TEST_EVAL_LOG_FILE" >&2
    fi
}

# Use bash built-ins to trim a string
# source: https://stackoverflow.com/a/3352015
trim() {
    local var="$*"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"
    printf '%s' "$var"
}

# contains(string, substring)
#
# Returns 0 if the specified string contains the specified substring,
# otherwise returns 1.
contains() {
    string="$1"
    substring="$2"
    if [ "${string#*"$substring"}" != "$string" ]; then
        return 0 # $substring is in $string
    else
        return 1 # $substring is not in $string
    fi
}

__test_wait_for_pid() {
    local pre_pids_file="$1"

    exec_pids="$(mktemp)"
    ps -eo tty,pid | awk "--assign=tmuxTty=$tmux_tty" '{if ($1 == tmuxTty) {print $2}}' >"$exec_pids"

    while read -r pid; do
        sed --in-place "s/$pid//" "$exec_pids"
    done <"$pre_pids_file"

    gawk --include inplace '{if (NF) {print $0}}' "$exec_pids"

    if [ "$(wc -l <"$exec_pids")" -eq 0 ]; then
        # No further spawned processes left
        return 0
    else
        # Some other program is still running
        pid="$(tail -n 1 "$exec_pids")"
        rm "$exec_pids"
        name="$(trim "$(tr '\0' ' ' <"/proc/$pid/cmdline")")"

        msg "Waiting until command ('$name') finishes (has pid: $pid).."

        # This allows waiting for non-children of the current shell
        # source: https://stackoverflow.com/a/76046235
        tail --pid "$pid" --follow /dev/null &
        wait $!

        # Give the `testShell` some time, to process the next command from a chained command.
        sleep 0.2
        __test_wait_for_pid "$pre_pids_file"
    fi
}

__test_eval() {
    tmux="$__TEST_TMUX"
    tpane="$__TEST_TMUX_PANE"
    file="$1"

    awk --file "$__TEST_EVAL_AWK_CLEAN_FILE" "$file" | while read -r cmd args; do
        case "$cmd" in
        "Type")
            msg "Sending keys to application '$args'.."
            "$tmux" send-keys -t "$tpane": "$args"
            ;;
        "Sleep")
            msg "Sleeping for '$args' seconds.."
            sleep "$args"
            ;;
        "Exec")
            local pre_exec_pids
            local tmux_tty

            msg "Executing command '$args'.."
            tmux_tty="$("$tmux" list-panes -t "$tpane" -F "#{pane_tty}" | sed 's|/dev/||')"

            pre_exec_pids="$(mktemp)"
            ps -eo tty,pid | awk "--assign=tmuxTty=$tmux_tty" '{if ($1 == tmuxTty) {print $2}}' >"$pre_exec_pids"

            "$tmux" send-keys -t "$tpane": "$args" "Enter"
            sleep 1

            __test_wait_for_pid "$pre_exec_pids"

            rm "$pre_exec_pids"
            msg "Finished command '$args'."
            ;;
        "Expect" | "ExpectNot")
            msg "Trying to match regex ('$args') for currently visible content.."

            get_plane_text() {
                alternate=""
                [ "$__TEST_EVAL_USE_ALTERNATE_SCREEN" = "true" ] && alternate="-a"

                "$tmux" capture-pane -t "$tpane" -p $alternate -S 0 -E -
            }

            matched=""
            if get_plane_text | grep "$args"; then
                matched=true
            else
                matched=false
            fi

            case "$cmd" in
            "Expect")
                if [ "$matched" = true ]; then
                    msg "Regex matched."
                else
                    msg "Failed to find string, matched by regex '$args' on the screen"
                    msg current screen:
                    get_plane_text | msg

                    exit 1
                fi
                ;;
            "ExpectNot")
                if [ "$matched" = false ]; then
                    msg "Regex successfully not matched."
                else
                    msg "Found to find string, matched by regex '$args' on the screen. But expected none"
                    msg current screen:
                    get_plane_text | msg

                    exit 1
                fi
                ;;
            *)
                msg "Entered unrechable code. This is a bug."
                exit 1
                ;;
            esac
            ;;
        "SetGolden")
            msg "Trying to set '$args' as golden file."
            [ -f "$args" ] || {
                msg "Argument is not a file!"
                exit 1
            }
            printf "%s" "$args" >"$__TEST_EVAL_GOLDEN_FILE"
            msg "Set golden file to: '$args'"
            ;;
        *)
            msg "Unrecognized command: '$cmd'"
            exit 1
            ;;
        esac
    done
}