use core::ops::{Index, IndexMut};

use crate::raytracer::{random_f64, random_f64_range};

#[derive(Debug, Clone, Copy, Default)]
pub struct Vec3([f64; 3]);

pub type Point3 = Vec3;
pub type Color = Vec3;

impl Vec3 {
	pub const fn new(x: f64, y: f64, z: f64) -> Self {
		Self([x, y, z])
	}

	pub const fn x(&self) -> f64 {
		self.0[0]
	}

	pub const fn y(&self) -> f64 {
		self.0[1]
	}

	pub const fn z(&self) -> f64 {
		self.0[2]
	}

	pub const fn r(&self) -> f64 {
		self.x()
	}

	pub const fn g(&self) -> f64 {
		self.y()
	}

	pub const fn b(&self) -> f64 {
		self.z()
	}

	pub fn len(&self) -> f64 {
		self.len_squared().sqrt()
	}

	pub fn len_squared(&self) -> f64 {
		self.dot(self)
	}

	pub fn dot(&self, rhs: &Self) -> f64 {
		(self.0[0] * rhs[0]) + (self.0[1] * rhs[1]) + (self.0[2] * rhs[2])
	}

	pub fn cross(&self, rhs: &Self) -> Self {
		Self::new(
			self[1] * rhs[2] - self[2] * rhs[1],
			self[2] * rhs[0] - self[0] * rhs[2],
			self[0] * rhs[1] - self[1] * rhs[0],
		)
	}

	pub fn unit_vector(&self) -> Self {
		*self / self.len()
	}

	pub fn random() -> Self {
		Self::new(random_f64(), random_f64(), random_f64())
	}

	pub fn random_range(min: f64, max: f64) -> Self {
		Self::new(
			random_f64_range(min, max),
			random_f64_range(min, max),
			random_f64_range(min, max),
		)
	}

	pub fn random_in_unit_sphere() -> Self {
		loop {
			let p = Self::random_range(-1.0, 1.0);
			if p.len_squared() < 1.0 {
				return p;
			}
		}
	}

	pub fn random_in_unit_disk() -> Self {
		loop {
			let p = Self::new(random_f64_range(-1.0, 1.0), random_f64_range(-1.0, 1.0), 0.0);
			if p.len_squared() < 1.0 {
				return p;
			}
		}
	}

	pub fn random_unit_vector() -> Self {
		Self::random_in_unit_sphere().unit_vector()
	}

	pub fn random_on_hemisphere(&self) -> Self {
		let on_unit_sphere = Self::random_unit_vector();

		if on_unit_sphere.dot(self) > 0.0 {
			on_unit_sphere
		} else {
			-on_unit_sphere
		}
	}

	pub fn near_zero(&self) -> bool {
		const S: f64 = 1e-8;

		self[0].abs() < S && self[1].abs() < S && self[2].abs() < S
	}

	pub fn reflect(self, n: Self) -> Self {
		self - 2.0 * self.dot(&n) * n
	}

	pub fn refract(self, n: Self, etai_over_etat: f64) -> Self {
		let cos_theta = (-self).dot(&n).min(1.0);
		let r_out_perpendicular = etai_over_etat * (self + cos_theta * n);
		let r_out_parallel = -((1.0 - r_out_perpendicular.len_squared()).abs().sqrt()) * n;
		r_out_perpendicular + r_out_parallel
	}
}

impl Index<usize> for Vec3 {
	type Output = f64;

	fn index(&self, index: usize) -> &Self::Output {
		&self.0[index]
	}
}

impl IndexMut<usize> for Vec3 {
	fn index_mut(&mut self, index: usize) -> &mut Self::Output {
		&mut self.0[index]
	}
}

impl core::ops::Add for Vec3 {
	type Output = Self;

	fn add(self, rhs: Self) -> Self::Output {
		Self::new(self[0] + rhs[0], self[1] + rhs[1], self[2] + rhs[2])
	}
}

impl core::ops::AddAssign<Vec3> for &mut Vec3 {
	fn add_assign(&mut self, rhs: Vec3) {
		self[0] += rhs[0];
		self[1] += rhs[1];
		self[2] += rhs[2];
	}
}

impl core::ops::AddAssign for Vec3 {
	fn add_assign(&mut self, rhs: Self) {
		self[0] += rhs[0];
		self[1] += rhs[1];
		self[2] += rhs[2];
	}
}

impl core::ops::Sub for Vec3 {
	type Output = Self;

	fn sub(self, rhs: Self) -> Self::Output {
		Self::new(self[0] - rhs[0], self[1] - rhs[1], self[2] - rhs[2])
	}
}

impl core::ops::Neg for Vec3 {
	type Output = Self;

	fn neg(self) -> Self::Output {
		Self::new(-self[0], -self[1], -self[2])
	}
}

impl core::ops::Mul<f64> for Vec3 {
	type Output = Self;

	fn mul(self, rhs: f64) -> Self::Output {
		Vec3::new(self[0] * rhs, self[1] * rhs, self[2] * rhs)
	}
}

impl core::ops::MulAssign<f64> for &mut Vec3 {
	fn mul_assign(&mut self, rhs: f64) {
		self[0] *= rhs;
		self[1] *= rhs;
		self[2] *= rhs;
	}
}

impl core::ops::Mul<Vec3> for f64 {
	type Output = Vec3;

	fn mul(self, rhs: Vec3) -> Self::Output {
		rhs * self
	}
}

impl core::ops::Mul for Vec3 {
	type Output = Self;

	fn mul(self, rhs: Self) -> Self::Output {
		Self::new(self[0] * rhs[0], self[1] * rhs[1], self[2] * rhs[2])
	}
}

impl core::ops::MulAssign<Vec3> for &mut Vec3 {
	fn mul_assign(&mut self, rhs: Vec3) {
		self[0] *= rhs[0];
		self[1] *= rhs[1];
		self[2] *= rhs[2];
	}
}

impl core::ops::MulAssign for Vec3 {
	fn mul_assign(&mut self, rhs: Self) {
		self[0] *= rhs[0];
		self[1] *= rhs[1];
		self[2] *= rhs[2];
	}
}

impl core::ops::Div<f64> for Vec3 {
	type Output = Self;

	fn div(self, rhs: f64) -> Self::Output {
		(1. / rhs) * self
	}
}

impl core::ops::Div for Vec3 {
	type Output = Self;

	fn div(self, rhs: Self) -> Self::Output {
		Self::new(self[0] / rhs[0], self[1] / rhs[1], self[2] / rhs[2])
	}
}

impl core::ops::DivAssign for Vec3 {
	fn div_assign(&mut self, rhs: Self) {
		self[0] /= rhs[0];
		self[1] /= rhs[1];
		self[2] /= rhs[2];
	}
}
