use std::ffi::{CStr, c_void};
use std::rc::Rc;
#[cfg(feature = "text")]
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use batch::quad::TextureQuadBatchRenderer;
use batch::rrect::RRectBatchRenderer;
use common::{TextureWrapping, pop_debug_group, push_debug_group};
use glam::{Vec2, Vec4};
use glow::HasContext;
use rustc_hash::FxHashMap;

use crate::common::{TextureInterpolation, create_shader_program, slice_as_bytes, upload_texture};
use crate::white_rect::WhiteRectRenderer;

mod batch;
mod common;
mod texas;
mod white_rect;

#[cfg(feature = "text")]
mod quadlas;
#[cfg(feature = "text")]
mod text;

#[cfg(feature = "text")]
use crate::batch::glyph_quad::GlyphQuadBatchRenderer;
#[cfg(feature = "text")]
pub use text::TextLayout;

// re-exports
pub use glam;
#[cfg(feature = "text")]
pub use parley;

pub type Color = u32;

/// Set this in main when checking for the GL_KHR_debug extension.
pub static OPENGL_DEBUG_ENABLED: AtomicBool = AtomicBool::new(false);

/// Whether OpenGL debug logs (`KHR_debug` extension) are enabled for the renderer.
pub fn is_opengl_debug_enabled() -> bool {
	OPENGL_DEBUG_ENABLED.load(Ordering::Relaxed)
}

fn debug_message_callback(src: u32, ty: u32, _id: u32, severity: u32, msg: &str) {
	let ty = match ty {
		glow::DEBUG_TYPE_ERROR => "Error: ",
		glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior: ",
		glow::DEBUG_TYPE_MARKER => "Marker: ",
		glow::DEBUG_TYPE_OTHER => "",
		glow::DEBUG_TYPE_POP_GROUP => "Pop Group: ",
		glow::DEBUG_TYPE_PORTABILITY => "Portability: ",
		glow::DEBUG_TYPE_PUSH_GROUP => "Push Group: ",
		glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior: ",
		glow::DEBUG_TYPE_PERFORMANCE => "Performance: ",
		ty => unreachable!("unknown debug type {ty}"),
	};

	#[cfg(feature = "logging")]
	match severity {
		glow::DEBUG_SEVERITY_NOTIFICATION => {
			if src != glow::DEBUG_SOURCE_APPLICATION {
				tracing::debug!(target: "opengl", "{ty}{msg}")
			}
		}
		glow::DEBUG_SEVERITY_LOW => tracing::info!(target: "opengl", "{ty}{msg}"),
		glow::DEBUG_SEVERITY_MEDIUM => tracing::warn!(target: "opengl", "{ty}{msg}"),
		glow::DEBUG_SEVERITY_HIGH => tracing::error!(target: "opengl", "{ty}{msg}"),
		sevr => unreachable!("unknown debug severity {sevr}"),
	};

	#[cfg(not(feature = "logging"))]
	match severity {
		glow::DEBUG_SEVERITY_NOTIFICATION => {
			if src != glow::DEBUG_SOURCE_APPLICATION {
				eprintln!("[OpenGL] [DEBUG] {ty}{msg}")
			}
		}
		glow::DEBUG_SEVERITY_LOW => eprintln!("[OpenGL] [ INFO] {ty}{msg}"),
		glow::DEBUG_SEVERITY_MEDIUM => eprintln!("[OpenGL] [ WARN] {ty}{msg}"),
		glow::DEBUG_SEVERITY_HIGH => eprintln!("[OpenGL] [ERROR] {ty}{msg}"),
		sevr => unreachable!("unknown debug severity {sevr}"),
	}
}

