words words

words words

TODO words

use crate::space::point::Point2d;

pub struct Space2d<Star> {
	data: Vec<Vec<Star>>,

	x_len: usize,
	y_len: usize,

	x_offset: isize,
	y_offset: isize
}

impl<Star: std::clone::Clone> Space2d<Star> {
	#[allow(dead_code)]

	pub fn new_grid_sized (default_object: Star, num_rows: usize, num_cols: usize) -> Space2d<Star> {
		return Space2d {
			data: vec![vec![default_object; num_cols]; num_rows],
			x_len: num_cols,
			y_len: num_rows,

			x_offset: 0,
			y_offset: 0
		};
	}
}

impl Space2d<String> {
	pub fn new_from_string (input: String) -> Space2d<String> {
		let data = input.lines()
			.map(|row| row.chars().map(|x| x.to_string()).collect::<Vec<_>>())
			.collect::<Vec<_>>();

		return Space2d {
			x_len: data[0].len(),
			y_len: data.len(),

			x_offset: 0,
			y_offset: 0,

			data: data
		};
	}

	pub fn new_from_file (file_path: &str) -> Space2d<String> {
		let file_contents = std::fs::read_to_string(file_path)
			.expect(&format!("Could not find \"{}\". Did you make a typo?", file_path));

		return Space2d::new_from_string(file_contents);
	}
}

impl<Star> Space2d<Star> {
	#[allow(dead_code)]

	// get 1d vectors from different things
	// get row, get col, get height, get xxx

	// insert vectors that nudge like things to the side
	// insert row, col, height, etc

	// remove row, col, etc, etc

	// rotate and reflect

	pub fn indices (&self) -> impl Iterator<Item = Point2d> + '_ {
		(self.y_offset..(self.y_offset + self.y_len as isize)).flat_map(move |y|
			(self.x_offset..(self.x_offset + self.x_len as isize)).map(move |x|
				Point2d::new(x, y)
			)
		)
	}

	pub fn point_is_in_bounds (&self, point: &Point2d) -> bool {
		return true
			&& self.x_offset <= point.x() && point.x() < self.x_offset + self.x_len as isize
			&& self.y_offset <= point.y() && point.y() < self.y_offset + self.y_len as isize;
	}

	pub fn get (&self, point: &Point2d) -> Option<&Star> {
		if self.point_is_in_bounds(point) {
			Some(&self.data[(point.y() + self.y_offset) as usize][(point.x() + self.x_offset) as usize])
		} else {
			None
		}
	}

	pub fn set (&mut self, point: &Point2d, value: Star) {
		if self.point_is_in_bounds(point) {
			self.data[(point.y() + self.y_offset) as usize][(point.x() + self.x_offset) as usize] = value;
		} else {
			// Do nothing
		}
	}

	pub fn num_cols (&self) -> usize { self.x_len }
	pub fn num_rows (&self) -> usize { self.y_len }
	// pub fn x_offset (&self) -> usize { self.x_offset }
	// pub fn y_offset (&self) -> usize { self.y_offset }
}

impl<Star: std::cmp::PartialEq> Space2d<Star> {
	#[allow(dead_code)]

	pub fn find_all <'a> (&'a self, needle: &'a Star) -> impl Iterator<Item = Point2d> + 'a {
		return self.indices().filter(move |x| self.get(x).unwrap() == needle);
	}
}

impl<Star: std::string::ToString> Space2d<Star> {
	#[allow(dead_code)]

	pub fn print_compact (&self) {
		for row in self.y_offset..(self.y_offset + self.y_len as isize) {
			for col in self.x_offset..(self.x_offset + self.x_len as isize) {
				print!("{}", self.get(&Point2d::new(col, row)).unwrap().to_string().chars().nth(0).unwrap());
			}
			println!();
		}
	}
}
const DIMENSIONS_2D_SIZE: usize = 2;

const DIMENSIONS_2D_ORIGIN: Point2d = Point2d { coordinates: [ 0, 0] };
const DIMENSIONS_2D_DIRECTION_EAST: Point2d = Point2d { coordinates: [ 1, 0] };
const DIMENSIONS_2D_DIRECTION_WEST: Point2d = Point2d { coordinates: [-1, 0] };
const DIMENSIONS_2D_DIRECTION_NORTH: Point2d = Point2d { coordinates: [0,  1] };
const DIMENSIONS_2D_DIRECTION_SOUTH: Point2d = Point2d { coordinates: [0, -1] };

#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct Point2d {
	coordinates: [isize; DIMENSIONS_2D_SIZE]
}

impl Point2d {
	const fn dimensions () -> usize { DIMENSIONS_2D_SIZE }
	pub fn dimension_value (&self, dimension: usize) -> isize { self.coordinates[dimension] }

	pub fn x (&self) -> isize { self.coordinates[0] }
	pub fn y (&self) -> isize { self.coordinates[1] }
}

