use super::{Float, NumFloat};
use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
use crate::core::geometry::traits::{SqrtExt, VectorLike};
use crate::core::geometry::{max, min};
use crate::utils::gpu_array_from_fn;
use crate::utils::interval::Interval;
use crate::utils::math::lerp;
use core::mem;
use core::ops::{Add, Div, DivAssign, Mul, Sub};
use num_traits::{Bounded, Num};

// AABB BOUNDING BOXES

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Bounds<T, const N: usize> {
    pub p_min: Point<T, N>,
    pub p_max: Point<T, N>,
}

impl<'a, T, const N: usize> IntoIterator for &'a Bounds<T, N> {
    type Item = &'a Point<T, N>;
    type IntoIter = core::array::IntoIter<&'a Point<T, N>, 2>;

    fn into_iter(self) -> Self::IntoIter {
        [&self.p_min, &self.p_max].into_iter()
    }
}

impl<T, const N: usize> Bounds<T, N>
where
    T: Num + PartialOrd + Copy,
{
    pub fn from_point(p: Point<T, N>) -> Self {
        Self { p_min: p, p_max: p }
    }

    pub fn from_points(p1: Point<T, N>, p2: Point<T, N>) -> Self {
        let mut p_min_arr = [T::zero(); N];
        let mut p_max_arr = [T::zero(); N];

        for i in 0..N {
            if p1[i] < p2[i] {
                p_min_arr[i] = p1[i];
                p_max_arr[i] = p2[i];
            } else {
                p_min_arr[i] = p2[i];
                p_max_arr[i] = p1[i];
            }
        }

        Self {
            p_min: Point(p_min_arr),
            p_max: Point(p_max_arr),
        }
    }

    pub fn union_point(self, p: Point<T, N>) -> Self {
        let mut p_min = self.p_min;
        let mut p_max = self.p_max;
        for i in 0..N {
            p_min[i] = min(p_min[i], p[i]);
            p_max[i] = max(p_max[i], p[i]);
        }
        Self { p_min, p_max }
    }

    pub fn union(self, b2: Self) -> Self {
        let mut p_min = self.p_min;
        let mut p_max = self.p_max;
        for i in 0..N {
            p_min[i] = min(p_min[i], b2.p_min[i]);
            p_max[i] = max(p_max[i], b2.p_max[i]);
        }
        Self { p_min, p_max }
    }

    pub fn diagonal(&self) -> Vector<T, N> {
        self.p_max - self.p_min
    }

    pub fn centroid(&self) -> Point<T, N> {
        let two = T::one() + T::one();
        self.p_min + (self.diagonal() / two)
    }

    pub fn volume(&self) -> T {
        let d = self.diagonal();
        d.0.iter().fold(T::one(), |acc, &val| acc * val)
    }

    pub fn expand(&self, delta: impl Into<Vector<T, N>>) -> Self {
        let v = delta.into();
        Self {
            p_min: self.p_min - v,
            p_max: self.p_max + v,
        }
    }

    pub fn lerp(&self, t: Point<T, N>) -> Point<T, N> {
        let mut results_arr = [T::zero(); N];
        for i in 0..N {
            results_arr[i] = lerp(t[i], self.p_min[i], self.p_max[i])
        }

        Point(results_arr)
    }

    pub fn max_dimension(&self) -> usize
    where
        Point<T, N>: Sub<Output = Vector<T, N>>,
    {
        let d = self.diagonal();
        let mut max_dim = 0;
        let mut max_span = d[0];

        for i in 1..N {
            if d[i] > max_span {
                max_span = d[i];
                max_dim = i;
            }
        }
        max_dim
    }

    pub fn offset(&self, p: &Point<T, N>) -> Vector<T, N>
    where
        Point<T, N>: Sub<Output = Vector<T, N>>,
        Vector<T, N>: DivAssign<T>,
    {
        let mut o = *p - self.p_min;
        let d = self.diagonal();
        for i in 0..N {
            if d[i] > T::zero() {
                o[i] = o[i] / d[i];
            }
        }
        o
    }

    pub fn corner(&self, corner_index: usize) -> Point<T, N> {
        Point(gpu_array_from_fn(|i| {
            if (corner_index >> i) & 1 == 1 {
                self.p_max[i]
            } else {
                self.p_min[i]
            }
        }))
    }

    pub fn overlaps(&self, rhs: &Self) -> bool {
        for i in 0..N {
            if self.p_max[i] < rhs.p_min[i] || self.p_min[i] > rhs.p_max[i] {
                return false;
            }
        }
        true
    }

    pub fn contains(&self, p: Point<T, N>) -> bool {
        (0..N).all(|i| p[i] >= self.p_min[i] && p[i] <= self.p_max[i])
    }

    pub fn contains_exclusive(&self, p: Point<T, N>) -> bool {
        (0..N).all(|i| p[i] >= self.p_min[i] && p[i] < self.p_max[i])
    }

    pub fn is_empty(&self) -> bool {
        (0..N).any(|i| self.p_min[i] >= self.p_max[i])
    }

    pub fn is_degenerate(&self) -> bool {
        (0..N).any(|i| self.p_min[i] > self.p_max[i])
    }
}