unsafe fn init_opengl_debug_and_log_params(gl: &mut glow::Context, debug_gl: bool) {
	unsafe {
		#[cfg(feature = "logging")]
		{
			let renderer = gl.get_parameter_string(glow::RENDERER);
			tracing::info!("Renderer:    {}", renderer);

			let version = gl.get_parameter_string(glow::VERSION);
			tracing::info!("OpenGL ver:  {}", version);

			let shaders_version = gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION);
			tracing::info!("Shaders ver: {}", shaders_version);
		};

		if debug_gl {
			// Check for "GL_KHR_debug" support (not present on Apple *OS).
			let extensions = gl.supported_extensions();

			if extensions.contains("GL_KHR_debug") {
				#[cfg(feature = "logging")]
				tracing::info!("Debug ext:   supported");

				gl.enable(glow::DEBUG_OUTPUT);
				gl.enable(glow::DEBUG_OUTPUT_SYNCHRONOUS);
				gl.debug_message_callback(debug_message_callback);

				OPENGL_DEBUG_ENABLED.store(true, Ordering::Relaxed);
			} else {
				#[cfg(feature = "logging")]
				tracing::info!("Debug ext:   unsupported");
			}
		}

		#[cfg(feature = "logging")]
		{
			tracing::info!("");

			let max_texture_size = gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE);
			tracing::info!("Max texture size:           {}", max_texture_size);

			let max_3d_texture_size = gl.get_parameter_i32(glow::MAX_3D_TEXTURE_SIZE);
			tracing::info!("Max 3D texture size:        {}", max_3d_texture_size);

			let max_rectangle_texture_size = gl.get_parameter_i32(glow::MAX_RECTANGLE_TEXTURE_SIZE);
			tracing::info!("Max rectangle texture size: {}", max_rectangle_texture_size);

			if max_texture_size == 0 {
				tracing::warn!("Max texture size is a mystery. Weird GPU detected.");
			} else if max_texture_size < 2048 {
				tracing::warn!(
					"Max texture size is {} (less than 2048), device might be very old. Performance might be suboptimal.",
					max_texture_size
				);
			}
		};
	}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum BatchKind {
	None,
	RRect,
	Quad,

	#[cfg(feature = "text")]
	GlyphQuad,
}

#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct RRect {
	pub pos: Vec2,
	pub size: Vec2,
	pub border_radius: Vec4,
	pub border_width: Vec4,
	pub fill_color: u32,
	pub stroke_color: u32,
	pub box_blur: f32,
}

#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct RotatingRRect {
	pub pos: Vec2,
	pub size: Vec2,
	pub border_radius: Vec4,
	pub border_width: Vec4,
	pub fill_color: u32,
	pub stroke_color: u32,
	pub box_blur: f32,
	pub rotation: f32,
	pub origin: Vec2,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Quad {
	pub pos: Vec2,
	pub size: Vec2,
	pub origin: Vec2,
	pub uv_pos: Vec2,
	pub uv_size: Vec2,
	pub rotation: f32,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DebugStrip {
	pub kind: DebugStripKind,
	pub id: DebugStripId,
	pub color: Color,
	pub offset: Vec2,
	pub scale: Vec2,
}

#[derive(Clone)]
pub enum DrawCommand {
	RRect(RRect),
	RotatingRRect(RotatingRRect),
	TextureQuad(Quad, TextureHandle),
	#[cfg(feature = "text")]
	GlyphQuad(batch::glyph_quad::GlyphQuad, glow::Texture),

	DebugStrip(DebugStrip),

	MaskStart {
		draw_mask: bool,
	},
	MaskContent,
	MaskEnd,
}

#[derive(Clone, Copy)]
pub struct RawImage<'a> {
	pub width: u32,
	pub height: u32,
	pub pixels: &'a [u8],
}

struct BatchRenderers {
	rrect: RRectBatchRenderer,
	texture_quad: TextureQuadBatchRenderer,

	#[cfg(feature = "text")]
	glyph_quad: batch::glyph_quad::GlyphQuadBatchRenderer,
}

impl BatchRenderers {
	/// Returns number of executed draw calls.
	fn draw_batch(&mut self, batch_kind: BatchKind) -> usize {
		match batch_kind {
			BatchKind::None => 0,

			BatchKind::RRect => {
				self.rrect.upload_rrects();
				let draw_calls = self.rrect.draw();
				self.rrect.clear_rrects();
				draw_calls
			}

			BatchKind::Quad => {
				self.texture_quad.upload_quads();
				let draw_calls = self.texture_quad.draw();
				self.texture_quad.clear_quads();
				draw_calls
			}

			#[cfg(feature = "text")]
			BatchKind::GlyphQuad => {
				self.glyph_quad.upload_quads();
				let draw_calls = self.glyph_quad.draw();
				self.glyph_quad.clear_quads();
				draw_calls
			}
		}
	}
}

