pub mod sphere;

use crate::{
	interval::Interval,
	material::Materials,
	ray::Ray,
	vec3::{Point3, Vec3},
};

#[derive(Debug, Clone)]
pub struct HitRecord {
	pub p: Point3,
	pub normal: Vec3,
	pub t: f64,
	pub front_face: bool,
	pub mat: Materials,
}

impl HitRecord {
	pub fn new(t: f64, p: Point3, outward_normal: Vec3, mat: Materials, ray: &Ray) -> Self {
		let front_face = ray.direction().dot(&outward_normal) < 0.0;
		Self {
			p,
			normal: match front_face {
				true => outward_normal,
				false => -outward_normal,
			},
			t,
			front_face,
			mat,
		}
	}
}

pub trait Hittable {
	fn hit(&self, ray: &Ray, ray_t: &Interval) -> Option<HitRecord>;
}

#[derive(Debug, Clone, Copy)]
pub enum Object {
	Sphere(sphere::Sphere),
}

impl Hittable for Object {
	fn hit(&self, ray: &Ray, ray_t: &Interval) -> Option<HitRecord> {
		match self {
			Object::Sphere(sphere) => sphere.hit(ray, ray_t),
		}
	}
}

#[derive(Debug, Clone)]
pub struct HittableList {
	objects: Vec<Object>,
}

impl HittableList {
	pub fn new() -> Self {
		Self { objects: Vec::with_capacity(128) }
	}

	pub fn add(&mut self, object: Object) {
		self.objects.push(object);
	}
}

impl Hittable for HittableList {
	fn hit(&self, ray: &Ray, ray_t: &Interval) -> Option<HitRecord> {
		let mut out = None;
		let mut interval = Interval::new(ray_t.min, ray_t.max);

		for object in &self.objects {
			if let Some(hit_record) = object.hit(ray, &interval) {
				interval.max = hit_record.t;
				out = Some(hit_record);
			}
		}

		out
	}
}
