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

use serde::{Serialize, Deserialize};

use crate::world::{player::PlayerId, team::TeamId};

#[derive(Default, Clone, Serialize, Deserialize)]
pub struct Scores {
	player: Subscores<PlayerId>,
	team: Subscores<TeamId>,
}

#[derive(Default, Clone, Serialize, Deserialize)]
pub struct Subscores<I> where I: Ord {
	scores: BTreeMap<I, i32>,
	ordering: ScoreOrdering,
}

#[repr(u8)]
#[derive(Default, Clone, Copy, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum ScoreOrdering {
	Ascending,
	#[default] Descending,
}

impl Scores {
	pub fn new(player_ordering: ScoreOrdering, team_ordering: ScoreOrdering) -> Scores {
		Scores {
			player: Subscores { scores: BTreeMap::new(), ordering: player_ordering },
			team: Subscores { scores: BTreeMap::new(), ordering: team_ordering },
		}
	}

	pub fn clear(&mut self) {
		self.player.scores.clear();
		self.team.scores.clear();
	}

	pub fn get_player_scores(&self) -> &BTreeMap<PlayerId, i32> { &self.player.scores }
	pub fn get_team_scores(&self) -> &BTreeMap<TeamId, i32> { &self.team.scores }
	pub fn get_player_scores_mut(&mut self) -> &mut BTreeMap<PlayerId, i32> { &mut self.player.scores }
	pub fn get_team_scores_mut(&mut self) -> &mut BTreeMap<TeamId, i32> { &mut self.team.scores }
	#[cfg(feature = "client")] pub fn get_player_ordering(&self) -> ScoreOrdering { self.player.ordering }
	#[cfg(feature = "client")] pub fn get_team_ordering(&self) -> ScoreOrdering { self.team.ordering }
}

#[derive(Clone, Serialize, Deserialize)]
pub struct DScores {
	player_scores: BTreeMap<PlayerId, Option<i32>>,
	team_scores: BTreeMap<TeamId, Option<i32>>,
}

impl DScores {
	pub fn new(new: &Scores, old: &Scores) -> Option<DScores> {
		let delta = DScores {
			player_scores: DScores::diff(&new.player.scores, &old.player.scores),
			team_scores: DScores::diff(&new.team.scores, &old.team.scores),
		};

		(!delta.player_scores.is_empty() || !delta.team_scores.is_empty()).then_some(delta)
	}

	pub fn clear_players(&mut self) { self.player_scores.clear(); }
	pub fn clear_teams(&mut self) { self.team_scores.clear(); }

	fn diff<I>(new: &BTreeMap<I, i32>, old: &BTreeMap<I, i32>) -> BTreeMap<I, Option<i32>>
	where
		I: Copy + Ord,
	{
		let mut delta = BTreeMap::new();

		// All scores removed
		let (new_ids, old_ids): (BTreeSet<I>, BTreeSet<I>) = (new.keys().copied().collect(), old.keys().copied().collect());

		let to_remove = old_ids.difference(&new_ids).copied();
		for id in to_remove {
			delta.insert(id, None);
		}

		// All scores added or changed
		for (&id, &score) in new {
			if old.get(&id).copied() != Some(score) {
				delta.insert(id, Some(score));
			}
		}

		delta
	}

	pub fn apply(&self, scores: &mut Scores) {
		DScores::apply_diff(&mut scores.player.scores, &self.player_scores);
		DScores::apply_diff(&mut scores.team.scores, &self.team_scores);
	}

	fn apply_diff<I>(scores: &mut BTreeMap<I, i32>, delta: &BTreeMap<I, Option<i32>>)
	where
		I: Copy + Ord,
	{
		for (&id, delta) in delta {
			if let &Some(score) = delta { scores.insert(id, score); }
			else { scores.remove(&id); }
		}
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn consistent() {
		for i in 0..10000 {
			println!("{i}");
			property(scores(), scores());
		}

		for _ in 0..100 {
			property(scores(), Scores::default());
			property(Scores::default(), scores());
		}

		property(Scores::default(), Scores::default());
	}

	fn scores() -> Scores {
		let (mut player_scores, mut team_scores) = (BTreeMap::new(), BTreeMap::new());
		for id in 0..16 {
			if rand::random::<bool>() {
				player_scores.insert(PlayerId::from(id), (rand::random::<u32>() % 16) as i32 - 8);
			}
			if rand::random::<bool>() {
				team_scores.insert(TeamId::from_index(id as usize), (rand::random::<u32>() % 16) as i32 - 8);
			}
		}

		Scores {
			player: Subscores { scores: player_scores, ordering: ScoreOrdering::default() },
			team: Subscores { scores: team_scores, ordering: ScoreOrdering::default() },
		}
	}

	fn property(new: Scores, mut old: Scores) {
		if let Some(delta) = DScores::new(&new, &old) {
			delta.apply(&mut old);
		}
		assert_eq!(old.player.scores, new.player.scores);
		assert_eq!(old.team.scores, new.team.scores);
	}
}