impl<T, const N: usize> Default for Bounds<T, N>
where
    T: Bounded + Copy,
{
    fn default() -> Self {
        Self {
            p_min: Point([T::max_value(); N]),
            p_max: Point([T::min_value(); N]),
        }
    }
}

pub type Bounds2<T> = Bounds<T, 2>;
pub type Bounds2f = Bounds2<Float>;
pub type Bounds2i = Bounds2<i32>;
pub type Bounds2fi = Bounds2<Interval>;
pub type Bounds3<T> = Bounds<T, 3>;
pub type Bounds3i = Bounds3<i32>;
pub type Bounds3f = Bounds3<Float>;
pub type Bounds3fi = Bounds3<Interval>;

impl<T> Bounds3<T>
where
    T: Num + PartialOrd + Copy + Default,
{
    pub fn surface_area(&self) -> T {
        let d = self.diagonal();
        let two = T::one() + T::one();
        two * (d.x() * d.y() + d.x() * d.z() + d.y() * d.z())
    }
}

impl<T> Bounds3<T>
where
    T: NumFloat + PartialOrd + Copy + Default + SqrtExt,
{
    pub fn bounding_sphere(&self) -> (Point3<T>, T) {
        let two = T::one() + T::one();
        let center = self.p_min + self.diagonal() / two;
        let radius = if self.contains(center) {
            center.distance(self.p_max)
        } else {
            T::zero()
        };
        (center, radius)
    }

    pub fn insersect(&self, o: Point3<T>, d: Vector3<T>, t_max: T) -> Option<(T, T)> {
        let mut t0 = T::zero();
        let mut t1 = t_max;

        for i in 0..3 {
            let inv_ray_dir = T::one() / d[i];
            let mut t_near = (self.p_min[i] - o[i]) * inv_ray_dir;
            let mut t_far = (self.p_max[i] - o[i]) * inv_ray_dir;
            if t_near > t_far {
                mem::swap(&mut t_near, &mut t_far);
            }
            t0 = if t_near > t0 { t_near } else { t0 };
            t1 = if t_far < t1 { t_far } else { t1 };
            if t0 > t1 {
                return None;
            }
        }
        Some((t0, t1))
    }
}

impl<T> Bounds2<T>
where
    T: Num + Copy + Default,
{
    pub fn area(&self) -> T {
        let d: Vector2<T> = self.p_max - self.p_min;
        d.x() * d.y()
    }
}

impl Bounds2f {
    pub fn unit() -> Self {
        Self::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0))
    }
}

impl Bounds3f {
    #[inline(always)]
    pub fn intersect_p(
        &self,
        o: Point3f,
        ray_t_max: Float,
        inv_dir: Vector3f,
        dir_is_neg: &[usize; 3],
    ) -> Option<(Float, Float)> {
        let bounds = [&self.p_min, &self.p_max];

        // Check X
        let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x();
        let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x();

        // Check Y
        let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
        let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();

        if t_min > ty_max || ty_min > t_max {
            return None;
        }
        if ty_min > t_min {
            t_min = ty_min;
        }
        if ty_max < t_max {
            t_max = ty_max;
        }

        // Check Z
        let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
        let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();

        if t_min > tz_max || tz_min > t_max {
            return None;
        }
        if tz_min > t_min {
            t_min = tz_min;
        }
        if tz_max < t_max {
            t_max = tz_max;
        }

        if (t_min < ray_t_max) && (t_max > 0.0) {
            Some((t_min, t_max))
        } else {
            None
        }
    }

    pub fn intersect_with_inverse(&self, o: Point3f, d: Vector3f, ray_t_max: Float) -> bool {
        let inv_dir = Vector3::new(1.0 / d.x(), 1.0 / d.y(), 1.0 / d.z());
        let dir_is_neg: [usize; 3] = [
            (d.x() < 0.0) as usize,
            (d.y() < 0.0) as usize,
            (d.z() < 0.0) as usize,
        ];

        let bounds = [&self.p_min, &self.p_max];

        // Check for ray intersection against x and y slabs
        let mut t_min = (bounds[dir_is_neg[0]].x() - o.x()) * inv_dir.x();
        let mut t_max = (bounds[1 - dir_is_neg[0]].x() - o.x()) * inv_dir.x();
        let ty_min = (bounds[dir_is_neg[1]].y() - o.y()) * inv_dir.y();
        let ty_max = (bounds[1 - dir_is_neg[1]].y() - o.y()) * inv_dir.y();

        if t_min > ty_max || ty_min > t_max {
            return false;
        }
        if ty_min > t_min {
            t_min = ty_min;
        }
        if ty_max < t_max {
            t_max = ty_max;
        }

        let tz_min = (bounds[dir_is_neg[2]].z() - o.z()) * inv_dir.z();
        let tz_max = (bounds[1 - dir_is_neg[2]].z() - o.z()) * inv_dir.z();

        if t_min > tz_max || tz_min > t_max {
            return false;
        }
        if tz_min > t_min {
            t_min = tz_min;
        }
        if tz_max < t_max {
            t_max = tz_max;
        }

        (t_min < ray_t_max) && (t_max > 0.0)
    }
}
