#!/bin/bash
# Copyright 2026 California Institute of Technology
#
# You should have received a copy of the licensing terms for this
# software included in the file “LICENSE” located in the top-level
# directory of this package. If you did not, you can view a copy at
# http://dcc.ligo.org/M1500244/LICENSE.txt

# Pty smoke test of the real TUI binary: copy-to-clipboard, selection, and
# scope mode.
#
#   ./run_tui_smoke.sh [port]         (default port 5099)
#
# Starts the pcaspy soft IOC (loopback only), runs the TUI in a pty via
# script(1) with an 80x24 size, then injects: middle-clicks on the name and
# value cells of row 1 (X1:COUNT), a left-click on row 3 (X1:MODE) to move
# the selection, 's' into scope mode, '>' to expand the history lookback,
# the zoom/pan keys (no-ops on seconds of data, but must not break
# anything), 's' back, a PageDown (clamps the selection to the last
# channel), and a final 'q'.  The captured terminal output is checked for
# the two OSC 52 clipboard sequences, the "copied" feedback, the selection
# marker moving, the scope chart, the history-resize notice, the preview
# pane's info block (80 columns is wide enough for it), and the
# status-line clock.
#
# Needs python3 + pcaspy on PATH, like tests/e2e_ioc.rs.
set -eu
cd "$(dirname "$0")"
PORT="${1:-5099}"

(cd ../.. && cargo build -p term_scope)
BIN="$(cd ../.. && pwd)/target/debug/term_scope"

WORK="$(mktemp -d)"
IOC_PID=
cleanup() {
    [ -n "$IOC_PID" ] && kill "$IOC_PID" 2>/dev/null
    rm -rf "$WORK"
}
trap cleanup EXIT

# Start the IOC with its stdin held open on a fifo so we can stop it cleanly.
mkfifo "$WORK/ioc_stdin"
./run_test_ioc.sh "$PORT" < "$WORK/ioc_stdin" &
IOC_PID=$!
exec 9> "$WORK/ioc_stdin"
sleep 2

export EPICS_CA_ADDR_LIST=127.0.0.1 EPICS_CA_AUTO_ADDR_LIST=NO
export EPICS_CA_SERVER_PORT="$PORT"
# Hide the displays so the local-helper clipboard path (wl-copy/xclip/xsel)
# stays off: the test checks the OSC 52 output and must not overwrite the
# real clipboard of whoever runs it.
unset WAYLAND_DISPLAY DISPLAY
# SGR mouse coordinates are 1-based; button 0 is left, 1 is middle.  Layout:
# marker column 1, name columns 3-19 (X1:DOES_NOT_EXIST is 17 wide), value
# from column 21.  Row 1 is X1:COUNT, row 3 is X1:MODE.  Each click is a
# press+release pair.
(
    sleep 4
    printf '\x1b[<1;4;1M\x1b[<1;4;1m'   # middle-click X1:COUNT's name
    sleep 1
    printf '\x1b[<1;25;1M\x1b[<1;25;1m' # middle-click X1:COUNT's value
    sleep 1
    printf '\x1b[<0;4;3M\x1b[<0;4;3m'   # left-click row 3: select X1:MODE
    sleep 1
    printf 's'                          # scope the selected channel
    sleep 2
    printf 'p'                          # pause: badge + pause-time x label
    sleep 1
    printf 'p'                          # unpause
    sleep 1
    printf '>'                          # expand the history: 15 -> 20 min
    sleep 1
    printf '+h0'                        # zoom/pan/restore: no-ops on seconds
    sleep 1                             # of data, but must not break anything
    printf 's'                          # back to the list
    sleep 1
    printf '\x1b[6~'                    # PageDown: clamp to the last channel
    sleep 1
    printf 'q'
    sleep 1
# --lookback 15 exercises the CLI option, and keeps the '>' press below the
# 24 h maximum (the default is "auto" = 24 h) so it produces a resize notice.
) | script -qec "stty cols 80 rows 24; timeout 25 $BIN --lookback 15 data/channels.txt" \
    "$WORK/tui_out.raw"

echo quit >&9
exec 9>&-
wait "$IOC_PID" 2>/dev/null || true
IOC_PID=

mapfile -t COPIES < <(grep -a -o ']52;c;[A-Za-z0-9+/=]*' "$WORK/tui_out.raw" \
    | sed 's/^]52;c;//' | while read -r b64; do
        echo "$b64" | base64 -d
        echo
    done)

FAIL=0
if [ "${COPIES[0]:-}" = "X1:COUNT" ]; then
    echo "ok: middle-click on the name copied \"${COPIES[0]}\""
