//! `Div` — two-input f64 quotient (input 0 divided by input 1), the ratio
//! combinator RSI-class signals need (`avg_gain / avg_loss`). IEEE-754
//! division, no error channel: `x / 0.0 -> inf` (signed by `x`'s sign),
//! `0.0 / 0.0 -> NaN` — Rust's native `f64` division already follows this,
//! so no special-casing is needed.

use aura_core::{Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, ScalarKind};

/// Two-input f64 quotient: input 0 divided by input 1 (IEEE-754). Emits `None`
/// until both inputs have a value.
pub struct Div {
    out: [Cell; 1],
}

impl Div {
    /// Build a `Div` node.
    pub fn new() -> Self {
        Self { out: [Cell::from_f64(0.0)] }
    }

    /// The param-generic recipe for a blueprint primitive: paramless, builds
    /// through `Div::new`.
    pub fn builder() -> PrimitiveBuilder {
        PrimitiveBuilder::new(
            "Div",
            NodeSchema {
                inputs: vec![
                    PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "lhs".into() },
                    PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "rhs".into() },
                ],
                output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
                params: vec![],
            },
            |_| Box::new(Div::new()),
        )
    }
}

impl Default for Div {
    fn default() -> Self {
        Self::new()
    }
}

impl Node for Div {
    fn lookbacks(&self) -> Vec<usize> {
        vec![1, 1]
    }

    fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
        let a = ctx.f64_in(0);
        let b = ctx.f64_in(1);
        if a.is_empty() || b.is_empty() {
            return None;
        }
        self.out[0] = Cell::from_f64(a[0] / b[0]);
        Some(&self.out)
    }

    fn label(&self) -> String {
        "Div".to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use aura_core::{AnyColumn, Scalar, Timestamp};

    #[test]
    fn div_is_quotient_once_both_inputs_present() {
        let mut div = Div::new();
        let mut inputs = vec![
            AnyColumn::with_capacity(ScalarKind::F64, 1),
            AnyColumn::with_capacity(ScalarKind::F64, 1),
        ];

        // only input 0 present -> None
        inputs[0].push(Scalar::f64(10.0)).unwrap();
        assert_eq!(div.eval(Ctx::new(&inputs, Timestamp(0))), None);

        // both present -> a / b
        inputs[1].push(Scalar::f64(4.0)).unwrap();
        assert_eq!(div.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_f64(2.5)].as_slice()));
    }

    #[test]
    fn div_by_zero_is_signed_infinity_zero_over_zero_is_nan() {
        // The IEEE-754 property that makes Div safe with no error channel: a
        // nonzero numerator over zero yields a signed infinity, and 0/0 yields
        // NaN — both representable as plain f64, never a panic or an Err.
        let mut div = Div::new();
        let mut inputs = vec![
            AnyColumn::with_capacity(ScalarKind::F64, 1),
            AnyColumn::with_capacity(ScalarKind::F64, 1),
        ];

        inputs[0].push(Scalar::f64(5.0)).unwrap();
        inputs[1].push(Scalar::f64(0.0)).unwrap();
        let got = div.eval(Ctx::new(&inputs, Timestamp(0))).unwrap();
        assert_eq!(got[0].f64(), f64::INFINITY);

        inputs[0].push(Scalar::f64(0.0)).unwrap();
        inputs[1].push(Scalar::f64(0.0)).unwrap();
        let got = div.eval(Ctx::new(&inputs, Timestamp(0))).unwrap();
        assert!(got[0].f64().is_nan());
    }

    #[test]
    fn input_slots_are_named_lhs_rhs() {
        let names: Vec<String> = Div::builder().schema().inputs.iter().map(|p| p.name.clone()).collect();
        assert_eq!(names, ["lhs", "rhs"]);
    }
}