#[cfg(feature = "text")]
pub struct TextContext {
	font_context: parley::FontContext,
	layout_context: parley::LayoutContext<()>,
	scale_factor: f32,
}

#[cfg(feature = "text")]
impl TextContext {
	pub fn new(scale_factor: f32, font_context: parley::FontContext) -> Self {
		let layout_context = parley::LayoutContext::new();

		Self {
			font_context,
			layout_context,
			scale_factor,
		}
	}

	pub fn register_font_ttf(&mut self, ttf: Arc<dyn AsRef<[u8]> + Send + Sync>) {
		use parley::fontique::Blob;
		self.font_context.collection.register_fonts(Blob::new(ttf), None);
	}

	pub fn build_text(
		&mut self,
		alignment: parley::Alignment,
		color: Color,
		text: &str,
		f: impl FnOnce(&mut parley::RangedBuilder<'_, ()>),
	) -> TextLayout {
		let mut builder = (self.layout_context).ranged_builder(&mut self.font_context, text, self.scale_factor, true);

		(f)(&mut builder);

		// Build the builder into a Layout
		TextLayout::new(
			builder.build(text),
			text.to_owned(),
			alignment,
			color,
			self.scale_factor,
		)
	}

	pub fn contexts(&self) -> (&parley::FontContext, &parley::LayoutContext<()>) {
		(&self.font_context, &self.layout_context)
	}

	pub fn contexts_mut(&mut self) -> (&mut parley::FontContext, &mut parley::LayoutContext<()>) {
		(&mut self.font_context, &mut self.layout_context)
	}

	pub fn scale_factor(&self) -> f32 {
		self.scale_factor
	}
}

#[cfg(feature = "text")]
struct TextRendererState {
	quadlas: quadlas::Quadlas,
	quadlas_texture: glow::Texture,
	scale_cx: swash::scale::ScaleContext,
	scale_render: swash::scale::Render<'static>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TextureHandle(glow::Texture);

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DebugStripId(glow::Buffer);

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DebugStripKind {
	Points,
	Lines,
}

pub struct Renderer {
	gl: Rc<glow::Context>,
	viewport: Vec2,
	clear_color: [f32; 4],

	textures: Vec<glow::Texture>,

	batch_renderers: BatchRenderers,
	white_rect_renderer: WhiteRectRenderer,

	debug_strip_shader: glow::Program,
	debug_strip_vao: glow::VertexArray,
	u_debug_strip_viewport: glow::UniformLocation,
	u_debug_strip_offset: glow::UniformLocation,
	u_debug_strip_scale: glow::UniformLocation,
	u_debug_strip_color: glow::UniformLocation,
	debug_strip_lengths: FxHashMap<DebugStripId, usize>,

	#[cfg(feature = "text")]
	text: TextRendererState,

	command_buffer: Vec<DrawCommand>,
	current_frame: u64,
	draw_calls_in_prev_frame: usize,
}

impl Renderer {
	pub fn new_with_str_loader<F>(viewport: Vec2, scale_factor: f32, debug_logs: bool, loader_function: F) -> Self
	where
		F: FnMut(&str) -> *const c_void,
	{
		let gl = unsafe { glow::Context::from_loader_function(loader_function) };
		Self::new_inner(gl, viewport, scale_factor, debug_logs)
	}

	pub fn new_with_cstr_loader<F>(viewport: Vec2, scale_factor: f32, debug_logs: bool, loader_function: F) -> Self
	where
		F: FnMut(&CStr) -> *const c_void,
	{
		let gl = unsafe { glow::Context::from_loader_function_cstr(loader_function) };
		Self::new_inner(gl, viewport, scale_factor, debug_logs)
	}

