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

use serde::{Serialize, Deserialize};
use strum::EnumCount;
use strum_macros::{EnumCount, EnumIter};
use glam::Vec2;

use super::{Changes, powerup::PowerupType, player::PlayerId};

#[cfg(feature = "client")] use super::MAX_PLAYERS;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, EnumCount, EnumIter)]
pub enum Effect {
	Speed,
	Reload,
	Regeneration,
	Damage,
	Forcefield,
}

pub enum PowerType {
	Multiplicative,
	Additive,
}

impl Effect {
	pub fn try_from_type(typ: PowerupType) -> Option<Effect> {
		match typ {
			PowerupType::Speed => Some(Effect::Speed),
			PowerupType::Reload => Some(Effect::Reload),
			PowerupType::Health => Some(Effect::Regeneration),
			PowerupType::Damage => Some(Effect::Damage),
			PowerupType::Forcefield => Some(Effect::Forcefield),
			PowerupType::Teleportation => None,
		}
	}

	pub fn power_type(self) -> PowerType {
		match self {
			Effect::Speed | Effect::Reload | Effect::Regeneration | Effect::Damage => PowerType::Multiplicative,
			Effect::Forcefield => PowerType::Additive,
		}
	}

	#[cfg(feature = "client")]
	pub fn default_power(self) -> f32 {
		match self.power_type() {
			PowerType::Multiplicative => 1.0,
			PowerType::Additive => 0.0,
		}
	}
}

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
pub struct EffectInfo {
	pub time: f32,
	pub power: f32,
}

impl EffectInfo {
	pub fn active(self) -> bool {
		self.time > 0.0
	}

	pub fn get_power(self) -> Option<f32> {
		self.active().then_some(self.power)
	}

	#[cfg(feature = "client")]
	pub fn get_mul(self) -> f32 {
		self.get_power().unwrap_or(1.0)
	}

	#[cfg(feature = "client")]
	pub fn normalise(&mut self, effect: Effect) {
		if !self.active() {
			self.power = effect.default_power();
		}
	}
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct AllEffectInfo([EffectInfo; Effect::COUNT]);

impl AllEffectInfo {
	pub fn get(&self, effect: Effect) -> EffectInfo {
		self.0[effect as usize]
	}

	pub fn get_mut(&mut self, effect: Effect) -> &mut EffectInfo {
		&mut self.0[effect as usize]
	}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Effects(BTreeMap<PlayerId, AllEffectInfo>);

impl Effects {
	pub fn new() -> Effects {
		Effects(BTreeMap::new())
	}

	#[cfg(feature = "client")]
	pub fn validate(&self) -> Result<(), &'static str> {
		if self.0.len() <= MAX_PLAYERS { Ok(()) }
		else { Err("too many players") }
	}

	pub fn remove_player(&mut self, id: PlayerId) {
		self.0.remove(&id);
	}

	pub fn get_all_info(&self, id: PlayerId) -> Option<&AllEffectInfo> {
		self.0.get(&id)
	}

	fn get_info(&self, id: PlayerId, effect: Effect) -> Option<EffectInfo> {
		Some(self.get_all_info(id)?.get(effect))
	}

	#[cfg(feature = "client")]
	pub fn active(&self, id: PlayerId, effect: Effect) -> bool {
		self.get_info(id, effect).is_some_and(EffectInfo::active)
	}

	#[cfg(feature = "client")]
	pub fn get_time(&self, id: PlayerId, effect: Effect) -> Option<f32> {
		let info = self.get_info(id, effect)?;
		info.active().then_some(info.time)
	}

	/**
	 * Returns the "power" of the effect if the player has that effect, otherwise
	 * None.
	 *
	 * The interpretation of this "power" depends on the type of effect. For
	 * forcefields, it's the strength of the repulsion and for everything else
	 * it's a multiplier.
	 */
	pub fn get_power(&self, id: PlayerId, effect: Effect) -> Option<f32> {
		self.get_info(id, effect)?.get_power()
	}

	/**
	 * Convenience method for effects when the power is a multiplier, to which a
	 * default of 1 is returned if the effect isn't active.
	 */
	pub fn get_mul(&self, id: PlayerId, effect: Effect) -> f32 {
		self.get_power(id, effect).unwrap_or(1.0)
	}

	pub fn add_effect(&mut self, id: PlayerId, effect: Effect, info: EffectInfo, changes: &mut impl Changes, player_pos: Vec2, play_sound: bool) {
		let effects = self.0.entry(id).or_default();
		let old_info = effects.get_mut(effect);
		changes.effect_event(id, effect, *old_info, info, player_pos, play_sound);
		*old_info = info;
	}

	/**
	 * Returns an iterator of all player ids and the corresponding time of all
	 * players with `effect`.
	 */
	pub fn player_iter_with_effect(&self, effect: Effect) -> impl Iterator<Item = (PlayerId, EffectInfo)> + '_ {
		self.0.iter().filter_map(move |(&id, effects)| {
			let info = effects.get(effect);
			info.active().then_some((id, info))
		})
	}

	pub fn update(&mut self, dt: f32) {
		self.0.retain(|_, effects| {
			let mut has_active = false;
			for effect in &mut effects.0 {
				effect.time -= dt;
				has_active |= effect.active();
			}
			has_active
		});
	}
}
