use std::f32::consts::PI;
use std::f32::MAX;
use std::f32::MIN;

use crate::aabb::AABB;
use crate::hitable::Hit;
use crate::hitable::HitRecord;
use crate::ray::Ray;
use crate::vec3::Vec3;

pub struct RotateY<H>
where
    H: Hit,
{
    hitable: H,
    sin_theta: f32,
    cos_theta: f32,
    bbox: Option<AABB>,
}

impl<H> RotateY<H>
where
    H: Hit,
{
    pub fn new(hitable: H, angle: f32) -> RotateY<H> {
        let radians = PI / 180. * angle;
        let sin_theta = radians.sin();
        let cos_theta = radians.cos();
        let mut min = vec![MAX, MAX, MAX];
        let mut max = vec![MIN, MIN, MIN];
        let bbox = hitable.bounding_box(0., 1.).unwrap();
        for i in 0..2 {
            for j in 0..2 {
                for k in 0..2 {
                    let x = i as f32 * bbox.max().x + (1 - i) as f32 * bbox.min().x;
                    let y = j as f32 * bbox.max().y + (1 - j) as f32 * bbox.min().y;
                    let z = k as f32 * bbox.max().z + (1 - k) as f32 * bbox.min().z;
                    let new_x = cos_theta * x + sin_theta * z;
                    let new_z = -sin_theta * x + cos_theta * z;
                    let tester = Vec3::new(new_x, y, new_z);
                    for c in 0..3 {
                        if tester[c] > max[c] {
                            max[c] = tester[c];
                        }
                        if tester[c] < min[c] {
                            min[c] = tester[c];
                        }
                    }
                }
            }
        }
        RotateY {
            hitable,
            sin_theta,
            cos_theta,
            bbox: Some(AABB::new(
                Vec3::new(min[0], min[1], min[2]),
                Vec3::new(max[0], max[1], max[2]),
            )),
        }
    }
}

impl<H> Hit for RotateY<H>
where
    H: Hit,
{
    fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
        let origin = Vec3::new(
            self.cos_theta * r.origin[0] - self.sin_theta * r.origin[2],
            r.origin.y,
            self.sin_theta * r.origin[0] + self.cos_theta * r.origin[2],
        );
        let direction = Vec3::new(
            self.cos_theta * r.direction[0] - self.sin_theta * r.direction[2],
            r.direction.y,
            self.sin_theta * r.direction[0] + self.cos_theta * r.direction[2],
        );
        let rotated_r = Ray::new(origin, direction, r.time);
        if let Some(rec) = self.hitable.hit(rotated_r, t_min, t_max) {
            let p = Vec3::new(
                self.cos_theta * rec.p[0] + self.sin_theta * rec.p[2],
                rec.p[1],
                -self.sin_theta * rec.p[0] + self.cos_theta * rec.p[2],
            );
            let normal = Vec3::new(
                self.cos_theta * rec.normal[0] + self.sin_theta * rec.normal[2],
                rec.normal[1],
                -self.sin_theta * rec.normal[0] + self.cos_theta * rec.normal[2],
            );
            return Some(HitRecord { p, normal, ..rec });
        }
        None
    }

    fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
        self.bbox.clone()
    }
}
