// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use glam::{IVec2, Vec2};

use super::Blocks;

use crate::utils::maths::{RectCorners, Rect, glam_fix::Fix};

pub struct RangeIterator<'a> {
	blocks: &'a Blocks,
	i: usize,
	p0: Vec2,
	pos: IVec2,
	min: IVec2,
	max: IVec2,
}

// Block structure represented as two points
pub struct Block(RectCorners);

impl Block {
	pub fn get_rect(&self) -> &impl Rect {
		&self.0
	}

	/**
	 * Returns an outwards-pointing normal vector that the circle is making on
	 * collision with the block. If there is no collision, a somewhat reasonable
	 * result would be returned.
	 */
	pub fn get_normal(&self, pos: Vec2) -> Vec2 {
		/*
		 * Divides space into nine 3x3 sections, for the circle's centre being
		 * before, in the middle of or after the block on each axis.
		 */
		enum Section {
			Before,
			Middle,
			After,
		}

		use Section::{Before, Middle, After};
		use std::f32::consts::FRAC_1_SQRT_2 as F1S2;

		fn get_section(pos: f32, x0: f32, x1: f32) -> Section {
			if pos < x0 { Before }
			else if pos > x1 { After }
			else { Middle }
		}


		let (p0, p1) = self.0.inner();

		let (x_sect, y_sect) = (get_section(pos.x, p0.x, p1.x), get_section(pos.y, p0.y, p1.y));

		match (x_sect, y_sect) {
			(Before, Middle) => Vec2::NEG_X,
			( After, Middle) => Vec2::X,
			(Middle, Before) => Vec2::NEG_Y,
			(Middle,  After) => Vec2::Y,
			(Before, Before) => (pos - p0).normalise_or(Vec2::new(-F1S2, -F1S2)),
			( After, Before) => (pos - Vec2::new(p1.x, p0.y)).normalise_or(Vec2::new(F1S2, -F1S2)),
			(Before,  After) => (pos - Vec2::new(p0.x, p1.y)).normalise_or(Vec2::new(-F1S2, F1S2)),
			( After,  After) => (pos - p1).normalise_or(Vec2::new(F1S2, F1S2)),
			(Middle, Middle) => {
				let block_centre = p0.midpoint(p1);
				let diff = pos - block_centre;
				let larger_axis = (diff.y.abs() > diff.x.abs()) as usize;
				let mut ret = Vec2::ZERO;
				let val = diff[larger_axis].signum();
				ret[larger_axis] = val;
				ret
			}
		}
	}
}

impl RangeIterator<'_> {
	pub(super) fn new(blocks: &Blocks, p0: Vec2, p1: Vec2) -> RangeIterator<'_> {
		debug_assert!(p0.cmple(p1).all() || (p0 + p1).is_nan()); // Doesn't panic if NaN for whatever reason, preventing a remote panic vulnerability

		let block_size = blocks.block_size.get() as f32;
		let off = block_size * blocks.grid_size.as_vec2() / 2.0;

		let mut min = ((p0 + off) / block_size).floor().as_ivec2();
		let mut max = ((p1 + off) / block_size).floor().as_ivec2();

		for i in 0..2 {
			min[i] = min[i].max(0);
			max[i] = max[i].min(blocks.grid_size[i] as i32 - 1);
			if min[i] as u32 >= blocks.grid_size[i] || max[i] < 0 {
				return RangeIterator { // If min not ≤ max, returns an iterator that immediately yields None rather than getting an error
					blocks,
					i: 0,
					p0: Vec2::ZERO,
					pos: IVec2::ZERO,
					min: IVec2::ZERO,
					max: IVec2::NEG_ONE,
				};
			}
		}

		RangeIterator {
			blocks,
			i: (min.x + min.y * blocks.grid_size.x as i32) as usize,
			p0: min.as_vec2() * block_size - off,
			pos: min, min, max,
		}
	}
}

impl Iterator for RangeIterator<'_> {
	type Item = Block;

	fn next(&mut self) -> Option<Block> {
		loop {
			if self.pos.x > self.max.x {
				self.i += self.blocks.grid_size.x as usize - (self.pos.x - self.min.x) as usize;
				self.pos.x = self.min.x;
				self.pos.y += 1;
				if self.pos.y > self.max.y {
					self.pos = self.max; // Allows the micro-optimisation of only checking this condition while in the loop
					return None;
				}
			}

			let is_block = self.blocks.data[self.i];

			self.i += 1;
			self.pos.x += 1;

			if is_block {
				let p0 = self.p0 + (IVec2::new(self.pos.x - 1 /* Undoes the previous increment */, self.pos.y) - self.min).as_vec2() * self.blocks.block_size.get() as f32;
				return Some(Block(RectCorners::new_unchecked(p0, p0 + Vec2::splat(self.blocks.block_size.get() as f32))));
			}
		}
	}
}
