use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::session::Time as SessionTime;
use strafesnet_common::physics::{MiscInstruction,SetControlInstruction};
use crate::file::LoadFormat;
use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
use strafesnet_session::session::{self,SessionInputInstruction,SessionControlInstruction,SessionPlaybackInstruction};
use strafesnet_settings::directories::Directories;

pub enum Instruction{
	WindowEvent(winit::event::WindowEvent),
	DeviceEvent(winit::event::DeviceEvent),
}

//holds thread handles to dispatch to
struct WindowContext<'a>{
	manual_mouse_lock:bool,
	mouse_pos:glam::DVec2,
	simulation_paused:bool,
	screen_size:glam::UVec2,
	window:&'a winit::window::Window,
	physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<PhysicsWorkerInstruction,SessionTime>>,
}

impl WindowContext<'_>{
	fn get_middle_of_screen(&self)->winit::dpi::PhysicalPosition<u32>{
		winit::dpi::PhysicalPosition::new(self.screen_size.x/2,self.screen_size.y/2)
	}
	fn free_mouse(&mut self){
		self.manual_mouse_lock=false;
		match self.window.set_cursor_position(self.get_middle_of_screen()){
			Ok(())=>(),
			Err(e)=>println!("Could not set cursor position: {:?}",e),
		}
		match self.window.set_cursor_grab(winit::window::CursorGrabMode::None){
			Ok(())=>(),
			Err(e)=>println!("Could not release cursor: {:?}",e),
		}
		self.window.set_cursor_visible(true);
	}
	fn lock_mouse(&mut self){
		//if cursor is outside window don't lock but apparently there's no get pos function
		//let pos=window.get_cursor_pos();
		match self.window.set_cursor_grab(winit::window::CursorGrabMode::Locked){
			Ok(())=>(),
			Err(_)=>{
				match self.window.set_cursor_grab(winit::window::CursorGrabMode::Confined){
					Ok(())=>(),
					Err(e)=>{
						self.manual_mouse_lock=true;
						println!("Could not confine cursor: {:?}",e)
					},
				}
			}
		}
		self.window.set_cursor_visible(false);
	}
	fn window_event(&mut self,time:SessionTime,event:winit::event::WindowEvent){
		match event{
			winit::event::WindowEvent::DroppedFile(path)=>{
				match crate::file::load(path.as_path()){
					Ok(LoadFormat::Map(map))=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::ChangeMap(map)}).unwrap(),
					Ok(LoadFormat::Bot(bot))=>self.physics_thread.send(TimedInstruction{time,instruction:PhysicsWorkerInstruction::LoadReplay(bot)}).unwrap(),
					Err(e)=>println!("Failed to load file: {e}"),
				}
			},
			winit::event::WindowEvent::Focused(state)=>{
				// don't unpause if manually paused
				if self.simulation_paused{
					return;
				}
				//pause unpause
				self.physics_thread.send(TimedInstruction{
					time,
					instruction:PhysicsWorkerInstruction::SessionControl(SessionControlInstruction::SetPaused(!state)),
				}).unwrap();
				//recalculate pressed keys on focus
			},
			winit::event::WindowEvent::KeyboardInput{
				event:winit::event::KeyEvent{state,logical_key,repeat:false,..},
				..
			}=>{
				match (logical_key,state){
					(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab),winit::event::ElementState::Pressed)=>self.free_mouse(),
					(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab),winit::event::ElementState::Released)=>self.lock_mouse(),
					(winit::keyboard::Key::Named(winit::keyboard::NamedKey::F11),winit::event::ElementState::Pressed)=>{
						if self.window.fullscreen().is_some(){
							self.window.set_fullscreen(None);
						}else{
							self.window.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
						}
					},
					(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),winit::event::ElementState::Pressed)=>{
						self.manual_mouse_lock=false;
						match self.window.set_cursor_grab(winit::window::CursorGrabMode::None){
							Ok(())=>(),
							Err(e)=>println!("Could not release cursor: {:?}",e),
						}
						self.window.set_cursor_visible(true);
					},
					(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Enter),winit::event::ElementState::Pressed)=>{
						let paused=!self.simulation_paused;
						self.simulation_paused=paused;
						if paused{
							self.free_mouse();
						}else{
							self.lock_mouse();
						}
						let instruction=PhysicsWorkerInstruction::SessionControl(SessionControlInstruction::SetPaused(paused));
						self.physics_thread.send(TimedInstruction{
							time,
							instruction,
						}).unwrap();
					}
					(keycode,state)=>{
						if self.simulation_paused{
							return;
						}
						let s=state.is_pressed();

						// internal variants for this scope
						enum SessionInstructionSubset{
							Input(SessionInputInstruction),
							Control(SessionControlInstruction),
							Playback(SessionPlaybackInstruction),
						}
						macro_rules! input_ctrl{
							($variant:ident,$state:expr)=>{
								Some(SessionInstructionSubset::Input(SessionInputInstruction::SetControl(SetControlInstruction::$variant($state))))
							};
						}
						macro_rules! input_misc{
							($variant:ident,$state:expr)=>{
								s.then_some(SessionInstructionSubset::Input(SessionInputInstruction::Misc(MiscInstruction::$variant)))
							};
						}
						macro_rules! session_ctrl{
							($variant:ident,$state:expr)=>{
								s.then_some(SessionInstructionSubset::Control(SessionControlInstruction::$variant))
							};
						}
						macro_rules! session_playback{
							($variant:ident,$state:expr)=>{
								s.then_some(SessionInstructionSubset::Playback(SessionPlaybackInstruction::$variant))
							};
						}
						impl From<SessionInstructionSubset> for PhysicsWorkerInstruction{
							fn from(value:SessionInstructionSubset)->Self{
								match value{
									SessionInstructionSubset::Input(session_input_instruction)=>PhysicsWorkerInstruction::SessionInput(session_input_instruction),
									SessionInstructionSubset::Control(session_control_instruction)=>PhysicsWorkerInstruction::SessionControl(session_control_instruction),
									SessionInstructionSubset::Playback(session_playback_instruction)=>PhysicsWorkerInstruction::SessionPlayback(session_playback_instruction),
								}
							}
						}

						if let Some(session_instruction)=match keycode{
							winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>input_ctrl!(SetJump,s),
							// TODO: bind system so playback pausing can use spacebar
							winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowUp)=>session_playback!(IncreaseTimescale,s),
							winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowDown)=>session_playback!(DecreaseTimescale,s),
							winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowLeft)=>session_playback!(SkipBack,s),
							winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowRight)=>session_playback!(SkipForward,s),
							winit::keyboard::Key::Character(key)=>match key.as_str(){
								"W"|"w"=>input_ctrl!(SetMoveForward,s),
								"A"|"a"=>input_ctrl!(SetMoveLeft,s),
								"S"|"s"=>input_ctrl!(SetMoveBack,s),
								"D"|"d"=>input_ctrl!(SetMoveRight,s),
								"E"|"e"=>input_ctrl!(SetMoveUp,s),
								"Q"|"q"=>input_ctrl!(SetMoveDown,s),
								"Z"|"z"=>input_ctrl!(SetZoom,s),
								"R"|"r"=>s.then(||{
									//mouse needs to be reset since the position is absolute
									self.mouse_pos=glam::DVec2::ZERO;
									SessionInstructionSubset::Input(SessionInputInstruction::Mode(session::ImplicitModeInstruction::ResetAndRestart))
								}),
								"F"|"f"=>input_misc!(PracticeFly,s),
								"B"|"b"=>session_ctrl!(CopyRecordingIntoReplayAndSpectate,s),
								"X"|"x"=>session_ctrl!(StopSpectate,s),
								"N"|"n"=>session_ctrl!(SaveReplay,s),
								"J"|"j"=>session_ctrl!(LoadIntoReplayState,s),
								_=>None,
							},
							_=>None,
						}{
							self.physics_thread.send(TimedInstruction{
								time,
								instruction:session_instruction.into(),
							}).unwrap();
						}
					},
				}
			},
			winit::event::WindowEvent::Resized(size)=>{
				self.physics_thread.send(
					TimedInstruction{
						time,
						instruction:PhysicsWorkerInstruction::Resize(size)
					}
				).unwrap();
			},
			winit::event::WindowEvent::RedrawRequested=>{
				self.window.request_redraw();
				self.physics_thread.send(
					TimedInstruction{
						time,
						instruction:PhysicsWorkerInstruction::Render
					}
				).unwrap();
			},
			_=>(),
		}
	}

	fn device_event(&mut self,time:SessionTime,event:winit::event::DeviceEvent){
		match event{
			winit::event::DeviceEvent::MouseMotion{
				delta:(delta_x,delta_y),
			}=>{
				if self.manual_mouse_lock{
					match self.window.set_cursor_position(self.get_middle_of_screen()){
						Ok(())=>(),
						Err(e)=>println!("Could not set cursor position: {:?}",e),
					}
				}
				self.mouse_pos+=glam::dvec2(delta_x,delta_y);
				if self.simulation_paused{
					return;
				}
				self.physics_thread.send(TimedInstruction{
					time,
					instruction:PhysicsWorkerInstruction::SessionInput(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())),
				}).unwrap();
			},
			winit::event::DeviceEvent::MouseWheel {
			    delta,
			}=>{
				println!("mousewheel {:?}",delta);
				if false{//self.physics.style.use_scroll{
					self.physics_thread.send(TimedInstruction{
						time,
						instruction:PhysicsWorkerInstruction::SessionInput(SessionInputInstruction::SetControl(SetControlInstruction::SetJump(true))),//activates the immediate jump path, but the style modifier prevents controls&CONTROL_JUMP bit from being set to auto jump
					}).unwrap();
				}
			},
			_=>(),
		}
	}
}
pub fn worker<'a>(
	window:&'a winit::window::Window,
	setup_context:crate::setup::SetupContext<'a>,
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>{
	// WindowContextSetup::new
	#[cfg(feature="user-install")]
	let directories=Directories::user().unwrap();
	#[cfg(not(feature="user-install"))]
	let directories=Directories::portable().unwrap();

	let user_settings=directories.settings();

	let mut graphics=strafesnet_graphics::graphics::GraphicsState::new(&setup_context.device,&setup_context.queue,&setup_context.config);
	graphics.load_user_settings(&user_settings);

	//WindowContextSetup::into_context
	let screen_size=glam::uvec2(setup_context.config.width,setup_context.config.height);
	let graphics_thread=crate::graphics_worker::new(graphics,setup_context.config,setup_context.surface,setup_context.device,setup_context.queue);
	let mut window_context=WindowContext{
		manual_mouse_lock:false,
		mouse_pos:glam::DVec2::ZERO,
		simulation_paused:false,
		//make sure to update this!!!!!
		screen_size,
		window,
		physics_thread:crate::physics_worker::new(
			graphics_thread,
			directories,
			user_settings,
		),
	};

	//WindowContextSetup::into_worker
	crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTime>|{
		match ins.instruction{
			Instruction::WindowEvent(window_event)=>{
				window_context.window_event(ins.time,window_event);
			},
			Instruction::DeviceEvent(device_event)=>{
				window_context.device_event(ins.time,device_event);
			},
		}
	})
}
