//! Example 4 — edge behaviour of the new combinators.
//!
//! (a) Warm-up barrier: `LinComb` withholds output (`None`) until EVERY leg has
//!     a value. With two legs that warm up at different times (Sma(2) at t=1,
//!     Sma(5) at t=4), the combined output must first appear at t=4, never
//!     folding a cold leg in as 0.0.
//!
//! (b) The empty-weights build error: `LinComb::new(vec![])` panics (a build-time
//!     param error, documented on the public surface), caught here and reported.

mod recorder;

use std::cell::RefCell;
use std::panic;
use std::rc::Rc;

use aura_core::ScalarKind;
use aura_core::{Scalar, Timestamp};
use aura_engine::{Edge, Harness, SourceSpec, Target};
use aura_std::{LinComb, Sma};

use recorder::{Recorder, Tape};

fn warm_up_barrier() {
    const SMA2: usize = 0;
    const SMA5: usize = 1;
    const LINCOMB: usize = 2;
    const REC: usize = 3;

    let tape: Tape = Rc::new(RefCell::new(Vec::new()));

    let nodes: Vec<Box<dyn aura_core::Node>> = vec![
        Box::new(Sma::new(2)),
        Box::new(Sma::new(5)),
        Box::new(LinComb::new(vec![1.0, 1.0])),
        Box::new(Recorder::new(tape.clone())),
    ];

    let sources = vec![SourceSpec {
        kind: ScalarKind::F64,
        targets: vec![
            Target { node: SMA2, slot: 0 },
            Target { node: SMA5, slot: 0 },
        ],
    }];

    let edges = vec![
        Edge { from: SMA2, to: LINCOMB, slot: 0, from_field: 0 },
        Edge { from: SMA5, to: LINCOMB, slot: 1, from_field: 0 },
        Edge { from: LINCOMB, to: REC, slot: 0, from_field: 0 },
    ];

    let mut harness = Harness::bootstrap(nodes, sources, edges).expect("bootstrap");

    let prices: &[f64] = &[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0];
    let stream: Vec<(Timestamp, Scalar)> = prices
        .iter()
        .enumerate()
        .map(|(i, &p)| (Timestamp(i as i64), Scalar::F64(p)))
        .collect();

    harness.run(vec![stream]);

    let tape = tape.borrow();
    let first = tape.first().map(|&(t, _)| t.0);
    println!("(a) warm-up: LinComb first fires at t={:?} (expect t=4, when Sma(5) warms up)", first);
    for (ts, v) in tape.iter() {
        println!("      t={:>2}  combined={:.4}", ts.0, v);
    }
    assert_eq!(first, Some(4), "barrier: no output until the slower leg (Sma(5)) is present");
    // No leading rows that would betray a cold leg silently read as 0.0.
    assert!(
        tape.iter().all(|&(t, _)| t.0 >= 4),
        "no row before both legs are warm (no implicit cold-leg 0.0)"
    );
    println!("    -> no cold-leg-as-0.0 rows; barrier honoured = OK");
}

fn empty_weights_panic() {
    print!("(b) empty weights: LinComb::new(vec![]) ... ");
    let prev = panic::take_hook();
    panic::set_hook(Box::new(|_| {})); // silence the default panic print
    let result = panic::catch_unwind(|| {
        let _ = LinComb::new(vec![]);
    });
    panic::set_hook(prev);
    match result {
        Ok(_) => println!("NO PANIC (surface says it should panic!) -- unexpected"),
        Err(_) => println!("panicked as documented = OK"),
    }
    assert!(result.is_err(), "LinComb::new(vec![]) must panic per the public surface");
}

fn main() {
    warm_up_barrier();
    println!();
    empty_weights_panic();
}
