# @brief Common functions and variables for Tool Under Test (TUT).
#
# The script requires variables:
# TUT_PATH - Assumed absolute path to the directory in which the TUT is located.
# TUT_NAME - TUT name (without path).
#
# The script sets the variables:
# TUT - The path (including the name) of the executable TUT.
# error_prompt - Delimiter on error.
# error_head - Header on error.

package require Expect

# Complete the path for Tool Under Test (TUT). For example, on Windows, TUT can be located in the Debug or Release
# subdirectory. Note that Release build takes precedence over Debug.
set conftypes {{} Release Debug}
foreach i $conftypes {
    if { [file executable "$TUT_PATH/$i/$TUT_NAME"] || [file executable "$TUT_PATH/$i/$TUT_NAME.exe"] } {
        set TUT "$TUT_PATH/$i/$TUT_NAME"
        break
    }
}
if {![info exists TUT]} {
    error "$TUT_NAME executable not found"
}

# prompt of error message
set error_prompt ">>>"
# the beginning of error message
set error_head "$error_prompt Check-failed"

# detection on eof and timeout will be on every expect command
expect_after {
    eof {
        global error_head
        error "$error_head unexpected termination"
    } timeout {
        global error_head
        error "$error_head timeout"
    }
}

# Run commands from command line
tcltest::loadTestedCommands

# namespace of internal functions
namespace eval ly::private {}

# Send command 'cmd' to the process, then check output string by 'pattern'.
# Parameter cmd is a string of arguments.
# Parameter pattern is a regex or an exact string to match. If is not specified, only prompt assumed afterwards.
# It must not contain a prompt. There can be an '$' character at the end of the pattern, in which case the regex
# matches the characters before the prompt.
# Parameter 'opt' can contain:
#   -ex     has a similar meaning to the expect command. The 'pattern' parameter is used as a simple string
#           for exact matching of the output. So 'pattern' is not a regular expression but some characters
#           must still be escaped, eg ][.
proc ly_cmd {cmd {pattern ""} {opt ""}} {
    global prompt

    send -- "${cmd}\r"
    expect -- "${cmd}\r\n"

    if { $pattern eq "" } {
        # command without output
        expect ^$prompt
        return
    }

    # definition of an expression that matches failure
    set failure_pattern "\r\n${prompt}$"

    if { $opt eq "" && [string index $pattern end] eq "$"} {
        # check output by regular expression
        # It was explicitly specified how the expression should end.
        set pattern [string replace $pattern end end]
        expect {
            -re "${pattern}\r\n${prompt}$" {}
            -re $failure_pattern {
                error "unexpected output:\n$expect_out(buffer)"
            }
        }
    } elseif { $opt eq "" } {
        # check output by regular expression
        expect {
            -re "${pattern}.*\r\n${prompt}$" {}
            -re $failure_pattern {
                error "unexpected output:\n$expect_out(buffer)"
            }
        }
    } elseif { $opt eq "-ex" } {
        # check output by exact matching
        expect {
            -ex "${pattern}\r\n${prompt}" {}
            -re $failure_pattern {
                error "unexpected output:\n$expect_out(buffer)"
            }
        }
    } else {
        global error_head
        error "$error_head unrecognized value of parameter 'opt'"
    }
}

# Send command 'cmd' to the process, expect some header and then check output string by 'pattern'.
# This function is useful for checking an error that appears in the form of a header.
# Parameter header is the expected header on the output.
# Parameter cmd is a string of arguments.
# Parameter pattern is a regex. It must not contain a prompt.
proc ly_cmd_header {cmd header pattern} {
    global prompt

    send -- "${cmd}\r"
    expect -- "${cmd}\r\n"

    expect {
        -re "$header .*${pattern}.*\r\n${prompt}$" {}
        -re "\r\n${prompt}$" {
            error "unexpected output:\n$expect_out(buffer)"
        }
    }
}

# Whatever is written is sent, output is ignored and then another prompt is expected.
# Parameter cmd is optional and any output is ignored.
proc ly_ignore {{cmd ""}} {
    global prompt

    send "${cmd}\r"
    expect -re "$prompt$"
}

# Send a completion request and check if the anchored regex output matches.
proc ly_completion {input output} {
    global prompt

    send -- "${input}\t"
    expect -re "${input}${output}"
}

# Send a completion request and check if the anchored regex hint options match.
proc ly_hint {input prev_input hints} {
    global prompt

    set output {}
    foreach i $hints {
        # each element might have some number of spaces and CRLF around it
        append output "${i} *(\\r\\n)?"
    }
    set termcode1 "\r\\u001b\\\[0K"
    set termcode2 ".*"

    send -- "${input}\t"
    # expecting the hints, previous input from which the hints were generated
    # and some number of terminal control characters
    expect -re "${output}${termcode1}${prompt}${prev_input}${termcode2}"
}
