use std::{f32::consts::PI, time::Instant};

use frienderer::{DebugStrip, DebugStripKind, DrawCommand, Quad, RRect, RawImage, Renderer, RotatingRRect, TextLayout};
use glam::{Vec2, Vec4, ivec2, uvec2, vec2};
use glfw::{Action, Context, Key, OpenGlProfileHint, WindowHint};
use image::ImageFormat;
use parley::{Alignment, FontContext, FontStack, LayoutContext, LineHeight, StyleProperty};
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

const OPENGL_PNG: &[u8] = include_bytes!("./opengl.png");

fn main() {
	let filter = tracing_subscriber::filter::Targets::new()
		.with_target("winit", Level::WARN)
		.with_default(Level::DEBUG);

	tracing_subscriber::registry()
		.with(tracing_subscriber::fmt::layer())
		.with(filter)
		.init();

	let mut glfw = glfw::init(glfw::fail_on_errors).unwrap();
	glfw.window_hint(WindowHint::ContextVersion(3, 3));
	glfw.window_hint(WindowHint::OpenGlProfile(OpenGlProfileHint::Core));
	glfw.window_hint(WindowHint::Resizable(true));
	glfw.window_hint(WindowHint::TransparentFramebuffer(true));

	let (mut window, events) = glfw
		.create_window(800, 600, "Hello friend :3", glfw::WindowMode::Windowed)
		.expect("Failed to create GLFW window.");

	window.set_key_polling(true);
	window.make_current();

	let (width, height) = window.get_size();
	let scale_factor = window.get_content_scale().0;
	let viewport = ivec2(width, height).as_vec2();

	let mut renderer = Renderer::new_with_str_loader(viewport, scale_factor, true, |symbol| {
		(window.get_proc_address(symbol))
			.map(|f| f as *const _)
			.unwrap_or_default()
	});
	renderer.set_clear_color(0.0, 0.0, 0.0, 0.5);

	// Context necessary to create text layouts
	// (You can create a TextLayoutContext containing both)
	let mut font_context = FontContext::new();
	let mut layout_context = LayoutContext::new();

	// Create some text layouts
	// Note: text layouts should not be recreated every frame. Make sure to have a cache for them.

	let colon_three = {
		let text = "Hello :3";
		let mut builder = layout_context.ranged_builder(&mut font_context, text, scale_factor, true);
		{
			builder.push_default(FontStack::from("Roboto, sans-serif"));
			builder.push_default(StyleProperty::LineHeight(LineHeight::FontSizeRelative(1.3)));
			builder.push_default(StyleProperty::FontSize(32.0));
		}
		TextLayout::new(
			builder.build(text),
			text.to_owned(),
			Alignment::Left,
			0xffffffff,
			scale_factor,
		)
	};
	let wacky_monospace_text = {
		let text = "[ ---- wacky monospace text ---- ]";
		let mut builder = layout_context.ranged_builder(&mut font_context, text, scale_factor, true);
		{
			builder.push_default(FontStack::from("'JetBrainsMono Nerd Font', monospace"));
			builder.push_default(StyleProperty::LineHeight(LineHeight::FontSizeRelative(1.3)));
			builder.push_default(StyleProperty::FontSize(16.0));
		}
		TextLayout::new(
			builder.build(text),
			text.to_owned(),
			Alignment::Left,
			0x00ff00ff,
			scale_factor,
		)
	};

	// Load some image
	// Note: images also need to be cached. Don't upload a texture every frame.
	let opengl_image = image::load_from_memory_with_format(OPENGL_PNG, ImageFormat::Png).unwrap();
	let opengl_texture = renderer.upload_texture(RawImage {
		width: opengl_image.width(),
		height: opengl_image.height(),
		pixels: opengl_image.as_bytes(),
	});

	// Debug strips
	let mut ds_points = vec![Vec2::ZERO; 32];
	let mut ds_lines = vec![Vec2::ZERO; 32];
	let dsi_points = renderer.create_debug_strip(&ds_points);
	let dsi_lines = renderer.create_debug_strip(&ds_lines);

	// Some global state (OpenGL DVD screen!)
	let bouncer_size = uvec2(opengl_image.width(), opengl_image.height()).as_vec2();
	let mut bouncer_pos = vec2(5.0, viewport.y - 5.0) + bouncer_size * Vec2::NEG_Y;
	let mut bouncer_vel = vec2(100.0, 72.7);

	let start = Instant::now();

	let mut prev_t = 0.0;
	while !window.should_close() {
		glfw.poll_events();
		for (_, event) in glfw::flush_messages(&events) {
			#[allow(clippy::single_match)]
			match event {
				glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => window.set_should_close(true),
				_ => {}
			}
		}

		let (width, height) = window.get_size();
		let viewport = ivec2(width, height).as_vec2();
		let center = viewport / 2.0;

		renderer.resize(
			((width as f32) * scale_factor) as i32,
			((height as f32) * scale_factor) as i32,
			scale_factor,
		);

		// Update then draw stuff
		{
			let t = start.elapsed().as_secs_f32();
			let dt = t - prev_t;
			prev_t = t;

			// Update state
			{
				// Detect collisions with the window borders
				if bouncer_pos.x <= 0.0 && bouncer_vel.x.is_sign_negative() {
					bouncer_vel.x *= -1.0;
				}
				if bouncer_pos.x + bouncer_size.x >= viewport.x && bouncer_vel.x.is_sign_positive() {
					bouncer_vel.x *= -1.0;
				}
				if bouncer_pos.y <= 0.0 && bouncer_vel.y.is_sign_negative() {
					bouncer_vel.y *= -1.0;
				}
				if bouncer_pos.y + bouncer_size.y >= viewport.y && bouncer_vel.y.is_sign_positive() {
					bouncer_vel.y *= -1.0;
				}

				bouncer_pos += dt * bouncer_vel;
			}

			// Update debug strips
			{
				let amplitude = 100.0;
				let point_space = 4.0;

				for (i, point) in ds_points.iter_mut().enumerate() {
					let x = t + i as f32 / 10.0;
					*point = vec2(i as f32 * point_space, amplitude * x.sin());
				}

				for (i, point) in ds_lines.iter_mut().enumerate() {
					let x = t + i as f32 / 10.0;
					*point = vec2(i as f32 * point_space, amplitude * x.cos());
				}

				renderer.update_debug_strip(dsi_points, &ds_points);
				renderer.update_debug_strip(dsi_lines, &ds_lines);
			}

			// Draw some text
			renderer.render_text_rotated(&colon_three, center, Vec2::splat(0.5), t / 10.0, scale_factor);

			let wmst_size = wacky_monospace_text.size();
			renderer.render_text(
				&wacky_monospace_text,
				vec2(center.x - wmst_size.x / 2.0, viewport.y - wmst_size.y - 5.0),
				scale_factor,
			);

			// Draw rectangles moving
			{
				let t1 = t;
				let t2 = t + PI;
				let t3 = t + PI / 2.0;

				let dist_from_center = 200.0;
				let rect_radius = 30.0;
				let rect_size = 100.0;
				let rect_center = rect_size / 2.0;

				let do_pos = |t: f32| center + dist_from_center * vec2(t.sin(), t.cos());

				renderer.push_draw_commands(&[
					DrawCommand::RRect(RRect {
						pos: do_pos(t1) - rect_center,
						size: Vec2::splat(rect_size),
						border_radius: Vec4::splat(rect_radius),
						border_width: Vec4::splat(0.0),
						fill_color: 0xff6464ff,
						stroke_color: 0x00000000,
						box_blur: 0.0,
					}),
					DrawCommand::RRect(RRect {
						pos: do_pos(t2) - rect_center - 20.0,
						size: Vec2::splat(rect_size + 40.0),
						border_radius: Vec4::splat(rect_radius + 20.0),
						border_width: Vec4::splat(0.0),
						fill_color: 0x00000080,
						stroke_color: 0x00000000,
						box_blur: 20.0,
					}),
					DrawCommand::RRect(RRect {
						pos: do_pos(t2) - rect_center,
						size: Vec2::splat(rect_size),
						border_radius: Vec4::splat(rect_radius),
						border_width: Vec4::splat(0.0),
						fill_color: 0x6480ffff,
						stroke_color: 0x00000000,
						box_blur: 0.0,
					}),
					DrawCommand::RotatingRRect(RotatingRRect {
						pos: do_pos(t3),
						size: Vec2::splat(rect_size),
						border_radius: Vec4::splat(rect_radius),
						border_width: Vec4::splat(0.0),
						fill_color: 0xffff64ff,
						stroke_color: 0x00000000,
						box_blur: 0.0,
						rotation: t / PI,
						origin: Vec2::splat(0.5),
					}),
				]);
			}

			// Draw an image
			renderer.push_draw_command(DrawCommand::TextureQuad(
				Quad {
					pos: bouncer_pos,
					size: bouncer_size,
					origin: Vec2::ZERO,
					uv_pos: Vec2::ZERO,
					uv_size: Vec2::ONE,
					rotation: 0.0,
				},
				opengl_texture,
			));

			// Debug strips
			renderer.push_draw_command(DrawCommand::DebugStrip(DebugStrip {
				kind: DebugStripKind::Lines,
				id: dsi_lines,
				color: 0xff80cc,
				offset: vec2(50.0, viewport.y / 2.0),
				scale: Vec2::splat(2.0),
			}));
			renderer.push_draw_command(DrawCommand::DebugStrip(DebugStrip {
				kind: DebugStripKind::Points,
				id: dsi_points,
				color: 0x80ffcc,
				offset: vec2(50.0, viewport.y / 2.0),
				scale: Vec2::splat(2.0),
			}));
		}
		renderer.draw();

		window.swap_buffers();
	}
}
