//! RMath: a math library for building CPU-based renderers.

#![allow(dead_code)]

mod normal;
mod point;
mod sealed;
mod utils;
mod vector;
pub mod wide4;
mod xform;

use std::ops::{Add, Mul, Neg, Sub};

pub use self::{
    normal::Normal, point::Point, vector::Vector, xform::AsXform, xform::Xform, xform::XformFull,
};

/// Trait for calculating dot products.
pub trait DotProduct {
    fn dot(self, other: Self) -> f32;

    fn dot_fast(self, other: Self) -> f32;
}

#[inline(always)]
pub fn dot<T: DotProduct>(a: T, b: T) -> f32 {
    a.dot(b)
}

#[inline(always)]
pub fn dot_fast<T: DotProduct>(a: T, b: T) -> f32 {
    a.dot_fast(b)
}

/// Trait for calculating cross products.
pub trait CrossProduct {
    fn cross(self, other: Self) -> Self;

    fn cross_fast(self, other: Self) -> Self;
}

#[inline(always)]
pub fn cross<T: CrossProduct>(a: T, b: T) -> T {
    a.cross(b)
}

#[inline(always)]
pub fn cross_fast<T: CrossProduct>(a: T, b: T) -> T {
    a.cross_fast(b)
}

//-------------------------------------------------------------

/// Trait representing types that can do fused multiply-add.
trait FMulAdd {
    /// `(self * b) + c` with only one floating point rounding error.
    fn fma(self, b: Self, c: Self) -> Self;
}

impl FMulAdd for f32 {
    fn fma(self, b: Self, c: Self) -> Self {
        self.mul_add(b, c)
    }
}

/// `(a * b) - (c * d)` but done with high precision via floating point tricks.
///
/// See https://pharr.org/matt/blog/2019/11/03/difference-of-floats
#[inline(always)]
fn difference_of_products<T>(a: T, b: T, c: T, d: T) -> T
where
    T: Copy + FMulAdd + Add<Output = T> + Mul<Output = T> + Neg<Output = T>,
{
    let cd = c * d;
    let dop = a.fma(b, -cd);
    let err = (-c).fma(d, cd);
    dop + err
}

/// `(a * b) + (c * d)` but done with high precision via floating point tricks.
#[inline(always)]
fn sum_of_products<T>(a: T, b: T, c: T, d: T) -> T
where
    T: Copy + FMulAdd + Add<Output = T> + Mul<Output = T> + Neg<Output = T>,
{
    let cd = c * d;
    let sop = a.fma(b, cd);
    let err = c.fma(d, -cd);
    sop + err
}

/// `a * b` but also returns a rounding error for precise composition
/// with other operations.
#[inline(always)]
fn two_prod<T>(a: T, b: T) -> (T, T)
// (product, rounding_err)
where
    T: Copy + FMulAdd + Mul<Output = T> + Neg<Output = T>,
{
    let ab = a * b;
    (ab, a.fma(b, -ab))
}

/// `a + b` but also returns a rounding error for precise composition
/// with other operations.
#[inline(always)]
fn two_sum<T>(a: T, b: T) -> (T, T)
// (sum, rounding_err)
where
    T: Copy + Add<Output = T> + Sub<Output = T>,
{
    let sum = a + b;
    let delta = sum - a;
    (sum, (a - (sum - delta)) + (b - delta))
}

/// `a - b` but also returns a rounding error for precise composition
/// with other operations.
#[inline(always)]
fn two_diff<T>(a: T, b: T) -> (T, T)
// (diff, rounding_err)
where
    T: Copy + Add<Output = T> + Sub<Output = T>,
{
    let diff = a - b;
    let delta = diff - a;
    (diff, (a - (diff - delta)) - (b + delta))
}