// Took great inspiration from ndarray's macro
// https://docs.rs/ndarray/latest/src/ndarray/impl_ops.rs.html#53
macro_rules! impl_elementwise_operation {
	($type1:ty, $type2:ty, $std_ops_name:ident, $fn_name:ident, $operator:tt) => {
		
		// Both owned
		impl std::ops::$std_ops_name<$type2> for $type1 {
			type Output = $type1;

			fn $fn_name (self, other: $type2) -> $type1 {
				let mut coordinates = [0; Self::Output::dimensions()];
				for i in 0..Self::Output::dimensions() {
					coordinates[i] = self.dimension_value(i) $operator other.dimension_value(i);
				}
				Self::Output { coordinates }
			}
		}

		// lhs is reference
		impl<'a> std::ops::$std_ops_name<$type2> for &'a $type1 {
			type Output = $type1;

			fn $fn_name (self, other: $type2) -> $type1 {
				let mut coordinates = [0; Self::Output::dimensions()];
				for i in 0..Self::Output::dimensions() {
					coordinates[i] = self.dimension_value(i) $operator other.dimension_value(i);
				}
				Self::Output { coordinates }
			}
		}

		// rhs is reference
		impl<'b> std::ops::$std_ops_name<&'b $type2> for $type1 {
			type Output = $type1;

			fn $fn_name (self, other: &'b $type2) -> $type1 {
				let mut coordinates = [0; Self::Output::dimensions()];
				for i in 0..Self::Output::dimensions() {
					coordinates[i] = self.dimension_value(i) $operator other.dimension_value(i);
				}
				Self::Output { coordinates }
			}
		}

		// Both references
		impl<'a, 'b> std::ops::$std_ops_name<&'b $type2> for &'a $type1 {
			type Output = $type1;

			fn $fn_name (self, other: &'b $type2) -> $type1 {
				let mut coordinates = [0; Self::Output::dimensions()];
				for i in 0..Self::Output::dimensions() {
					coordinates[i] = self.dimension_value(i) $operator other.dimension_value(i);
				}
				Self::Output { coordinates }
			}
		}
	}
}

pub trait FakeDimensions {
	fn dimension_value (self, dimension: usize) -> Self;
}

impl FakeDimensions for isize {
	fn dimension_value (self, _dimension: usize) -> Self { self }
}

impl_elementwise_operation!(Point2d, isize, Add, add, +);
impl_elementwise_operation!(Point2d, isize, Sub, sub, -);
impl_elementwise_operation!(Point2d, isize, Mul, mul, *);
impl_elementwise_operation!(Point2d, isize, Div, div, /);
impl_elementwise_operation!(Point2d, isize, Rem, rem, %);

impl_elementwise_operation!(Point2d, Point2d, Add, add, +);
impl_elementwise_operation!(Point2d, Point2d, Sub, sub, -);

impl Point2d {
	pub fn origin () -> Point2d { DIMENSIONS_2D_ORIGIN }
	pub fn cardinal_north () -> Point2d { DIMENSIONS_2D_DIRECTION_NORTH }
	pub fn cardinal_east () -> Point2d { DIMENSIONS_2D_DIRECTION_EAST }
	pub fn cardinal_south () -> Point2d { DIMENSIONS_2D_DIRECTION_SOUTH }
	pub fn cardinal_west () -> Point2d { DIMENSIONS_2D_DIRECTION_WEST }
}

impl Point2d {
	#[allow(dead_code)]

	pub fn new (x: isize, y: isize) -> Point2d {
		Point2d { coordinates: [x, y] }
	}

	// Higher dimensions include up, down, ana, kata
	pub fn north (&self) -> Point2d { self + DIMENSIONS_2D_DIRECTION_NORTH }
	pub fn east (&self) -> Point2d { self + DIMENSIONS_2D_DIRECTION_EAST }
	pub fn south (&self) -> Point2d { self + DIMENSIONS_2D_DIRECTION_SOUTH }
	pub fn west (&self) -> Point2d { self + DIMENSIONS_2D_DIRECTION_WEST }

	pub fn neighbor_cross (&self) -> impl Iterator<Item = Point2d> + '_ {
		return vec![self.north(), self.east(), self.west(), self.south()].into_iter();
	}

	pub fn neighbor_block (&self) -> impl Iterator<Item = Point2d> + '_ {
		return vec![
			self.north().west(), self.north(), self.north().east(),
			self.west(),                       self.east(),
			self.south().west(), self.south(), self.south().east()
		].into_iter();
	}
}

impl Point2d {
	#[allow(dead_code)]

	pub fn points_in_bounding_box <'a> (start: &'a Point2d, end: &'a Point2d) -> impl Iterator<Item = Point2d> + 'a {
		(start.y()..=end.y()).flat_map(move |y|
			(start.x()..=end.x()).map(move |x|
				Point2d::new(x, y)
			)
		)
	}
}

// https://stackoverflow.com/questions/77588838/how-to-create-a-custom-hash-function-in-rust
impl std::hash::Hash for Point2d {
	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
		let x = (self.x().unsigned_abs() * 2 + (self.x().signum() - 1).unsigned_abs() / 2) as u64;
		let y = (self.y().unsigned_abs() * 2 + (self.y().signum() - 1).unsigned_abs() / 2) as u64;

		/* szudziks function */
		let hash_val = if x >= y { x * x + x + y } else { x + y * y };
		state.write_u64(hash_val);
	}
}

impl std::fmt::Display for Point2d {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
		write!(f, "({}, {})", self.x(), self.y())
	}
}