use std::fmt;

use crate::ray::Ray;
use crate::vec3::Vec3;

#[derive(Debug, Clone, PartialEq)]
pub struct AABB {
    bounds: [Vec3; 2],
}

impl fmt::Display for AABB {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({})-({})", self.bounds[0], self.bounds[1])
    }
}

fn min(x: f32, y: f32) -> f32 {
    if x < y {
        x
    } else {
        y
    }
}

fn max(x: f32, y: f32) -> f32 {
    if x > y {
        x
    } else {
        y
    }
}

impl AABB {
    pub fn new(min: Vec3, max: Vec3) -> AABB {
        AABB { bounds: [min, max] }
    }

    pub fn volume(&self) -> f32 {
        (self.min().x - self.max().x).abs()
            * (self.min().y - self.max().y).abs()
            * (self.min().z - self.max().z).abs()
    }

    pub fn longest_axis(&self) -> usize {
        let xd = (self.min().x - self.max().x).abs();
        let yd = (self.min().y - self.max().y).abs();
        let zd = (self.min().z - self.max().z).abs();

        if xd >= yd && xd >= zd {
            return 0;
        }
        if yd >= xd && yd >= zd {
            return 1;
        }
        2
    }

    pub fn mid_point(&self) -> Vec3 {
        self.bounds[0] + (self.bounds[1] - self.bounds[0]) / 2.
    }

    pub fn min(&self) -> Vec3 {
        self.bounds[0]
    }
    pub fn max(&self) -> Vec3 {
        self.bounds[1]
    }

    pub fn hit2(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
        let mut t_min = t_min;
        let mut t_max = t_max;
        for axis in 0..3 {
            let d_inv = 1.0 / r.direction[axis];
            let t0 = min(
                (self.min()[axis] - r.origin[axis]) * d_inv,
                (self.max()[axis] - r.origin[axis]) * d_inv,
            );
            let t1 = max(
                (self.min()[axis] - r.origin[axis]) * d_inv,
                (self.max()[axis] - r.origin[axis]) * d_inv,
            );
            t_min = max(t0, t_min);
            t_max = min(t1, t_max);
            if t_max <= t_min {
                return false;
            }
        }
        true
    }

    pub fn hit_precompute(&self, r: Ray, t0: f32, t1: f32) -> bool {
        // TODO(wathiede): this has bugs.
        let mut t_min = (self.bounds[r.sign[0]].x - r.origin.x) * r.inv_direction.x;
        let mut t_max = (self.bounds[1 - r.sign[0]].x - r.origin.x) * r.inv_direction.x;
        let t_y_min = (self.bounds[r.sign[0]].y - r.origin.y) * r.inv_direction.y;
        let t_y_max = (self.bounds[1 - r.sign[0]].y - r.origin.y) * r.inv_direction.y;

        if t_min > t_y_max || t_y_min > t_max {
            return false;
        }

        if t_y_min > t_min {
            t_min = t_y_min;
        }
        if t_y_max < t_max {
            t_max = t_y_max;
        }

        let t_z_min = (self.bounds[r.sign[2]].z - r.origin.z) * r.inv_direction.z;
        let t_z_max = (self.bounds[1 - r.sign[2]].z - r.origin.z) * r.inv_direction.z;
        if t_min > t_z_max || t_z_min > t_max {
            return false;
        }
        if t_z_min > t_min {
            t_min = t_z_min;
        }
        if t_z_max < t_max {
            t_max = t_z_max;
        }
        t_min < t1 && t_max > t0
    }

    pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> bool {
        let mut t_min = t_min;
        let mut t_max = t_max;
        for axis in 0..3 {
            let t0 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
                .min((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
            let t1 = ((self.min()[axis] - r.origin[axis]) * r.inv_direction[axis])
                .max((self.max()[axis] - r.origin[axis]) * r.inv_direction[axis]);
            t_min = t0.max(t_min);
            t_max = t1.min(t_max);
            if t_max <= t_min {
                return false;
            }
        }
        true
    }

    pub fn hit_fast(&self, r: Ray, _t_min: f32, _t_max: f32) -> bool {
        let b = self;
        let mut t1 = (b.min()[0] - r.origin[0]) * 1. / r.direction[0];
        let mut t2 = (b.max()[0] - r.origin[0]) * 1. / r.direction[0];

        let mut tmin = t1.min(t2);
        let mut tmax = t1.max(t2);

        for i in 1..3 {
            t1 = (b.min()[i] - r.origin[i]) * 1. / r.direction[i];
            t2 = (b.max()[i] - r.origin[i]) * 1. / r.direction[i];

            tmin = tmin.max(t1.min(t2));
            tmax = tmax.min(t1.max(t2));
        }

        tmax > tmin.max(0.0)
    }
}

pub fn surrounding_box(box0: &AABB, box1: &AABB) -> AABB {
    let min = Vec3::new(
        box0.min().x.min(box1.min().x),
        box0.min().y.min(box1.min().y),
        box0.min().z.min(box1.min().z),
    );
    let max = Vec3::new(
        box0.max().x.max(box1.max().x),
        box0.max().y.max(box1.max().y),
        box0.max().z.max(box1.max().z),
    );
    AABB::new(min, max)
}
