use std::{
	time::Instant,
};

use anyhow::Result;

use opengl_rust::{
	prelude::*,
};

mod graphics;
mod level_loader;
mod logic;
mod virtual_gamepad;

use graphics::Graphics;
use level_loader::LoadedLevel;
use logic::LogicState;
use virtual_gamepad::VirtualGamepad;

pub struct GameState {
	logic: LogicState,
}

#[tokio::main]
async fn main () -> Result <()> {
	tracing_subscriber::fmt::fmt ()
	.with_env_filter (tracing_subscriber::EnvFilter::from_default_env())
	.with_span_events (tracing_subscriber::fmt::format::FmtSpan::CLOSE)
	.init ();
	
	let sdl_context = sdl2::init ().map_err (|e| anyhow! ("Can't init SDL: {}", e))?;
	let video_subsystem = sdl_context.video ().map_err (|e| anyhow! ("Can't get SDL video subsystem: {}", e))?;
	let window = video_subsystem.window ("3D platformer", 320 * 2, 240 * 2)
		.position_centered ()
		.opengl ()
		.build ()
	?;
	
	gl::load_with (|s| {
		video_subsystem.gl_get_proc_address (s) as *const _
	});
	
	if ! gl::ClearColor::is_loaded () {
		bail! ("gl::ClearColor didn't load, something is wrong with OpenGL");
	}
	
	let gl_ctx = window.gl_create_context ().map_err (|e| anyhow! ("Can't create OpenGL context: {}", e))?;
	
	window.gl_make_current (&gl_ctx).map_err (|e| anyhow! ("Can't make OpenGL context current: {}", e))?;
	
	let mut event_pump = sdl_context.event_pump ().unwrap ();
	
	let mut time_step = TimeStep::new (60, 1000);
	
	let level = LoadedLevel::from_path ("gltf/level-00.glb")?;
	
	let mut graphics = Graphics::new ();
	
	let mut gl_state = Default::default ();
	let mut game_state = GameState {
		logic: Default::default (),
	};
	let phys_params = opengl_rust::physics::Params {
		dt: 1.0 / 60.0,
		gravity: (0.0, 0.0, -0.5).into (),
		margin: 0.00125,
	};
	
	game_state.logic.reset_level (&level);
	
	let mut next_upf_print = 60;
	let mut last_upf_instant = Instant::now ();
	
	let mut depth_buffer = vec! [0u32; 320 * 2 * 240 * 2];
	
	'running: loop {
		let _frames_to_do = time_step.step ();
		let mut player_gamepad = virtual_gamepad::VirtualGamepad::default ();
		
		for event in event_pump.poll_iter () {
			match event {
				Event::Quit {..} |
				Event::KeyDown { keycode: Some (Keycode::Escape), .. } => {
					break 'running
				},
				Event::KeyDown { keycode: Some (Keycode::R), .. } => {
					game_state.logic.reset_level (&level);
				},
				Event::KeyDown { keycode: Some (Keycode::F12), .. } => {
					let depth_bytes: Vec <u8> = depth_buffer.iter ()
					.map (|x| (x >> 16) as u16)
					.map (|x| x.to_le_bytes ())
					.flatten ()
					.collect ();
					
					let mut f = std::fs::File::create ("screenshot.data")?;
					f.write_all (&depth_bytes)?;
				},
				Event::KeyDown { scancode: Some (Scancode::Space), repeat: false, .. } => {
					player_gamepad.jump.pressed = true;
				},
				Event::KeyDown { scancode: Some (Scancode::Left), repeat: false, .. } => {
					player_gamepad.d_left.pressed = true;
				},
				Event::KeyDown { scancode: Some (Scancode::Right), repeat: false, .. } => {
					player_gamepad.d_right.pressed = true;
				},
				Event::KeyDown { scancode: Some (Scancode::Up), repeat: false, .. } => {
					player_gamepad.d_up.pressed = true;
				},
				Event::KeyDown { scancode: Some (Scancode::Down), repeat: false, .. } => {
					player_gamepad.d_down.pressed = true;
				},
				
				_ => (),
			}
		}
		
		{
			let kb_state = event_pump.keyboard_state ();
			
			if kb_state.is_scancode_pressed (Scancode::Space) {
				player_gamepad.jump.held = true;
			}
			if kb_state.is_scancode_pressed (Scancode::Left) {
				player_gamepad.d_left.held = true;
			}
			if kb_state.is_scancode_pressed (Scancode::Right) {
				player_gamepad.d_right.held = true;
			}
			if kb_state.is_scancode_pressed (Scancode::Up) {
				player_gamepad.d_up.held = true;
			}
			if kb_state.is_scancode_pressed (Scancode::Down) {
				player_gamepad.d_down.held = true;
			}
		}
		
		let p_gp = player_gamepad;
		
		let logic_step_output = game_state.logic.step (&level.phys_tris, &phys_params, p_gp);
		if logic_step_output.reset_level {
			game_state.logic.reset_level (&level);
		}
		
		// dbg! (game_state.logic.player.pos);
		
		window.gl_make_current (&gl_ctx).unwrap ();
		
		let prediction_frames = 4;
		let mut predicted_logic = game_state.logic.clone ();
		for _ in 0..prediction_frames {
			predicted_logic.step (&level.phys_tris, &phys_params, p_gp);
		}
		
		graphics.draw (&level.phys_tris, &predicted_logic, &mut gl_state, &level, &level.camera);
		graphics.frames += 1;
		
		if graphics.frames == next_upf_print {
			let now = Instant::now ();
			let upf = (now - last_upf_instant).as_micros () / 60;
			
			dbg! (upf);
			
			next_upf_print += 60;
			last_upf_instant = now;
		}
		
		window.gl_swap_window ();
		
		unsafe {
			gl::ReadPixels (0, 0, 320 * 2, 240 * 2, gl::DEPTH_COMPONENT, gl::UNSIGNED_INT, &mut depth_buffer [0] as *mut u32 as *mut std::ffi::c_void);
		}
		
		tokio::time::sleep (Duration::from_millis (10)).await;
	}
	
	Ok (())
}

/*
struct TriangleStream {
	pub verts: gpu_buffers::VertexBuffer,
	pub indices: gpu_buffers::IndexBuffer,
}
*/
