use crate::physics_worker::InputInstruction;
use strafesnet_common::integer;
use strafesnet_common::instruction::TimedInstruction;

pub enum WindowInstruction{
	Resize(winit::dpi::PhysicalSize<u32>),
	WindowEvent(winit::event::WindowEvent),
	DeviceEvent(winit::event::DeviceEvent),
	RequestRedraw,
	Render,
}

//holds thread handles to dispatch to
struct WindowContext<'a>{
	manual_mouse_lock:bool,
	mouse:strafesnet_common::mouse::MouseState,//std::sync::Arc<std::sync::Mutex<>>
	screen_size:glam::UVec2,
	window:&'a winit::window::Window,
	physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<crate::physics_worker::Instruction>>,
}

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 window_event(&mut self,time:integer::Time,event:winit::event::WindowEvent){
		match event{
			winit::event::WindowEvent::DroppedFile(path)=>{
				match crate::file::load(path.as_path()){
					Ok(map)=>self.physics_thread.send(TimedInstruction{time,instruction:crate::physics_worker::Instruction::ChangeMap(map)}).unwrap(),
					Err(e)=>println!("Failed to load map: {e}"),
				}
			},
			winit::event::WindowEvent::Focused(state)=>{
				//pause unpause
				self.physics_thread.send(TimedInstruction{
					time,
					instruction:crate::physics_worker::Instruction::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.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(state.is_pressed());
					},
					(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab),winit::event::ElementState::Released)=>{
						//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(state.is_pressed());
					},
					(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);
					},
					(keycode,state)=>{
						let s=state.is_pressed();
						if let Some(input_instruction)=match keycode{
							winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>Some(InputInstruction::Jump(s)),
							winit::keyboard::Key::Character(key)=>match key.as_str(){
								"W"|"w"=>Some(InputInstruction::MoveForward(s)),
								"A"|"a"=>Some(InputInstruction::MoveLeft(s)),
								"S"|"s"=>Some(InputInstruction::MoveBack(s)),
								"D"|"d"=>Some(InputInstruction::MoveRight(s)),
								"E"|"e"=>Some(InputInstruction::MoveUp(s)),
								"Q"|"q"=>Some(InputInstruction::MoveDown(s)),
								"Z"|"z"=>Some(InputInstruction::Zoom(s)),
								"R"|"r"=>if s{
									//mouse needs to be reset since the position is absolute
									self.mouse=strafesnet_common::mouse::MouseState::default();
									Some(InputInstruction::ResetAndRestart)
								}else{None},
								"F"|"f"=>if s{Some(InputInstruction::PracticeFly)}else{None},
								_=>None,
							},
							_=>None,
						}{
							self.physics_thread.send(TimedInstruction{
								time,
								instruction:crate::physics_worker::Instruction::Input(input_instruction),
							}).unwrap();
						}
					},
				}
			},
			_=>(),
		}
	}

	fn device_event(&mut self,time:integer::Time,event: winit::event::DeviceEvent){
		match event{
			winit::event::DeviceEvent::MouseMotion{
			    delta,//these (f64,f64) are integers on my machine
			}=>{
				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),
					}
				}
				//do not step the physics because the mouse polling rate is higher than the physics can run.
				//essentially the previous input will be overwritten until a true step runs
				//which is fine because they run all the time.
				let delta=glam::ivec2(delta.0 as i32,delta.1 as i32);
				self.mouse.pos+=delta;
				self.physics_thread.send(TimedInstruction{
					time,
					instruction:crate::physics_worker::Instruction::Input(InputInstruction::MoveMouse(self.mouse.pos)),
				}).unwrap();
			},
			winit::event::DeviceEvent::MouseWheel {
			    delta,
			}=>{
				println!("mousewheel {:?}",delta);
				if false{//self.physics.style.use_scroll{
					self.physics_thread.send(TimedInstruction{
						time,
						instruction:crate::physics_worker::Instruction::Input(InputInstruction::Jump(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<WindowInstruction>>{
	// WindowContextSetup::new
	let user_settings=crate::settings::read_user_settings();

	let mut graphics=crate::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:strafesnet_common::mouse::MouseState::default(),
		//make sure to update this!!!!!
		screen_size,
		window,
		physics_thread:crate::physics_worker::new(
			graphics_thread,
			user_settings,
		),
	};

	//WindowContextSetup::into_worker
	crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction>|{
		match ins.instruction{
			WindowInstruction::RequestRedraw=>{
				window_context.window.request_redraw();
			}
			WindowInstruction::WindowEvent(window_event)=>{
				window_context.window_event(ins.time,window_event);
			},
			WindowInstruction::DeviceEvent(device_event)=>{
				window_context.device_event(ins.time,device_event);
			},
			WindowInstruction::Resize(size)=>{
				window_context.physics_thread.send(
					TimedInstruction{
						time:ins.time,
						instruction:crate::physics_worker::Instruction::Resize(size)
					}
				).unwrap();
			}
			WindowInstruction::Render=>{
				window_context.physics_thread.send(
					TimedInstruction{
						time:ins.time,
						instruction:crate::physics_worker::Instruction::Render
					}
				).unwrap();
			}
		}
	})
}