else
    echo "FAIL: name copy got \"${COPIES[0]:-<nothing>}\", wanted X1:COUNT"
    FAIL=1
fi
if [[ "${COPIES[1]:-}" =~ ^[0-9]+$ ]]; then
    echo "ok: middle-click on the value copied counter value \"${COPIES[1]}\""
else
    echo "FAIL: value copy got \"${COPIES[1]:-<nothing>}\", wanted an integer"
    FAIL=1
fi
if grep -aq 'copied "X1:COUNT"' "$WORK/tui_out.raw"; then
    echo 'ok: status line showed the copied feedback'
else
    echo 'FAIL: no "copied" feedback in the status line'
    FAIL=1
fi
# The selection marker '>' redrawn at row 3, column 1 (style codes may
# precede it within the same cursor-move run).
if grep -aqP '\x1b\[3;1H(\x1b\[[0-9;]*m)*>' "$WORK/tui_out.raw"; then
    echo 'ok: left-click moved the selection marker to row 3'
else
    echo 'FAIL: no selection marker drawn at row 3'
    FAIL=1
fi
# Scope mode: a bordered chart appears (corner glyph) with delta-time labels.
if grep -aq $'\xe2\x94\x8c' "$WORK/tui_out.raw"; then
    echo 'ok: scope mode drew a bordered chart'
else
    echo 'FAIL: no chart border found'
    FAIL=1
fi
if grep -aqP -- '-[0-9]+s' "$WORK/tui_out.raw"; then
    echo 'ok: scope x-axis shows delta-time labels'
else
    echo 'FAIL: no delta-time x labels'
    FAIL=1
fi
if grep -aqP '[0-9]{2}:[0-9]{2}:[0-9]{2}' "$WORK/tui_out.raw"; then
    echo 'ok: status line shows the HH:MM:SS clock'
else
    echo 'FAIL: no clock in the output'
    FAIL=1
fi
# Preview pane (80 columns leaves 42 for it): the info block shows the
# backend description and a state row derived from the live value.
if grep -aq 'element count' "$WORK/tui_out.raw"; then
    echo 'ok: preview pane shows the channel description'
else
    echo 'FAIL: no channel description in the preview pane'
    FAIL=1
fi
if grep -aqP 'state +(\x1b\[[0-9;]*m)*connected' "$WORK/tui_out.raw"; then
    echo 'ok: preview pane derives a connected state row'
else
    echo 'FAIL: no connected state row in the preview pane'
    FAIL=1
fi
# PageDown clamps the selection to the last channel (row 6,
# X1:DOES_NOT_EXIST), whose preview is info-only and disconnected.
if grep -aqP '\x1b\[6;1H(\x1b\[[0-9;]*m)*>' "$WORK/tui_out.raw"; then
    echo 'ok: PageDown moved the selection marker to the last row'
else
    echo 'FAIL: no selection marker drawn at row 6 after PageDown'
    FAIL=1
fi
if grep -aqP 'state +(\x1b\[[0-9;]*m)*disconnected' "$WORK/tui_out.raw"; then
    echo 'ok: disconnected channel shows a disconnected state row'
else
    echo 'FAIL: no disconnected state row in the preview pane'
    FAIL=1
fi
if grep -aq 'history: 20 min' "$WORK/tui_out.raw"; then
    echo 'ok: history expand showed the lookback notice'
else
    echo 'FAIL: no history-resize notice after ">"'
    FAIL=1
fi
if grep -aqP 'PAUSED [0-9]{2}:[0-9]{2}:[0-9]{2}' "$WORK/tui_out.raw"; then
    echo 'ok: pause badge carries the pause timestamp'
else
    echo 'FAIL: no timestamped PAUSED badge'
    FAIL=1
fi
# While paused the scope x-axis right edge shows the same time as the badge.
# In an 80x24 terminal the x-axis labels are drawn on row 22.
PAUSE_TIME="$(grep -aoP 'PAUSED \K[0-9]{2}:[0-9]{2}:[0-9]{2}' "$WORK/tui_out.raw" | head -1 || true)"
if [ -n "$PAUSE_TIME" ] &&
    grep -aqP "\x1b\[22;[0-9]+H(\x1b\[[0-9;]*m)*${PAUSE_TIME}" "$WORK/tui_out.raw"; then
    echo "ok: paused scope labels the right edge with the pause time ($PAUSE_TIME)"
else
    echo 'FAIL: no pause-time x-axis label in the scope view'
    FAIL=1
fi

[ "$FAIL" -eq 0 ] && echo PASS
exit "$FAIL"
