// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::collections::{BTreeMap, BTreeSet};

use common_macros::{b_tree_map, b_tree_set};
use strum::EnumCount;
use serde::{Serialize, Deserialize};
use winit::{keyboard::KeyCode, event::{MouseButton, WindowEvent}};

use super::action::{Action, Input, ActionEvent};

type Pressed = [BTreeSet<Input>; Action::COUNT];
type InputsToActions = BTreeMap<Input, BTreeSet<Action>>;
type ActionsToInputs = BTreeMap<Action, BTreeSet<Input>>;

#[derive(Clone, Serialize, Deserialize)]
#[serde(from = "ActionsToInputs", into = "ActionsToInputs")]
pub struct ActionManager {
	mapping: InputsToActions,
	inv_mapping: ActionsToInputs,
	pressed: Pressed,
	enabled: bool,
}

impl ActionManager {
	pub fn set_enabled(&mut self, enabled: bool) {
		self.enabled = enabled;
	}

	pub fn is_bound_to(&self, input: Input, action: Action) -> bool {
		self.mapping.get(&input).is_some_and(|actions| actions.contains(&action))
	}

	pub fn event(&mut self, event: &WindowEvent, mut action_listener: impl FnMut(ActionEvent)) {
		if !self.enabled { return; }

		if let Some((input, state)) = Input::from_event(event) {
			if let Some(actions) = self.mapping.get(&input) {
				let func = if state.pressed() { ActionManager::pressed } else { ActionManager::released };
				for &action in actions {
					// Returns true if a press/release action should be emitted
					if func(&mut self.pressed, action, input) {
						action_listener(ActionEvent::Action(action, state));
					}
				}
			}
		} else if matches!(event, WindowEvent::Focused(false)) {
			action_listener(ActionEvent::AllReleased);
		}
	}

	pub fn inputs_of_action(&self, action: Action) -> Option<impl Iterator<Item = Input> + '_> {
		self.inv_mapping.get(&action).map(|inputs| inputs.iter().copied())
	}

	pub fn bind(&mut self, input: Input, action: Action) {
		self.mapping.entry(input).or_default().insert(action);
		self.inv_mapping.entry(action).or_default().insert(input);
	}

	pub fn unbind(&mut self, input: Input, action: Action) {
		self.mapping.entry(input).or_default().remove(&action);
		self.inv_mapping.entry(action).or_default().remove(&input);
	}

	fn pressed(pressed: &mut Pressed, action: Action, input: Input) -> bool {
		let inputs_pressed = &mut pressed[action as usize];
		inputs_pressed.insert(input);

		/*
		 * Always emitting a press action as it might be helpful and is never
		 * unhelpful.
		 *
		 * For example, suppose that the user first presses escape to exit one GUI
		 * and never releases it. Then if they press caps lock, the GUI should
		 * exit again.
		 *
		 * This won't have a negative affect on something like moving the player,
		 * as additional press actions won't change the state.
		 */
		true
	}

	fn released(pressed: &mut Pressed, action: Action, input: Input) -> bool {
		let inputs_pressed = &mut pressed[action as usize];
		inputs_pressed.remove(&input);
		inputs_pressed.is_empty()
	}
}

impl Default for ActionManager {
	fn default() -> ActionManager {
		let left = || b_tree_set![Input::Key(KeyCode::KeyA), Input::Key(KeyCode::ArrowLeft)];
		let right = || b_tree_set![Input::Key(KeyCode::KeyD), Input::Key(KeyCode::ArrowRight)];

		b_tree_map!(
			// UI
			Action::GuiLeave => b_tree_set![Input::Key(KeyCode::Escape), Input::Key(KeyCode::CapsLock)],
			Action::PlayingLeave => b_tree_set![Input::Key(KeyCode::Escape), Input::Key(KeyCode::Backquote)], // Not also using caps lock because that's too close to 'A' and so might accidentally be pressed
			Action::ToggleFullscreen => b_tree_set![Input::Key(KeyCode::F11)],
			Action::TeamChatOpen => b_tree_set![Input::Key(KeyCode::KeyT)],
			Action::GlobalChatOpen => b_tree_set![Input::Key(KeyCode::KeyG)],
			Action::CommandOpen => b_tree_set![Input::Key(KeyCode::Slash)],
			Action::ToggleHud => b_tree_set![Input::Key(KeyCode::KeyH)],
			Action::StoryTextClose => b_tree_set![Input::Key(KeyCode::Escape), Input::Key(KeyCode::CapsLock), Input::Key(KeyCode::Space), Input::Key(KeyCode::Enter), Input::Key(KeyCode::NumpadEnter)],

			// Controls
			Action::MoveLeft => left(),
			Action::MoveRight => right(),
			Action::MoveDown => b_tree_set![Input::Key(KeyCode::KeyS), Input::Key(KeyCode::ArrowDown)],
			Action::MoveUp => b_tree_set![Input::Key(KeyCode::KeyW), Input::Key(KeyCode::ArrowUp)],
			Action::Shoot => b_tree_set![Input::MouseButton(MouseButton::Left), Input::Key(KeyCode::Space)],
			Action::DropFlag => b_tree_set![Input::Key(KeyCode::KeyQ)],

			// Camera
			Action::ZoomIn => b_tree_set![Input::Key(KeyCode::KeyK)],
			Action::ZoomOut => b_tree_set![Input::Key(KeyCode::KeyJ)],
			Action::ZoomReset => b_tree_set![Input::MouseButton(MouseButton::Middle)],
			Action::Drag => b_tree_set![Input::MouseButton(MouseButton::Left)],
			Action::FollowPlayer => b_tree_set![Input::Key(KeyCode::KeyF)],
			Action::PrevPlayer => left(),
			Action::NextPlayer => right(),

			// Debug
			Action::PrintPos => b_tree_set![Input::MouseButton(MouseButton::Right)],
		).into()
	}
}

impl From<ActionsToInputs> for ActionManager {
	fn from(mapping: ActionsToInputs) -> ActionManager {
		let inv_mapping = mapping;
		let mut mapping = InputsToActions::new();
		for (&action, inputs) in &inv_mapping {
			for &input in inputs {
				mapping.entry(input).or_default().insert(action);
			}
		}

		ActionManager { mapping, inv_mapping, pressed: Default::default(), enabled: true }
	}
}

impl From<ActionManager> for ActionsToInputs {
	fn from(action_manager: ActionManager) -> ActionsToInputs {
		action_manager.inv_mapping
	}
}
