use crate::hittable::*;
use crate::material::Material;
use crate::vec3::*;

pub struct Sphere<'a> {
    center: Point3,
    radius: f64,
    mat_ptr: &'a dyn Material,
}

impl Sphere<'_> {
    pub fn new<T: Material>(center: Point3, radius: f64, mat_ptr: &'static T) -> Self {
        Sphere {center, radius, mat_ptr}
    }
}

impl Hittable for Sphere<'_> {
    fn hit(&self, r: &crate::ray::Ray, t_min: f64, t_max: f64) -> Option<HitRecord<'_>> {
        let oc = r.origin() - self.center;
        let a = r.direction().length_squared();
        let half_b = oc.dot(&r.direction());
        let c = oc.length_squared() - self.radius * self.radius;

        let discriminant = half_b * half_b - a * c;
        if discriminant < 0.0 {
            return None;
        }
        let sqrtd = discriminant.sqrt();

        // Find the nearest root that lies in the acceptable range
        let mut root = (-half_b - sqrtd) / a;
        if root < t_min || t_max < root {
            root = (-half_b + sqrtd) / a;
            if root < t_min || t_max < root {
                return None;
            }
        }

        let p = r.at(root);
        let outward_normal = (p - self.center) / self.radius;
        let front_face = r.direction().dot(&outward_normal) < 0.0;

        let outward_normal = if front_face {
            outward_normal
        } else {
            outward_normal * -1.0
        };

        Some(HitRecord {
            t: root,
            p: p,
            normal: outward_normal,
            front_face,
            mat_ptr: self.mat_ptr,
        })
    }
}