//! momentum-lab — an aura research project: node logic + the exported vocabulary.
//!
//! Every node type this crate provides is registered in `vocabulary()` /
//! `type_ids()` under the `momentum_lab::` namespace prefix; the `aura` host loads
//! this cdylib per invocation and merges it with the std vocabulary.

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

/// One-input f64 pass-through. Emits `None` until its input has a value.
pub struct Identity {
    out: [Cell; 1],
}

impl Identity {
    pub fn new() -> Self {
        Self { out: [Cell::from_f64(0.0)] }
    }
    pub fn builder() -> PrimitiveBuilder {
        PrimitiveBuilder::new(
            "momentum_lab::Identity",
            NodeSchema {
                inputs: vec![PortSpec {
                    kind: ScalarKind::F64,
                    firing: Firing::Any,
                    name: "value".into(),
                }],
                output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
                params: vec![],
            },
            |_| Box::new(Identity::new()),
        )
    }
}

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

impl Node for Identity {
    fn lookbacks(&self) -> Vec<usize> {
        vec![1]
    }
    fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
        let w = ctx.f64_in(0);
        if w.is_empty() {
            return None;
        }
        self.out[0] = Cell::from_f64(w[0]);
        Some(&self.out)
    }
    fn label(&self) -> String {
        "momentum_lab::Identity".to_string()
    }
}

/// One-input f64 sign-flip. Emits `-value` (so a downstream consumer can invert a
/// bias). Authored by the fieldtester as the "author your own node" step.
pub struct Negate {
    out: [Cell; 1],
}

impl Negate {
    pub fn new() -> Self {
        Self { out: [Cell::from_f64(0.0)] }
    }
    pub fn builder() -> PrimitiveBuilder {
        PrimitiveBuilder::new(
            "momentum_lab::Negate",
            NodeSchema {
                inputs: vec![PortSpec {
                    kind: ScalarKind::F64,
                    firing: Firing::Any,
                    name: "value".into(),
                }],
                output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
                params: vec![],
            },
            |_| Box::new(Negate::new()),
        )
    }
}

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

impl Node for Negate {
    fn lookbacks(&self) -> Vec<usize> {
        vec![1]
    }
    fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
        let w = ctx.f64_in(0);
        if w.is_empty() {
            return None;
        }
        self.out[0] = Cell::from_f64(-w[0]);
        Some(&self.out)
    }
    fn label(&self) -> String {
        "momentum_lab::Negate".to_string()
    }
}

fn vocabulary(type_id: &str) -> Option<PrimitiveBuilder> {
    match type_id {
        "momentum_lab::Identity" => Some(Identity::builder()),
        "momentum_lab::Negate" => Some(Negate::builder()),
        _ => None,
    }
}

fn type_ids() -> &'static [&'static str] {
    &["momentum_lab::Identity", "momentum_lab::Negate"]
}

aura_core::aura_project! {
    namespace: "momentum_lab",
    vocabulary: vocabulary,
    type_ids: type_ids,
}