	fn new_inner(mut gl: glow::Context, viewport: Vec2, scale_factor: f32, debug_logs: bool) -> Self {
		unsafe {
			init_opengl_debug_and_log_params(&mut gl, debug_logs);

			let gl = Rc::new(gl);
			push_debug_group(&gl, "Initialization");

			// Normal blending
			gl.enable(glow::BLEND);
			gl.blend_func_separate(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA, glow::SRC_ALPHA, glow::ONE);
			gl.blend_equation_separate(glow::FUNC_ADD, glow::FUNC_ADD);

			gl.enable(glow::PROGRAM_POINT_SIZE);

			let rrect = RRectBatchRenderer::new(Rc::clone(&gl), viewport, scale_factor);
			let texture_quad = TextureQuadBatchRenderer::new(Rc::clone(&gl), viewport);

			#[cfg(feature = "text")]
			let glyph_quad = GlyphQuadBatchRenderer::new(Rc::clone(&gl), viewport);

			let white_rect_renderer = WhiteRectRenderer::new(Rc::clone(&gl));

			#[cfg(feature = "text")]
			let text = {
				let atlas_size = suitable_atlas_size(viewport.x as u32, viewport.y as u32);

				let quadlas_texture = gl.create_texture().unwrap();
				let max_texture_size = gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE).clamp(1024, 8192) as u32;

				let quadlas = quadlas::Quadlas::new(max_texture_size, atlas_size);

				let mut scale_render = swash::scale::Render::new(&[
					swash::scale::Source::ColorOutline(0),
					swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
					swash::scale::Source::Outline,
				]);
				scale_render.format(swash::zeno::Format::Alpha);

				TextRendererState {
					quadlas,
					quadlas_texture,
					scale_cx: swash::scale::ScaleContext::new(),
					scale_render,
				}
			};

			const SRC_VERT_DEBUG_STRIP: &str = include_str!("./shaders/debug_strip.vert");
			const SRC_FRAG_DEBUG_STRIP: &str = include_str!("./shaders/debug_strip.frag");
			let debug_strip_shader = create_shader_program(&gl, SRC_VERT_DEBUG_STRIP, SRC_FRAG_DEBUG_STRIP);
			let u_debug_strip_viewport = gl.get_uniform_location(debug_strip_shader, "u_viewport").unwrap();
			let u_debug_strip_offset = gl.get_uniform_location(debug_strip_shader, "u_offset").unwrap();
			let u_debug_strip_scale = gl.get_uniform_location(debug_strip_shader, "u_scale").unwrap();
			// let u_debug_strip_point_size = gl.get_uniform_location(debug_strip_shader, "u_point_size").unwrap();
			let u_debug_strip_color = gl.get_uniform_location(debug_strip_shader, "u_color").unwrap();
			gl.uniform_2_f32(Some(&u_debug_strip_viewport), viewport.x, viewport.y);

			let debug_strip_vao = gl.create_vertex_array().unwrap();

			pop_debug_group(&gl);

			let mut renderer = Self {
				gl,
				viewport,
				clear_color: [0.0, 0.0, 0.0, 1.0],

				textures: Vec::new(),

				batch_renderers: BatchRenderers {
					rrect,
					texture_quad,

					#[cfg(feature = "text")]
					glyph_quad,
				},
				white_rect_renderer,

				debug_strip_shader,
				debug_strip_vao,
				u_debug_strip_viewport,
				u_debug_strip_offset,
				u_debug_strip_scale,
				u_debug_strip_color,
				debug_strip_lengths: FxHashMap::default(),

				#[cfg(feature = "text")]
				text,

				command_buffer: Vec::new(),
				current_frame: 0,
				draw_calls_in_prev_frame: 0,
			};

			#[cfg(feature = "text")]
			renderer.reupload_glyph_atlas_texture();

			renderer
		}
	}

	pub fn clear_textures(&mut self) {
		unsafe {
			push_debug_group(&self.gl, "Clear textures");
			for texture in &self.textures {
				self.gl.delete_texture(*texture);
			}
			pop_debug_group(&self.gl);
		}

		self.textures.clear();
	}

	pub fn upload_texture(&self, image: RawImage) -> TextureHandle {
		self.upload_texture_with_interp(image, TextureInterpolation::Linear)
	}

