use std::ops::Deref;

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

pub struct Sphere {
    center: Point3,
    radius: f64,
    material: Box<dyn Material>,
}

impl Sphere {
    pub fn new<T: Material + 'static>(center: Point3, radius: f64, material: Box<T>) -> Self {
        Sphere {center, radius, material}
    }
}

impl Hittable for Sphere {
    fn hit(&self, r: &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,
            material: self.material.deref(),
        })
    }
}