use crate::prelude::*;

#[derive(Debug, Clone, Copy)]
pub struct AABB {
    min: Pos3,
    max: Pos3,
}

impl AABB {
    pub fn new_unchecked(min: Pos3, max: Pos3) -> Self {
        Self { min, max }
    }

    pub fn new(a: Pos3, b: Pos3) -> Self {
        Self {
            min: Pos3::new(
                Float::min(a.x, b.x),
                Float::min(a.y, b.y),
                Float::min(a.z, b.z),
            ),
            max: Pos3::new(
                Float::max(a.x, b.x),
                Float::max(a.y, b.y),
                Float::max(a.z, b.z),
            ),
        }
    }

    pub fn extend(self, other: Pos3) -> Self {
        Self {
            min: Pos3::new(
                Float::min(self.min.x, other.x),
                Float::min(self.min.y, other.y),
                Float::min(self.min.z, other.z),
            ),
            max: Pos3::new(
                Float::max(self.max.x, other.x),
                Float::max(self.max.y, other.y),
                Float::max(self.max.z, other.z),
            ),
        }
    }

    pub fn min(self) -> Pos3 {
        self.min
    }

    pub fn max(self) -> Pos3 {
        self.max
    }

    pub fn center(self) -> Pos3 {
        Pos3::new(
            (self.min.x + self.max.x) * 0.5,
            (self.min.y + self.max.y) * 0.5,
            (self.min.z + self.max.z) * 0.5,
        )
    }

    pub fn contains_point(self, pos: Pos3) -> bool {
        self.min.x <= pos.x
            && pos.x <= self.max.x
            && self.min.y <= pos.y
            && pos.y <= self.max.y
            && self.min.z <= pos.z
            && pos.z <= self.max.z
    }

    pub fn contains_aabb(self, aabb: Self) -> bool {
        self.contains_point(aabb.min) && self.contains_point(aabb.max)
    }

    pub fn size(self) -> Dir3 {
        Dir3::new(
            self.max.x - self.min.x,
            self.max.y - self.min.y,
            self.max.z - self.min.z,
        )
    }

    pub fn intersect_ray(self, ray: Ray, min: Float, max: Float) -> Option<Float> {
        let t_min_x = (self.min.x() - ray.start().x()) / ray.dir().x();
        let t_min_y = (self.min.y() - ray.start().y()) / ray.dir().y();
        let t_min_z = (self.min.z() - ray.start().z()) / ray.dir().z();

        let t_max_x = (self.max.x() - ray.start().x()) / ray.dir().x();
        let t_max_y = (self.max.y() - ray.start().y()) / ray.dir().y();
        let t_max_z = (self.max.z() - ray.start().z()) / ray.dir().z();

        let t_min = Float::max(
            Float::max(Float::min(t_min_x, t_max_x), Float::min(t_min_y, t_max_y)),
            Float::max(Float::min(t_min_z, t_max_z), min),
        );
        let t_max = Float::min(
            Float::min(Float::max(t_min_x, t_max_x), Float::max(t_min_y, t_max_y)),
            Float::min(Float::max(t_min_z, t_max_z), max),
        );

        if t_min <= t_max {
            Some(t_min)
        } else {
            None
        }
    }
}

#[cfg(test)]
mod test {

    use super::AABB;
    use crate::prelude::*;

    #[test]
    fn intersection() {
        let aabb = AABB::new(Pos3::new(-1.0, -1.0, -1.0), Pos3::new(1.0, 1.0, 1.0));

        let r = Ray::new(Pos3::new(-2.0, 0.0, 0.0), Dir3::new(1.0, 0.0, 0.0), 0.0);

        assert_eq!(aabb.intersect_ray(r, 0.0, 10.0), Some(1.0));

        let r = Ray::new(
            Pos3::new(-2.0, -2.0, 0.0),
            Dir3::new(1.0, 1.0, 0.0).normalize(),
            0.0,
        );

        assert_eq!(aabb.intersect_ray(r, 0.0, 10.0), Some(FloatConsts::SQRT_2));

        let r = Ray::new(Pos3::new(0.0, 0.0, 0.0), Dir3::new(1.0, 0.0, 0.0), 0.0);

        assert_eq!(aabb.intersect_ray(r, 0.0, 10.0), Some(0.0));
    }
}
