#!/usr/bin/env bash
# $Id: PhysicalTest 85 2026-09-05 08:27:31Z peter $
#
# Copyright (C) 2026 Peter Felecan
# Assisted by Claude Sonnet 5 (Anthropic)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PhysicalTest — two-host physical CAN integration test driver.
#
# Usage (System A — runs svahsim):
#   PhysicalTest --role sim [--interface can0] [--script PATH]
#
# Usage (System B — runs svah):
#   PhysicalTest --role app [--interface can0] [--axis 0]
#                           [--timeout MS] [--hold S]
#
# --hold S: after the IDLE transition, svah stays on bus S seconds
# polling heartbeats. A timeout (cable disconnect) causes exit non-zero.
# Used for PI-05 (bus disruption test).
#
# Exit status: 0 PASS, 1 FAIL, 2 usage error

set -e
trap 'echo "PhysicalTest: unexpected error on line ${LINENO}" >&2' ERR

# ------------------------------------------------------------------ #
# Defaults                                                             #
# ------------------------------------------------------------------ #

INTERFACE="can0"
BITRATE="250000"
AXIS="0"
TIMEOUT="2000"
HOLD=""
SCRIPT="PhysicalNoMotion.svs"
ROLE=""

# Binary locations: env vars, then PATH, then current directory
if [ -z "${SVAHSIM:-}" ]
then
{
    if command -v svahsim > /dev/null 2>&1
    then
    {
        SVAHSIM="svahsim"
    }
    else
    {
        SVAHSIM="./svahsim"
    }
    fi
}
fi

if [ -z "${SVAH:-}" ]
then
{
    if command -v svah > /dev/null 2>&1
    then
    {
        SVAH="svah"
    }
    else
    {
        SVAH="./svah"
    }
    fi
}
fi

# ------------------------------------------------------------------ #
# Usage                                                                #
# ------------------------------------------------------------------ #

usage()
{
    cat << USAGE
Usage: ${0##*/} --role sim|app [options]

Roles:
  sim   Run svahsim with the given script (System A)
  app   Run svah --no-motion (System B)

Options:
  -i, --interface IFACE   CAN interface (default: ${INTERFACE})
  -b, --bitrate   BPS     CAN bitrate (default: ${BITRATE})
  -a, --axis      ID      Axis node id (default: ${AXIS})
  -t, --timeout   MS      svah state-transition timeout ms (default: ${TIMEOUT})
  -H, --hold      S       svah hold duration in seconds after IDLE
                          (PI-05 bus disruption test, default: none)
  -s, --script    PATH    .svs script for svahsim (default: ${SCRIPT})
  -h, --help              Print this help and exit

Environment variables:
  SVAH      path to svah binary (overrides PATH/current-directory search)
  SVAHSIM   path to svahsim binary

Recommended sequence:
  1. System A:  PhysicalTest --role sim --interface can0
  2. System B:  PhysicalTest --role app --interface can0

For PI-05 (bus disruption test):
  1. System A:  PhysicalTest --role sim --interface can0 \\
                --script LongCycle.svs
  2. System B:  PhysicalTest --role app --interface can0 --hold 60
     Disconnect the CAN cable within the 60-second hold window.
     svah exits non-zero when the next heartbeat times out.
     Note: svahsim continues running until its timeline ends (60 s).
     This is normal in play mode — it has no EXPECT assertions.
USAGE
}

# ------------------------------------------------------------------ #
# Option parsing                                                       #
# ------------------------------------------------------------------ #

ARGS=$(getopt -o i:b:a:t:H:s:h \
    --long interface:,bitrate:,axis:,timeout:,hold:,script:,role:,help \
    -n "${0##*/}" -- "$@") || { usage; exit 2; }

eval set -- "${ARGS}"

while true
do
{
    case "${1}" in
        -i|--interface) INTERFACE="${2}"; shift 2 ;;
        -b|--bitrate)   BITRATE="${2}";   shift 2 ;;
        -a|--axis)      AXIS="${2}";      shift 2 ;;
        -t|--timeout)   TIMEOUT="${2}";   shift 2 ;;
        -H|--hold)      HOLD="${2}";      shift 2 ;;
        -s|--script)    SCRIPT="${2}";    shift 2 ;;
           --role)      ROLE="${2}";      shift 2 ;;
        -h|--help)      usage; exit 0 ;;
        --)             shift; break ;;
        *)              usage; exit 2 ;;
    esac
}
done