	pub fn upload_texture_with_interp(&self, image: RawImage, interpolation: TextureInterpolation) -> TextureHandle {
		unsafe {
			push_debug_group(&self.gl, "Upload texture");

			let texture = self.gl.create_texture().unwrap();

			common::upload_texture(
				&self.gl,
				texture,
				image.width,
				image.height,
				Some(image.pixels),
				TextureWrapping::ClampToBorder,
				interpolation,
			);

			pop_debug_group(&self.gl);

			TextureHandle(texture)
		}
	}

	pub fn delete_texture(&self, texture: TextureHandle) {
		unsafe {
			push_debug_group(&self.gl, "Delete texture");
			self.gl.delete_texture(texture.0);
			pop_debug_group(&self.gl);
		}
	}

	#[cfg(feature = "text")]
	pub fn glyph_atlas_debug_view(&mut self, pos_offset: Vec2, quad_size: Vec2) {
		(self.text.quadlas).debug_view(&mut self.command_buffer, pos_offset, quad_size);
	}

	#[cfg(feature = "text")]
	pub fn glyph_atlas_debug_glyph_priorities(&mut self, pos_offset: Vec2, quad_size: Vec2) {
		(self.text.quadlas).debug_glyph_priorities(&mut self.command_buffer, self.current_frame, pos_offset, quad_size);
	}

	#[cfg(feature = "text")]
	pub fn reupload_glyph_atlas_texture(&mut self) {
		let gl = &self.gl;
		unsafe {
			gl.delete_texture(self.text.quadlas_texture);
			self.text.quadlas_texture = gl.create_texture().unwrap();

			upload_texture(
				gl,
				self.text.quadlas_texture,
				self.text.quadlas.size(),
				self.text.quadlas.size(),
				None,
				TextureWrapping::ClampToBorder,
				TextureInterpolation::Nearest,
			);
		}
	}

	/// Returns the texture handle of the glyph atlas, along with its size in pixels (it's a square, so only one number is returned).
	#[cfg(feature = "text")]
	pub fn glyph_atlas_texture(&self) -> (TextureHandle, u32) {
		(TextureHandle(self.text.quadlas_texture), self.text.quadlas.size())
	}

	#[cfg(feature = "text")]
	pub fn clear_glyph_atlas(&mut self) {
		self.text.quadlas.clear_with_size(self.text.quadlas.size());
		self.reupload_glyph_atlas_texture();
	}

	#[cfg(feature = "text")]
	pub fn resize_glyph_atlas_using_window_size(&mut self, width: u32, height: u32) {
		self.text.quadlas.clear_with_size(suitable_atlas_size(width, height));
		self.reupload_glyph_atlas_texture();
	}

	pub fn create_debug_strip(&mut self, points: &[Vec2]) -> DebugStripId {
		let id = unsafe {
			let gl = &mut self.gl;

			let debug_strip_buffer = gl.create_buffer().unwrap();
			gl.bind_buffer(glow::ARRAY_BUFFER, Some(debug_strip_buffer));
			gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, slice_as_bytes(points), glow::DYNAMIC_DRAW);

			DebugStripId(debug_strip_buffer)
		};

		self.debug_strip_lengths.insert(id, points.len());

		id
	}

