// test_msp2_stim.cxx - host-side proof of the Candidate A stimulus, no VSI needed.
//
//   g++ -std=c++11 -o test_msp2_stim test_msp2_stim.cxx && ./test_msp2_stim
//
// Runs the scripted stimulus for 100 steps and feeds each output straight into
// the reacting class the twin wires it to (the same wiring as msp2_A.vsi.cmd,
// minus the fabric), then asserts what a real run must show. Ends with a
// NEGATIVE control: a watchdog whose timeout exceeds the stall never trips.
#include "msp2_stim.h"
#include <cstdio>
#include <cstdlib>

static int failures = 0;
#define CHECK(cond, msg) do { if (cond) std::printf("  ok   %s\n", msg); \
                              else { std::printf("  FAIL %s\n", msg); failures++; } } while (0)

int main() {
    const long STEPS = 100;
    msp2::TimeSource ts; msp2::CardDiscretes cd; msp2::SafetyHeartbeat hb;
    msp2::CardTimeLock lock; msp2::ChassisMonitor chassis; msp2::Watchdog wd;
    std::vector<int> states; int lock_step = -1, trip_step = -1, recover_step = -1;
    for (long n = 0; n < STEPS; ++n) {
        ts.step(n); cd.step(n); hb.step(n);                       // stimulus (card side / platform time)
        lock.observe(ts.pps, ts.irigb, ts.refclk);                // card <- time source
        int s = chassis.observe(cd.card_present, cd.health_ok, cd.bit_fail);   // chassis <- card
        int was = wd.tripped; wd.observe(hb.mon);                  // safety monitor <- card
        if (lock.locked && lock_step < 0) lock_step = (int)n;
        if (wd.tripped && !was && trip_step < 0) trip_step = (int)n;
        if (!wd.tripped && was && recover_step < 0) recover_step = (int)n;
        if (states.empty() || states.back() != s) states.push_back(s);
    }
    std::printf("chassis states seen:");
    for (size_t i = 0; i < states.size(); ++i) std::printf(" %s", msp2::ChassisMonitor::name((msp2::ChassisMonitor::State)states[i]));
    std::printf("\nlock at step %d, watchdog trip at %d, recover at %d, pulses %d, refclk stuck %d\n",
                lock_step, trip_step, recover_step, lock.pulses, lock.refclk_stuck);

    CHECK(states.size() == 5 && states[0] == 0 && states[1] == 1 && states[2] == 2 && states[3] == 3 && states[4] == 2,
          "chassis sees ABSENT -> BOOTING (card inserted at 3) -> HEALTHY (20) -> FAULT (60) -> HEALTHY (65)");
    CHECK(chassis.faults == 1, "exactly one fault episode");
    CHECK(lock.pulses == 10 && lock_step == 20, "10 PPS pulses in 100 steps, time lock on the 3rd (step 20)");
    CHECK(lock.refclk_stuck == 0, "REFCLK toggled every step");
    CHECK(trip_step == 70 + 5 - 1 && recover_step == 70 + 15, "watchdog trips on the 5th silent step (74) and clears when the heartbeat resumes (85)");
    CHECK(wd.trips == 1, "exactly one watchdog trip");
    CHECK(hb.bus == 20, "20 safety frames in 100 steps");
    CHECK(ts.irigb == 43200 + 9, "IRIG-B advanced 9 seconds by the last step");

    // negative control: a watchdog slower than the stall must never trip
    msp2::SafetyHeartbeat hb2; msp2::Watchdog wd2; wd2.timeout = 40;
    for (long n = 0; n < STEPS; ++n) { hb2.step(n); wd2.observe(hb2.mon); }
    CHECK(wd2.trips == 0, "NEGATIVE CONTROL: watchdog with timeout 40 > stall 15 never trips");
    // negative control: a card that never boots keeps the chassis in BOOTING, no HEALTHY, no FAULT
    msp2::CardDiscretes cd2; cd2.boot_steps = 1000; cd2.fault_at = 1000; msp2::ChassisMonitor ch2;
    for (long n = 0; n < STEPS; ++n) { cd2.step(n); ch2.observe(cd2.card_present, cd2.health_ok, cd2.bit_fail); }
    CHECK(ch2.state == msp2::ChassisMonitor::BOOTING && ch2.faults == 0, "NEGATIVE CONTROL: never-booting card stays BOOTING with no fault");

    std::printf("%s (%d failure(s))\n", failures ? "TEST FAILED" : "ALL CHECKS PASSED", failures);
    return failures ? 1 : 0;
}
