//! Example 3 — the variadic-arity case: `LinComb` over N>2 signal legs.
//!
//! Three moving averages combined with weights [0.5, 0.3, 0.2]. Verifies the
//! arity is fixed by `weights.len()` (3 input slots), and that the recorded
//! output equals the 3-leg weighted sum on every fired cycle. Also confirms
//! the firing barrier: LinComb withholds output until ALL THREE legs warm up
//! (i.e. until the slowest, Sma(4), produces a value).

mod recorder;

use std::cell::RefCell;
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 main() {
    const SMA2: usize = 0;
    const SMA3: usize = 1;
    const SMA4: usize = 2;
    const LINCOMB: usize = 3;
    const REC2: usize = 4;
    const REC3: usize = 5;
    const REC4: usize = 6;
    const REC_OUT: usize = 7;

    let t2: Tape = Rc::new(RefCell::new(Vec::new()));
    let t3: Tape = Rc::new(RefCell::new(Vec::new()));
    let t4: Tape = Rc::new(RefCell::new(Vec::new()));
    let tout: Tape = Rc::new(RefCell::new(Vec::new()));

    let weights = vec![0.5, 0.3, 0.2];

    let nodes: Vec<Box<dyn aura_core::Node>> = vec![
        Box::new(Sma::new(2)),
        Box::new(Sma::new(3)),
        Box::new(Sma::new(4)),
        Box::new(LinComb::new(weights.clone())),
        Box::new(Recorder::new(t2.clone())),
        Box::new(Recorder::new(t3.clone())),
        Box::new(Recorder::new(t4.clone())),
        Box::new(Recorder::new(tout.clone())),
    ];

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

    let edges = vec![
        Edge { from: SMA2, to: LINCOMB, slot: 0, from_field: 0 },
        Edge { from: SMA3, to: LINCOMB, slot: 1, from_field: 0 },
        Edge { from: SMA4, to: LINCOMB, slot: 2, from_field: 0 },
        Edge { from: SMA2, to: REC2, slot: 0, from_field: 0 },
        Edge { from: SMA3, to: REC3, slot: 0, from_field: 0 },
        Edge { from: SMA4, to: REC4, slot: 0, from_field: 0 },
        Edge { from: LINCOMB, to: REC_OUT, slot: 0, from_field: 0 },
    ];

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

    let prices: &[f64] = &[10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.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 (a, b, c, out) = (t2.borrow(), t3.borrow(), t4.borrow(), tout.borrow());
    println!("sma2 rows={}, sma3 rows={}, sma4 rows={}, lincomb rows={}", a.len(), b.len(), c.len(), out.len());

    // Sma(4) warms up last (needs 4 samples -> first value at t=3), so the 3-leg
    // barrier means LinComb's first output is at t=3.
    let first_out_ts = out.first().map(|&(t, _)| t.0);
    println!("LinComb first fires at t={:?} (expect t=3, the slowest leg's warm-up)", first_out_ts);
    assert_eq!(first_out_ts, Some(3), "3-leg warm-up barrier");

    println!("\n  t    sma2    sma3    sma4   lincomb   expected(0.5,0.3,0.2)");
    for &(ts, combined) in out.iter() {
        let s2 = a.iter().find(|&&(t, _)| t == ts).map(|&(_, v)| v).unwrap();
        let s3 = b.iter().find(|&&(t, _)| t == ts).map(|&(_, v)| v).unwrap();
        let s4 = c.iter().find(|&&(t, _)| t == ts).map(|&(_, v)| v).unwrap();
        let expected = 0.5 * s2 + 0.3 * s3 + 0.2 * s4;
        let ok = (combined - expected).abs() < 1e-12;
        println!(
            "  {:>2} {:>7.3} {:>7.3} {:>7.3} {:>8.4}  {:>8.4}  {}",
            ts.0, s2, s3, s4, combined, expected, if ok { "OK" } else { "MISMATCH" }
        );
        assert!(ok, "3-leg weighted sum mismatch at t={}", ts.0);
    }
    println!("\nall 3-leg weighted sums match = OK");
}
