use crate::{
	hittable::{sphere::Sphere, HittableList, Object},
	material::{dielectric::Dielectric, lambertian::Lambertian, metal::Metal, Materials},
	raytracer::Raytracer,
	vec3::{Color, Point3, Vec3},
};

pub struct SphereBuilder {
	child: RaytracerBuilder,
	center: Point3,
	radius: f64,
}

impl SphereBuilder {
	pub fn dielectric(self, refraction_index: f64) -> RaytracerBuilder {
		self.with_material(Materials::Dielectric(Dielectric::new(refraction_index)))
	}

	pub fn lambertian(self, albedo_r: f64, albedo_g: f64, albedo_b: f64) -> RaytracerBuilder {
		self.with_material(Materials::Lambertian(Lambertian::new(Color::new(
			albedo_r, albedo_g, albedo_b,
		))))
	}

	pub fn metal(self, albedo_r: f64, albedo_g: f64, albedo_b: f64, fuzz: f64) -> RaytracerBuilder {
		self.with_material(Materials::Metal(Metal::new(
			Color::new(albedo_r, albedo_g, albedo_b),
			fuzz,
		)))
	}

	pub fn with_material(self, material: Materials) -> RaytracerBuilder {
		let mut builder = self.child;
		builder.world.add(Object::Sphere(Sphere::new(self.center, self.radius, material)));
		builder
	}
}

#[derive(Debug, Clone)]
pub struct RaytracerBuilder {
	pub world: HittableList,

	pub image_height: usize,
	pub image_width: usize,
	pub samples_per_pixel: usize,
	pub max_depth: usize,

	pub defocus_angle: f64,
	pub focus_dist: f64,

	pub vertical_fov: f64,
	pub look_from: Point3,
	pub look_at: Point3,
	pub vup: Vec3,
}

impl Default for RaytracerBuilder {
	fn default() -> Self {
		Self {
			world: HittableList::new(),
			image_height: 1080,
			image_width: 1920,
			samples_per_pixel: 100,
			max_depth: 50,
			vertical_fov: 90.0,
			look_from: Point3::new(0.0, 0.0, 0.0),
			look_at: Point3::new(0.0, 0.0, -1.0),
			vup: Point3::new(0.0, 1.0, 0.0),
			defocus_angle: 0.0,
			focus_dist: 10.0,
		}
	}
}

macro_rules! fn_ref {
	($(pub fn $fident:ident(&mut $selfident:ident, $($aident:ident: $aty:ty),*) $b:block)*) => {
		paste::paste! {
			$(
				pub fn $fident(mut $selfident, $($aident: $aty),*) -> Self {
					$b
					$selfident
				}

				pub fn [<$fident _ref>](&mut $selfident, $($aident: $aty),*) -> &mut Self {
					$b
					$selfident
				}
			)*
		}
	};
}

impl RaytracerBuilder {
	fn_ref! {
		pub fn image_size(&mut self, width: usize, height: usize) {
			self.image_width = width;
			self.image_height = height;
		}

		pub fn look_from(&mut self, x: f64, y: f64, z: f64) {
			self.look_from = Point3::new(x, y, z);
		}
	
		pub fn look_at(&mut self, x: f64, y: f64, z: f64) {
			self.look_at = Point3::new(x, y, z);
		}
	
		pub fn vup(&mut self, x: f64, y: f64, z: f64) {
			self.vup = Point3::new(x, y, z);
		}
	
		pub fn max_depth(&mut self, depth: usize) {
			self.max_depth = depth;
		}
	
		pub fn samples_per_pixel(&mut self, samples_per_pixel: usize) {
			self.samples_per_pixel = samples_per_pixel;
		}
	
		pub fn vertical_fov(&mut self, vertical_fov: f64) {
			self.vertical_fov = vertical_fov;
		}
	
		pub fn defocus_angle(&mut self, defocus_angle: f64) {
			self.defocus_angle = defocus_angle;
		}
	
		pub fn focus_dist(&mut self, focus_dist: f64) {
			self.focus_dist = focus_dist;
		}
	}

	pub fn with_sphere(self, x: f64, y: f64, z: f64, radius: f64) -> SphereBuilder {
		SphereBuilder { child: self, center: Point3::new(x, y, z), radius }
	}

	pub fn finish(self) -> Raytracer {
		self.into()
	}
}
