//! Simple 2D texture atlas implementation.

use std::rc::Rc;

use glam::{UVec2, Vec2, uvec2};
use glow::HasContext;

use crate::common::{TextureInterpolation, TextureWrapping};

use super::{
	RawImage,
	common::{upload_subtexture, upload_texture},
};

/// A texture allocated on a texture atlas.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct Land {
	pub pos: UVec2,
	pub size: UVec2,
}

/// UV coordinates and size of an allocated land.
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct LandUv {
	pub pos: Vec2,
	pub size: Vec2,
}

/// A simple 2D texture atlas, for you to place textures into it like US states.
pub struct Texas {
	gl: Rc<glow::Context>,
	size: u32,
	texture: glow::Texture,
	curr_pos: UVec2,
	curr_row_height: u32,
}

impl Texas {
	pub fn new(gl: Rc<glow::Context>, size: u32, interpolation: TextureInterpolation) -> Self {
		unsafe {
			let texture = gl.create_texture().unwrap();
			upload_texture(
				&gl,
				texture,
				size,
				size,
				None,
				TextureWrapping::ClampToBorder,
				interpolation,
			);

			Self {
				gl,
				size,
				texture,
				curr_pos: UVec2::ZERO,
				curr_row_height: 0,
			}
		}
	}

	/// Allocates a plot of land in Texas.
	pub fn alloc_land(&mut self, gl: &glow::Context, image: RawImage) -> Option<Land> {
		// wrap back to the left
		if self.curr_pos.x + image.width > self.size {
			self.curr_pos = uvec2(0, self.curr_pos.y + self.curr_row_height);
			self.curr_row_height = 0;
		}

		// not enough space left in Texas
		if self.curr_pos.y + image.height > self.size {
			return None;
		}

		let land = Land {
			pos: self.curr_pos,
			size: uvec2(image.width, image.height),
		};

		self.curr_pos.x += image.width;
		self.curr_row_height = self.curr_row_height.max(image.height);

		unsafe {
			upload_subtexture(
				gl,
				self.texture,
				land.pos.x,
				land.pos.y,
				land.size.x,
				land.size.y,
				Some(image.pixels),
			);
		}

		Some(land)
	}

	pub fn clear_all(&mut self) {
		self.curr_pos = UVec2::ZERO;
		self.curr_row_height = 0;
	}

	pub fn uv_for_land(&self, land: Land) -> LandUv {
		LandUv {
			pos: land.pos.as_vec2() / self.size as f32,
			size: land.size.as_vec2() / self.size as f32,
		}
	}

	pub fn size(&self) -> u32 {
		self.size
	}

	pub fn texture(&self) -> glow::Texture {
		self.texture
	}
}