	pub fn update_debug_strip(&mut self, id: DebugStripId, points: &[Vec2]) {
		unsafe {
			let gl = &mut self.gl;
			push_debug_group(gl, "debug strip upload");
			gl.bind_buffer(glow::ARRAY_BUFFER, Some(id.0));
			gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, 0, slice_as_bytes(points));
			self.debug_strip_lengths.insert(id, points.len());
			pop_debug_group(gl);
		}
	}

	pub fn delete_debug_strip(&mut self, debug_strip_id: DebugStripId) {
		if self.debug_strip_lengths.remove(&debug_strip_id).is_some() {
			unsafe {
				self.gl.delete_buffer(debug_strip_id.0);
			}
		}
	}

	pub fn push_draw_command(&mut self, cmd: DrawCommand) {
		self.command_buffer.push(cmd);
	}

	pub fn push_draw_commands(&mut self, cmds: &[DrawCommand]) {
		self.command_buffer.extend_from_slice(cmds);
	}

	pub fn draw_command_count(&self) -> usize {
		self.command_buffer.len()
	}

	pub fn draw(&mut self) {
		unsafe {
			push_debug_group(&self.gl, "Draw");

			self.gl.bind_framebuffer(glow::FRAMEBUFFER, None);

			let [r, g, b, a] = self.clear_color;
			self.gl.clear_color(r, g, b, a);
			self.gl.clear(glow::COLOR_BUFFER_BIT);
		}

		self.batch_renderers.rrect.clear_rrects();
		self.batch_renderers.texture_quad.clear_quads();

		#[cfg(feature = "text")]
		self.batch_renderers.glyph_quad.clear_quads();

		let mut prev_batch_kind = BatchKind::None;

		#[derive(Debug, Clone, Copy)]
		enum StencilStackVal {
			MaskStart(bool),
			MaskContent,
		}

		let mut stencil_depth = 0;
		let mut stencil_stack: Vec<StencilStackVal> = Vec::new();

		self.draw_calls_in_prev_frame = 0;
		for cmd in self.command_buffer.drain(..) {
			// batch operations
			let batch_kind = match cmd {
				DrawCommand::RRect(rrect) => {
					self.batch_renderers.rrect.push_rrect(rrect);
					BatchKind::RRect
				}
				DrawCommand::RotatingRRect(rotating_rrect) => {
					self.batch_renderers.rrect.push_rotating_rrect(rotating_rrect);
					BatchKind::RRect
				}
				DrawCommand::TextureQuad(quad, texture) => {
					self.batch_renderers.texture_quad.push_quad(quad, texture.0);
					BatchKind::Quad
				}
				#[cfg(feature = "text")]
				DrawCommand::GlyphQuad(glyph_quad, texture) => {
					self.batch_renderers.glyph_quad.push_quad(glyph_quad, texture);
					BatchKind::GlyphQuad
				}
				_ => BatchKind::None,
			};

			if batch_kind != prev_batch_kind {
				self.draw_calls_in_prev_frame += self.batch_renderers.draw_batch(prev_batch_kind);
				prev_batch_kind = batch_kind;
			}

			// individual operations
			match cmd {
				DrawCommand::MaskStart { draw_mask } => {
					self.draw_calls_in_prev_frame += self.batch_renderers.draw_batch(prev_batch_kind);
					prev_batch_kind = BatchKind::None;

					if stencil_depth >= 255 {
						panic!("Stencil mask stack overflow (too many masks stacked on top of each other)");
					}

					unsafe {
						if stencil_depth == 0 {
							self.gl.enable(glow::STENCIL_TEST);
							self.gl.clear_stencil(0);
							self.gl.clear(glow::STENCIL_BUFFER_BIT);
						}

						push_debug_group(&self.gl, "Stencil Mask");

						self.gl.color_mask(draw_mask, draw_mask, draw_mask, draw_mask);
						self.gl.stencil_op(glow::KEEP, glow::INCR, glow::INCR);
						self.gl.stencil_func(glow::EQUAL, stencil_depth, 0xff);
					}

					stencil_depth += 1;
					stencil_stack.push(StencilStackVal::MaskStart(draw_mask));
				}
				DrawCommand::MaskContent => {
					self.draw_calls_in_prev_frame += self.batch_renderers.draw_batch(prev_batch_kind);
					prev_batch_kind = BatchKind::None;

					stencil_stack.pop();

					unsafe {
						pop_debug_group(&self.gl);
						push_debug_group(&self.gl, "Stencil Content");
						self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP);
						self.gl.stencil_func(glow::EQUAL, stencil_depth, 0xff);
						self.gl.color_mask(true, true, true, true);
					}

					stencil_stack.push(StencilStackVal::MaskContent);
				}
				DrawCommand::MaskEnd => {
					self.draw_calls_in_prev_frame += self.batch_renderers.draw_batch(prev_batch_kind);
					prev_batch_kind = BatchKind::None;

					stencil_depth -= 1;
					let stencil_stack_val = stencil_stack.pop();

					unsafe {
						pop_debug_group(&self.gl);

						push_debug_group(&self.gl, "Stencil Layer Clear");
						self.gl.color_mask(false, false, false, false);
						self.gl.stencil_op(glow::KEEP, glow::DECR, glow::DECR);
						self.gl.stencil_func(glow::EQUAL, stencil_depth + 1, 0xff);

						self.draw_calls_in_prev_frame += self.white_rect_renderer.draw();
						pop_debug_group(&self.gl);

						match stencil_stack_val {
							Some(StencilStackVal::MaskStart(draw_mask)) => {
								self.gl.color_mask(draw_mask, draw_mask, draw_mask, draw_mask);
								self.gl.stencil_op(glow::KEEP, glow::INCR, glow::INCR);
								self.gl.stencil_func(glow::EQUAL, stencil_depth, 0xff);
							}
							Some(StencilStackVal::MaskContent) => {
								self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP);
								self.gl.stencil_func(glow::EQUAL, stencil_depth, 0xff);
								self.gl.color_mask(true, true, true, true);
							}
							None => (),
						}

						if stencil_depth == 0 {
							self.gl.disable(glow::STENCIL_TEST);
						}
					}
				}

				DrawCommand::DebugStrip(debug_strip) => {
					let length = self.debug_strip_lengths[&debug_strip.id];

					let mode = match debug_strip.kind {
						DebugStripKind::Points => glow::POINTS,
						DebugStripKind::Lines => glow::LINE_STRIP,
					};

					unsafe {
						let gl = &self.gl;

						push_debug_group(gl, "Debug strip");
						gl.bind_buffer(glow::ARRAY_BUFFER, Some(debug_strip.id.0));

						gl.bind_vertex_array(Some(self.debug_strip_vao));
						gl.bind_vertex_array(Some(self.debug_strip_vao));

						// I have to enable vertex attrib array FOR THE CURRENTLY BINDED BUFFER.
						// Every time I read the documentation it would only ever mention the vertex array, so I spent
						// HOURS debugging this code thinking vertex attributes could just work with whatever buffer we give it,
						// but no, I have to specify that *every time*. Thank you, OpenGL. ¬¬
						gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, size_of::<Vec2>() as i32, 0 as _);
						gl.enable_vertex_attrib_array(0);

						gl.use_program(Some(self.debug_strip_shader));
						gl.uniform_1_i32(
							Some(&self.u_debug_strip_color),
							i32::from_ne_bytes(debug_strip.color.to_ne_bytes()),
						);
						gl.uniform_2_f32_slice(Some(&self.u_debug_strip_offset), &debug_strip.offset.to_array());
						gl.uniform_2_f32_slice(Some(&self.u_debug_strip_scale), &debug_strip.scale.to_array());
						gl.draw_arrays(mode, 0, length as i32);
						pop_debug_group(gl);
					}
				}

				_ => (),
			}
		}

		self.batch_renderers.draw_batch(prev_batch_kind);
		self.current_frame += 1;

		unsafe {
			pop_debug_group(&self.gl);
		}
	}

	pub fn resize(&mut self, width: i32, height: i32, scale_factor: f32) {
		unsafe {
			self.gl.viewport(0, 0, width, height);
			self.viewport = Vec2::new(width as f32, height as f32) / scale_factor;

			self.batch_renderers.rrect.resize(self.viewport);
			self.batch_renderers.texture_quad.resize(self.viewport);

			#[cfg(feature = "text")]
			self.batch_renderers.glyph_quad.resize(self.viewport);

			self.gl.use_program(Some(self.debug_strip_shader));
			(self.gl).uniform_2_f32(Some(&self.u_debug_strip_viewport), self.viewport.x, self.viewport.y);
		}
	}

	pub fn draw_calls_in_prev_frame(&self) -> usize {
		self.draw_calls_in_prev_frame
	}

	pub fn set_clear_color(&mut self, r: f32, g: f32, b: f32, a: f32) {
		self.clear_color = [r, g, b, a];
	}
}

impl Drop for Renderer {
	fn drop(&mut self) {
		self.clear_textures();
	}
}

fn suitable_atlas_size(width: u32, height: u32) -> u32 {
	2_u32.pow((width.max(height) as f32).log2().ceil() as u32) / 8
}
