use crate::application::Editor;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates, ModifierKeys};
use crate::messages::input_mapper::utility_types::input_mouse::{MouseButton, MouseKeys, MouseState};
use crate::messages::input_mapper::utility_types::misc::FrameTimeInfo;
use crate::messages::prelude::*;
use std::time::Duration;

#[derive(ExtractField)]
pub struct InputPreprocessorMessageContext<'a> {
	pub viewport: &'a ViewportMessageHandler,
}

#[derive(Debug, Default, ExtractField)]
pub struct InputPreprocessorMessageHandler {
	pub frame_time: FrameTimeInfo,
	pub time: u64,
	pub keyboard: KeyStates,
	pub mouse: MouseState,
}

#[message_handler_data]
impl<'a> MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext<'a>> for InputPreprocessorMessageHandler {
	fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, context: InputPreprocessorMessageContext<'a>) {
		let InputPreprocessorMessageContext { viewport } = context;

		match message {
			InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {
				self.update_states_of_modifier_keys(modifier_keys, responses);

				let mouse_state = editor_mouse_state.to_mouse_state(viewport);
				self.mouse.position = mouse_state.position;

				for key in mouse_state.mouse_keys {
					responses.add(InputMapperMessage::DoubleClick(match key {
						MouseKeys::LEFT => MouseButton::Left,
						MouseKeys::RIGHT => MouseButton::Right,
						MouseKeys::MIDDLE => MouseButton::Middle,
						MouseKeys::BACK => MouseButton::Back,
						MouseKeys::FORWARD => MouseButton::Forward,
						_ => unimplemented!(),
					}));
				}
			}
			InputPreprocessorMessage::KeyDown { key, key_repeat, modifier_keys } => {
				self.update_states_of_modifier_keys(modifier_keys, responses);
				self.keyboard.set(key as usize);
				if !key_repeat {
					responses.add(InputMapperMessage::KeyDownNoRepeat(key));
				}
				responses.add(InputMapperMessage::KeyDown(key));
			}
			InputPreprocessorMessage::KeyUp { key, key_repeat, modifier_keys } => {
				self.update_states_of_modifier_keys(modifier_keys, responses);
				self.keyboard.unset(key as usize);
				if !key_repeat {
					responses.add(InputMapperMessage::KeyUpNoRepeat(key));
				}
				responses.add(InputMapperMessage::KeyUp(key));
			}
			InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys } => {
				self.update_states_of_modifier_keys(modifier_keys, responses);

				let mouse_state = editor_mouse_state.to_mouse_state(viewport);
				self.mouse.position = mouse_state.position;

				self.translate_mouse_event(mouse_state, true, responses);
			}
			InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys } => {
				self.update_states_of_modifier_keys(modifier_keys, responses);

				let mouse_state = editor_mouse_state.to_mouse_state(viewport);
				self.mouse.position = mouse_state.position;

				responses.add(InputMapperMessage::PointerMove);

				// While any pointer button is already down, additional button down events are not reported, but they are sent as `pointermove` events
				self.translate_mouse_event(mouse_state, false, responses);
			}
			InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys } => {
				self.update_states_of_modifier_keys(modifier_keys, responses);

				let mouse_state = editor_mouse_state.to_mouse_state(viewport);
				self.mouse.position = mouse_state.position;

				self.translate_mouse_event(mouse_state, false, responses);
			}
			InputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys } => {
				self.update_states_of_modifier_keys(modifier_keys, responses);

				let mouse_state = editor_mouse_state.to_mouse_state(viewport);
				self.mouse.position = mouse_state.position;

				responses.add(InputMapperMessage::PointerShake);
			}
			InputPreprocessorMessage::CurrentTime { timestamp } => {
				responses.add(AnimationMessage::SetTime { time: timestamp as f64 });
				self.time = timestamp;
				self.frame_time.advance_timestamp(Duration::from_millis(timestamp));
			}
			InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => {
				self.update_states_of_modifier_keys(modifier_keys, responses);

				let mouse_state = editor_mouse_state.to_mouse_state(viewport);
				self.mouse.position = mouse_state.position;
				self.mouse.scroll_delta = mouse_state.scroll_delta;

				responses.add(InputMapperMessage::WheelScroll);
			}
		};
	}

	// Clean user input and if possible reconstruct it.
	// Store the changes in the keyboard if it is a key event.
	// Transform canvas coordinates to document coordinates.
	advertise_actions!();
}

impl InputPreprocessorMessageHandler {
	fn translate_mouse_event(&mut self, mut new_state: MouseState, allow_first_button_down: bool, responses: &mut VecDeque<Message>) {
		let click_mappings = [
			(MouseKeys::LEFT, Key::MouseLeft),
			(MouseKeys::RIGHT, Key::MouseRight),
			(MouseKeys::MIDDLE, Key::MouseMiddle),
			(MouseKeys::BACK, Key::MouseBack),
			(MouseKeys::FORWARD, Key::MouseForward),
		];

		for (bit_flag, key) in click_mappings {
			// Calculate the intersection between the two key states
			let old_down = self.mouse.mouse_keys & bit_flag == bit_flag;
			let new_down = new_state.mouse_keys & bit_flag == bit_flag;
			if !old_down && new_down {
				if allow_first_button_down || self.mouse.mouse_keys != MouseKeys::empty() {
					self.keyboard.set(key as usize);
					responses.add(InputMapperMessage::KeyDown(key));
				} else {
					// Required to stop a keyup being emitted for a keydown outside canvas
					new_state.mouse_keys ^= bit_flag;
				}
			}
			if old_down && !new_down {
				self.keyboard.unset(key as usize);
				responses.add(InputMapperMessage::KeyUp(key));
			}
		}

		self.mouse = new_state;
	}

