// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::ops::Add;

use glam::{UVec2, IVec2};
use strum_macros::{EnumCount, EnumIter};

/**
 * A cell in the grid of a maze.
 *
 * This data structure contains a bitfield for the four edges and whether the
 * cell has been visited.
 *
 * Note that the edge bits are 1 for an opening and 0 for a wall.
 *
 * Data structure: 000v tbrl
 * 	l = left open
 * 	r = right open
 * 	b = bottom open
 * 	t = top open
 * 	v = visited
 */
#[derive(Clone, Copy)]
pub(super) struct Cell(u8);

#[derive(Clone, Copy, EnumCount, EnumIter)]
pub(super) enum Edge {
	Left = 1 << 0,
	Right = 1 << 1,
	Bottom = 1 << 2,
	Top = 1 << 3,
}

impl Edge {
	/** Returns the opposing edge. */
	pub fn opposite(self) -> Edge {
		match self {
			Edge::Left => Edge::Right,
			Edge::Right => Edge::Left,
			Edge::Bottom => Edge::Top,
			Edge::Top => Edge::Bottom,
		}
	}
}

impl Add<Edge> for UVec2 {
	type Output = UVec2;

	/** Adds the position with the edge's direction. */
	fn add(self, rhs: Edge) -> UVec2 {
		let dir = match rhs {
			Edge::Left => IVec2::NEG_X,
			Edge::Right => IVec2::X,
			Edge::Bottom => IVec2::NEG_Y,
			Edge::Top => IVec2::Y,
		};

		(self.as_ivec2() + dir).as_uvec2()
	}
}

const VISITED: u8 = 1 << 4;

impl Cell {
	/** Creates a new unvisited cell with all edges walls. */
	pub fn new() -> Cell {
		Cell(0)
	}

	/** Returns whether the cell is visited. */
	pub fn is_visited(self) -> bool { (self.0 & VISITED) != 0 }

	/** Marks the cell as visited. */
	pub fn visit(&mut self) { self.0 |= VISITED; }

	/** Removes the wall at `edge`. */
	pub fn open(&mut self, edge: Edge) { self.0 |= edge as u8; }

	/** Adds a wall at `edge`. */
	pub fn close(&mut self, edge: Edge) { self.0 &= !(edge as u8); }

	/** Removes whether the edge is open. */
	pub fn is_open(self, edge: Edge) -> bool { (self.0 & edge as u8) != 0 }
}
