/// Cosine similarity in [-1, 1]. Higher is more similar.
pub fn score(a: &[f32], b: &[f32]) -> f32 {
    debug_assert_eq!(a.len(), b.len());
    let dot    = dot_product(a, b);
    let norm_a = dot_product(a, a).sqrt();
    let norm_b = dot_product(b, b).sqrt();
    let denom  = norm_a * norm_b;
    if denom < f32::EPSILON { 0.0 } else { dot / denom }
}

pub fn normalize(v: &mut [f32]) {
    let norm = dot_product(v, v).sqrt();
    if norm > f32::EPSILON {
        v.iter_mut().for_each(|x| *x /= norm);
    }
}

pub fn normalized_copy(v: &[f32]) -> Vec<f32> {
    let mut out = v.to_vec();
    normalize(&mut out);
    out
}

#[inline]
fn dot_product(a: &[f32], b: &[f32]) -> f32 {
    a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}

#[cfg(test)]
mod tests {
    use super::*;
    use approx::assert_relative_eq;

    #[test]
    fn identical_vectors() {
        let v = vec![1.0f32, 2.0, 3.0];
        assert_relative_eq!(score(&v, &v), 1.0, epsilon = 1e-6);
    }

    #[test]
    fn orthogonal_vectors() {
        let a = vec![1.0f32, 0.0, 0.0];
        let b = vec![0.0f32, 1.0, 0.0];
        assert_relative_eq!(score(&a, &b), 0.0, epsilon = 1e-6);
    }

    #[test]
    fn opposite_vectors() {
        let a = vec![1.0f32, 0.0, 0.0];
        let b = vec![-1.0f32, 0.0, 0.0];
        assert_relative_eq!(score(&a, &b), -1.0, epsilon = 1e-6);
    }

    #[test]
    fn normalize_produces_unit_vector() {
        let mut v = vec![3.0f32, 4.0];
        normalize(&mut v);
        let len: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
        assert_relative_eq!(len, 1.0, epsilon = 1e-6);
    }
}
