pub mod bounds;
pub mod cone;
pub mod primitives;
pub mod ray;
pub mod traits;

pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i};
pub use self::cone::DirectionCone;
pub use self::primitives::{
    Frame, MulAdd, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi,
    Point3i, Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi,
    Vector3i,
};
pub use self::ray::{Ray, RayDifferential};
pub use self::traits::{Lerp, SqrtExt, Tuple, VectorLike};

use crate::core::pbrt::{Float, PI};
use crate::utils::math::{clamp, square};
use num_traits::Float as NumFloat;

#[inline]
pub fn min<T: PartialOrd>(a: T, b: T) -> T {
    if a < b { a } else { b }
}

#[inline]
pub fn max<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

#[inline]
pub fn cos_theta(w: Vector3f) -> Float {
    w.z()
}
#[inline]
pub fn abs_cos_theta(w: Vector3f) -> Float {
    w.z().abs()
}
#[inline]
pub fn cos2_theta(w: Vector3f) -> Float {
    square(w.z())
}
#[inline]
pub fn sin2_theta(w: Vector3f) -> Float {
    0_f32.max(1. - cos2_theta(w))
}
#[inline]
pub fn sin_theta(w: Vector3f) -> Float {
    sin2_theta(w).sqrt()
}
#[inline]
pub fn tan_theta(w: Vector3f) -> Float {
    sin_theta(w) / cos_theta(w)
}
#[inline]
pub fn tan2_theta(w: Vector3f) -> Float {
    sin2_theta(w) / cos2_theta(w)
}
#[inline]
pub fn cos_phi(w: Vector3f) -> Float {
    let sin_theta = sin_theta(w);
    if sin_theta == 0. {
        1.
    } else {
        clamp(w.x() / sin_theta, -1., 1.)
    }
}

#[inline]
pub fn sin_phi(w: Vector3f) -> Float {
    let sin_theta = sin_theta(w);
    if sin_theta == 0. {
        0.
    } else {
        clamp(w.y() / sin_theta, -1., 1.)
    }
}

pub fn same_hemisphere(w: Vector3f, wp: Vector3f) -> bool {
    w.z() * wp.z() > 0.
}

pub fn spherical_direction(sin_theta: Float, cos_theta: Float, phi: Float) -> Vector3f {
    Vector3f::new(sin_theta * phi.cos(), sin_theta * phi.sin(), cos_theta)
}

pub fn spherical_triangle_area(a: Vector3f, b: Vector3f, c: Vector3f) -> Float {
    (2.0 * (a.dot(b.cross(c))).atan2(1.0 + a.dot(b) + a.dot(c) + b.dot(c))).abs()
}

pub fn spherical_quad_area(a: Vector3f, b: Vector3f, c: Vector3f, d: Vector3f) -> Float {
    let mut axb = a.cross(b);
    let mut bxc = b.cross(c);
    let mut cxd = c.cross(d);
    let mut dxa = d.cross(a);
    if axb.norm_squared() == 0.
        || bxc.norm_squared() == 0.
        || cxd.norm_squared() == 0.
        || dxa.norm_squared() == 0.
    {
        return 0.;
    }
    axb = axb.normalize();
    bxc = bxc.normalize();
    cxd = cxd.normalize();
    dxa = dxa.normalize();

    let alpha = dxa.angle_between(-axb);
    let beta = axb.angle_between(-bxc);
    let gamma = bxc.angle_between(-cxd);
    let delta = cxd.angle_between(-dxa);

    (alpha + beta + gamma + delta - 2. * PI).abs()
}

pub fn spherical_theta(v: Vector3f) -> Float {
    clamp(v.z(), -1.0, 1.0).acos()
}

pub fn spherical_phi(v: Vector3f) -> Float {
    let p = v.y().atan2(v.x());
    if p < 0.0 { p + 2.0 * PI } else { p }
}
