use crate::aabb::surrounding_box;
use crate::aabb::AABB;
use crate::hitable::Hit;
use crate::hitable::HitRecord;
use crate::material::Material;
use crate::ray::Ray;
use crate::sphere::get_sphere_uv;
use crate::vec3::dot;
use crate::vec3::Vec3;

pub struct MovingSphere<M>
where
    M: Material,
{
    center0: Vec3,
    center1: Vec3,
    radius: f32,
    material: M,
    time0: f32,
    time1: f32,
}

impl<M> MovingSphere<M>
where
    M: Material,
{
    pub fn new(
        center0: Vec3,
        center1: Vec3,
        radius: f32,
        time0: f32,
        time1: f32,
        material: M,
    ) -> MovingSphere<M> {
        MovingSphere {
            center0,
            center1,
            radius,
            material,
            time0,
            time1,
        }
    }
    fn center(&self, time: f32) -> Vec3 {
        self.center0
            + ((time - self.time0) / (self.time1 - self.time0)) * (self.center1 - self.center0)
    }
}

impl<M> Hit for MovingSphere<M>
where
    M: Material,
{
    fn hit(&self, r: Ray, t0: f32, t1: f32) -> Option<HitRecord> {
        let oc = r.origin - self.center(r.time);
        let a = dot(r.direction, r.direction);
        let b = dot(oc, r.direction);
        let c = dot(oc, oc) - self.radius * self.radius;
        let discriminant = b * b - a * c;
        if discriminant > 0. {
            let temp = (-b - (b * b - a * c).sqrt()) / a;
            if temp < t1 && temp > t0 {
                let point = r.point_at_parameter(temp);
                let uv = get_sphere_uv((point - self.center(r.time)) / self.radius);
                return Some(HitRecord {
                    t: temp,
                    uv,
                    p: point,
                    normal: (point - self.center(r.time)) / self.radius,
                    material: &self.material,
                });
            }
            let temp = (-b + (b * b - a * c).sqrt()) / a;
            if temp < t1 && temp > t0 {
                let point = r.point_at_parameter(temp);
                let uv = get_sphere_uv((point - self.center(r.time)) / self.radius);
                return Some(HitRecord {
                    t: temp,
                    uv,
                    p: point,
                    normal: (point - self.center(r.time)) / self.radius,
                    material: &self.material,
                });
            }
        }
        None
    }

    fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option<AABB> {
        let t_min_bb = AABB::new(
            self.center0 - Vec3::new(self.radius, self.radius, self.radius),
            self.center0 + Vec3::new(self.radius, self.radius, self.radius),
        );

        let t_max_bb = AABB::new(
            self.center1 - Vec3::new(self.radius, self.radius, self.radius),
            self.center1 + Vec3::new(self.radius, self.radius, self.radius),
        );
        Some(surrounding_box(&t_min_bb, &t_max_bb))
    }
}
