// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
mod events;
mod special_areas;
mod scoring;

pub use events::EventConfig;
pub use special_areas::{SpecialAreaConfig, Portal, Rotation, PortalDefaultColourConfig, EffectZone, EffectZoneDefaultColourConfig};
pub use scoring::{ScoringConfig, ScoringControllerConfig, ScoringControllerSubconfig, ScoringEventListener, OccupationZone};

use std::{iter, sync::Arc, num::NonZeroUsize, collections::BTreeMap};

use serde::Deserialize;
use glam::Vec2;

use super::{TeamInfo, super::common::config::blocks::BlockConfig};

use crate::world::{config::WorldConfig, colour::{Colour, ColourAlpha}, team::{Team, TeamId}, special_area::SpecialArea};
use crate::protocol::{discovery::{Name, Description}, message::{text_io::TextIoString, timer::Timer}};
use crate::utils::maths::RectCorners;

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
	#[serde(default = "max_clients_default")] pub max_clients: Option<NonZeroUsize>,
	pub instructions: Option<TextIoString>,
	pub world: WorldConfig,
	#[serde(default)] pub discovery: DiscoveryConfig,
	pub blocks: BlockConfig,
	#[serde(default)] pub events: EventConfig,
	#[serde(default)] pub special_areas: SpecialAreaConfig,
	#[serde(default)] pub scoring: ScoringConfig,
	#[serde(default)] pub lobby: LobbyConfig,
	pub timer: Option<Timer>,
	#[serde(default)] pub teams: Vec<TeamConfig>,
	#[serde(default)] pub flags: Vec<FlagConfig>,
}

#[derive(Default, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DiscoveryConfig {
	#[serde(default)] pub name: Name,
	#[serde(default)] pub desc: Description,
}

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LobbyConfig {
	#[serde(default)] pub time: f64,
	#[serde(default = "reset_if_no_players_default")] pub reset_if_no_players: bool,
	#[serde(default = "ready_threshold_default")] pub ready_threshold: f32,
	#[serde(default = "local_spawn_radius_default")] pub local_spawn_radius: f32,
	#[serde(default = "min_players_default")] pub min_players: usize,
}

impl Default for LobbyConfig {
	fn default() -> LobbyConfig {
		LobbyConfig {
			time: 0.0,
			reset_if_no_players: reset_if_no_players_default(),
			ready_threshold: ready_threshold_default(),
			local_spawn_radius: local_spawn_radius_default(),
			min_players: min_players_default(),
		}
	}
}

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TeamConfig {
	name: Arc<str>,
	colour: Box<str>,
	#[serde(default)] aliases: Vec<Arc<str>>,
	base: Option<TeamBase>,
	pub spawn_point: Option<Vec2>,
}

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TeamBase {
	rect: RectCorners,
	#[serde(default = "team_zone_colour_default")] colour: Option<TeamZoneColour>,
}

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum TeamZoneColour {
	Default,
	Custom(ColourAlpha),
	CustomAlpha(u8),
}

impl TeamZoneColour {
	pub fn get(&self, team_colour: Option<Colour>) -> ColourAlpha {
		match self {
			TeamZoneColour::Default => TeamZoneColour::default_with_alpha(team_colour, 127),
			TeamZoneColour::Custom(colour) => *colour,
			TeamZoneColour::CustomAlpha(alpha) => TeamZoneColour::default_with_alpha(team_colour, *alpha),
		}
	}

	fn default_with_alpha(team_colour: Option<Colour>, alpha: u8) -> ColourAlpha {
		let colour = team_colour.unwrap_or_default();
		colour.op(|x| x / 2).with_alpha(alpha)
	}
}

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FlagConfig {
	pub team: TeamId,
	pub default_pos: Vec2,
	#[serde(default)] pub colour: FlagColour,
}

#[derive(Clone, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum FlagColour {
	#[default] Default,
	Custom(ColourAlpha),
	CustomAlpha(u8),
}

impl FlagColour {
	pub fn get(&self, team_colour: Option<Colour>) -> ColourAlpha {
		match self {
			FlagColour::Default => team_colour.unwrap_or_default().into(),
			FlagColour::Custom(colour) => *colour,
			FlagColour::CustomAlpha(alpha) => team_colour.unwrap_or_default().with_alpha(*alpha),
		}
	}
}

// It would be nice if serde could inline this in the attribute
fn max_clients_default() -> Option<NonZeroUsize> { NonZeroUsize::new(8) }
fn reset_if_no_players_default() -> bool { true }
fn ready_threshold_default() -> f32 { 1.0 }
fn local_spawn_radius_default() -> f32 { 4.0 }
fn min_players_default() -> usize { 1 }
#[allow(clippy::unnecessary_wraps)] fn team_zone_colour_default() -> Option<TeamZoneColour> { Some(TeamZoneColour::Default) }

type TeamList = Box<[Team]>;
type TeamBaseList = Box<[Option<RectCorners>]>;

impl Config {
	pub fn validate(&self) -> Result<(), String> {
		if self.events.ammo_crate_supply_range.0 >= self.events.ammo_crate_supply_range.1 {
			return Err(format!("ammo crate supply range (a, b) requires a ≤ b, got (a, b) = {}, {}", self.events.ammo_crate_supply_range.0, self.events.ammo_crate_supply_range.1));
		}

		self.scoring.controller.player.validate()?;
		self.scoring.controller.team.validate()?;

		Ok(())
	}

	pub(super) fn get_teams(config_teams: Vec<TeamConfig>, special_areas: &mut Vec<SpecialArea>) -> Result<(TeamList, TeamInfo, TeamBaseList), String> {
		let mut teams = Vec::with_capacity(config_teams.len());
		let mut team_bases = Vec::with_capacity(config_teams.len());
		let mut map = BTreeMap::new();
		let mut shortest_aliases = Vec::with_capacity(config_teams.len());

		// No duplicate names/aliases
		for (index, team) in config_teams.into_iter().enumerate() {
			let colour = Colour::try_from(team.colour.as_ref()).map_err(|err| format!("invalid colour for team \"{}\": {err}", team.name))?;
			teams.push(Team::new(Arc::clone(&team.name), colour));

			team_bases.push(team.base.map(|base| {
				if let Some(base_colour) = base.colour {
					special_areas.extend_from_slice(&SpecialArea::rect(base.rect, base_colour.get(Some(colour))));
				}
				base.rect
			}));

			let id = TeamId::from_index(index);
			for name in iter::once(Arc::clone(&team.name)).chain(team.aliases.iter().map(Arc::clone)) {
				if map.insert(Arc::clone(&name), id).is_some() {
					return Err(format!("duplicate team name/alias of \"{name}\""));
				}
			}

			let shortest_alias = team.aliases.iter()
				.min_by_key(|name| name.len())
				.filter(|name| name.len() <= team.name.len()) // Don't include the alias if longer than the team's name
				.map(Arc::clone);
			shortest_aliases.push(shortest_alias);
		}

		Ok((teams.into_boxed_slice(), TeamInfo { map, shortest_aliases: shortest_aliases.into_boxed_slice() }, team_bases.into_boxed_slice()))
	}
}