	fn update_states_of_modifier_keys(&mut self, pressed_modifier_keys: ModifierKeys, responses: &mut VecDeque<Message>) {
		let is_key_pressed = |key_to_check: ModifierKeys| pressed_modifier_keys.contains(key_to_check);

		// Update the state of the concrete modifier keys based on the source state
		self.update_modifier_key(Key::Shift, is_key_pressed(ModifierKeys::SHIFT), responses);
		self.update_modifier_key(Key::Alt, is_key_pressed(ModifierKeys::ALT), responses);
		self.update_modifier_key(Key::Control, is_key_pressed(ModifierKeys::CONTROL), responses);

		// Update the state of either the concrete Meta or the Command keys based on which one is applicable for this platform
		let meta_or_command = match Editor::environment().is_mac() {
			true => Key::Command,
			false => Key::Meta,
		};
		self.update_modifier_key(meta_or_command, is_key_pressed(ModifierKeys::META_OR_COMMAND), responses);

		// Update the state of the virtual Accel key (the primary accelerator key) based on the source state of the Control or Command key, whichever is relevant on this platform
		let accel_virtual_key_state = match Editor::environment().is_mac() {
			true => is_key_pressed(ModifierKeys::META_OR_COMMAND),
			false => is_key_pressed(ModifierKeys::CONTROL),
		};
		self.update_modifier_key(Key::Accel, accel_virtual_key_state, responses);
	}

	fn update_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque<Message>) {
		let key_was_down = self.keyboard.get(key as usize);

		if key_was_down && !key_is_down {
			self.keyboard.unset(key as usize);
			responses.add(InputMapperMessage::KeyUp(key));
		} else if !key_was_down && key_is_down {
			self.keyboard.set(key as usize);
			responses.add(InputMapperMessage::KeyDown(key));
		}
	}
}

#[cfg(test)]
mod test {
	use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};
	use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta};
	use crate::messages::prelude::*;

	#[test]
	fn process_action_mouse_move_handle_modifier_keys() {
		let mut input_preprocessor = InputPreprocessorMessageHandler::default();

		let editor_mouse_state = EditorMouseState {
			editor_position: (4., 809.).into(),
			mouse_keys: MouseKeys::default(),
			scroll_delta: ScrollDelta::default(),
		};
		let modifier_keys = ModifierKeys::ALT;
		let message = InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys };

		let mut responses = VecDeque::new();

		let context = InputPreprocessorMessageContext {
			viewport: &ViewportMessageHandler::default(),
		};
		input_preprocessor.process_message(message, &mut responses, context);

		assert!(input_preprocessor.keyboard.get(Key::Alt as usize));
		assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Alt).into()));
	}

	#[test]
	fn process_action_mouse_down_handle_modifier_keys() {
		let mut input_preprocessor = InputPreprocessorMessageHandler::default();

		let editor_mouse_state = EditorMouseState::default();
		let modifier_keys = ModifierKeys::CONTROL;
		let message = InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys };

		let mut responses = VecDeque::new();

		let context = InputPreprocessorMessageContext {
			viewport: &ViewportMessageHandler::default(),
		};
		input_preprocessor.process_message(message, &mut responses, context);

		assert!(input_preprocessor.keyboard.get(Key::Control as usize));
		assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Control).into()));
	}

	#[test]
	fn process_action_mouse_up_handle_modifier_keys() {
		let mut input_preprocessor = InputPreprocessorMessageHandler::default();

		let editor_mouse_state = EditorMouseState::default();
		let modifier_keys = ModifierKeys::SHIFT;
		let message = InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys };

		let mut responses = VecDeque::new();

		let context = InputPreprocessorMessageContext {
			viewport: &ViewportMessageHandler::default(),
		};
		input_preprocessor.process_message(message, &mut responses, context);

		assert!(input_preprocessor.keyboard.get(Key::Shift as usize));
		assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Shift).into()));
	}

	#[test]
	fn process_action_key_down_handle_modifier_keys() {
		let mut input_preprocessor = InputPreprocessorMessageHandler::default();
		input_preprocessor.keyboard.set(Key::Control as usize);

		let key = Key::KeyA;
		let key_repeat = false;
		let modifier_keys = ModifierKeys::empty();
		let message = InputPreprocessorMessage::KeyDown { key, key_repeat, modifier_keys };

		let mut responses = VecDeque::new();

		let context = InputPreprocessorMessageContext {
			viewport: &ViewportMessageHandler::default(),
		};
		input_preprocessor.process_message(message, &mut responses, context);

		assert!(!input_preprocessor.keyboard.get(Key::Control as usize));
		assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::Control).into()));
	}

	#[test]
	fn process_action_key_up_handle_modifier_keys() {
		let mut input_preprocessor = InputPreprocessorMessageHandler::default();

		let key = Key::KeyS;
		let key_repeat = false;
		let modifier_keys = ModifierKeys::CONTROL | ModifierKeys::SHIFT;
		let message = InputPreprocessorMessage::KeyUp { key, key_repeat, modifier_keys };

		let mut responses = VecDeque::new();

		let context = InputPreprocessorMessageContext {
			viewport: &ViewportMessageHandler::default(),
		};
		input_preprocessor.process_message(message, &mut responses, context);

		assert!(input_preprocessor.keyboard.get(Key::Control as usize));
		assert!(input_preprocessor.keyboard.get(Key::Shift as usize));
		assert!(responses.contains(&InputMapperMessage::KeyDown(Key::Control).into()));
		assert!(responses.contains(&InputMapperMessage::KeyDown(Key::Control).into()));
	}
}