if [ -z "${ROLE}" ]
then
{
    echo "${0##*/}: --role sim|app is required" >&2
    usage
    exit 2
}
fi

# ------------------------------------------------------------------ #
# Pre-flight checks                                                    #
# ------------------------------------------------------------------ #

check_interface()
{
    if ! ip link show "${INTERFACE}" > /dev/null 2>&1
    then
    {
        cat << MSG >&2
${0##*/}: interface ${INTERFACE} not found.
Bring it up with:
  sudo modprobe can can_raw gs_usb
  sudo ip link set ${INTERFACE} type can bitrate ${BITRATE}
  sudo ip link set up ${INTERFACE}
MSG
        exit 1
    }
    fi

    local STATE
    STATE=$(ip -o link show "${INTERFACE}" | awk '{print $3}')
    if [[ "${STATE}" != *"UP"* ]]
    then
    {
        echo "${0##*/}: ${INTERFACE} is not UP (state: ${STATE})" >&2
        echo "Run: sudo ip link set up ${INTERFACE}" >&2
        exit 1
    }
    fi
}

check_interface

# ------------------------------------------------------------------ #
# Run                                                                  #
# ------------------------------------------------------------------ #

case "${ROLE}" in
    sim)
        if [ ! -f "${SCRIPT}" ]
        then
        {
            echo "${0##*/}: script not found: ${SCRIPT}" >&2
            exit 1
        }
        fi

        if ! command -v "${SVAHSIM}" > /dev/null 2>&1 && \
           [ ! -x "${SVAHSIM}" ]
        then
        {
            echo "${0##*/}: ${SVAHSIM} not found" \
                 "(not in PATH, not in current directory)" >&2
            exit 1
        }
        fi

        echo "PhysicalTest: starting svahsim on ${INTERFACE} with ${SCRIPT}"
        echo "PhysicalTest: start svah on System B (role app) now"
        exec "${SVAHSIM}" \
            --interface "${INTERFACE}" \
            --log-on-stderr \
            "${SCRIPT}"
        ;;

    app)
        if ! command -v "${SVAH}" > /dev/null 2>&1 && \
           [ ! -x "${SVAH}" ]
        then
        {
            echo "${0##*/}: ${SVAH} not found" \
                 "(not in PATH, not in current directory)" >&2
            exit 1
        }
        fi

        echo "PhysicalTest: running svah on ${INTERFACE}, axis ${AXIS}"

        SVAH_ARGS=(
            --interface "${INTERFACE}"
            --axis      "${AXIS}"
            --timeout   "${TIMEOUT}"
            --no-motion
            --log-on-stderr
        )

        if [ -n "${HOLD}" ]
        then
        {
            SVAH_ARGS+=(--hold "${HOLD}")
        }
        fi

        # Use if/else to prevent the ERR trap from firing when svah
        # exits non-zero (e.g. bus disruption detected in --hold mode).
        # Per DevelopmentStandard.org: ERR trap does not fire in if tests.
        RET=0
        if "${SVAH}" "${SVAH_ARGS[@]}"
        then
        {
            echo "PhysicalTest: PASS — svah exited 0"
        }
        else
        {
            RET=$?
            echo "PhysicalTest: FAIL — svah exited ${RET}"
        }
        fi
        exit "${RET}"
        ;;

    *)
        echo "${0##*/}: unknown role '${ROLE}' (use sim or app)" >&2
        exit 2
        ;;
esac
