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

use serde::Deserialize;

use super::{BTreeSet, BTreeMap};

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

/**
 * An enum for the many different types of conditions that can be used for the
 * win or elimination conditions.
 *
 * Note that "ids" in this context refers to either player ids or team ids.
 */
#[derive(Default, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum Condition<I> {
	/**
	 * Finds all ids whose scores satisfy the given condition, returning
	 * Some(ids) if ids isn't empty. Otherwise returns None.
	 */
	FirstTo(Comparison<i32>),

	/**
	 * Finds all ids whose scores satisfy the given condition.
	 *
	 * If there are multiple ids with that condition, return None. If there is
	 * just one id with that condition, return Some(id). If there are no ids with
	 * that condition, return Some(prev_ids), where `prev_ids` is the previous
	 * set of ids satisfying this condition (this set is initialised to be
	 * empty).
	 */
	LastWith(Comparison<i32>, #[serde(skip)] BTreeSet<I>),

	/**
	 * Finds the ids with the maximum or minimum score.
	 *
	 * If there are no ids in the game, returns None.
	 *
	 * If `exclusive` is set to true and there are multiple ids with the same
	 * extreme score, returns None.
	 */
	Extremum {
		#[serde(rename = "type")] typ: ExtremumType,
		exclusive: bool,
	},

	/**
	 * Returns Some(all ids) once the condition with the timer's time (in
	 * seconds) has been met, otherwise returning None.
	 *
	 * Note that if the timer has a limit to prevent it being displayed above or
	 * below a certain value, the time clamped with that limit will be used.
	 *
	 * If the timer is None, this will always return None.
	 */
	Timer(Comparison<f64>),

	/**
	 * Combines multiple conditions into a single condition.
	 *
	 * This first calls `achieved` on all child conditions and if the number of
	 * achieved conditions is greater than or equal to the necessary count, these
	 * sets of achieved ids are reduced (through union or intersection) and
	 * Some(reduced set) is returned. Otherwise returns None if not enough
	 * conditions are met.
	 *
	 * See the `And` and `Or` variants for special cases of this.
	 */
	Multiple {
		conds: Vec<Condition<I>>,
		necessary: NonZeroUsize,
		reduction: SetReduction,
	},

	/**
	 * Returns a set of ids if all conditions are achieved.
	 *
	 * The intersection of all these sets is returned.
	 *
	 * This is identical to Multiple { conds, necessary:
	 * NonZeroUsize::new(conds.len())?, reduction: SetReduction::Intersection }.
	 *
	 * Always None if there are no conditions.
	 */
	And(Vec<Condition<I>>),

	/**
	 * Returns a set of ids if at least one condition is achieved.
	 *
	 * The union of all these sets is returned.
	 *
	 * This is identical to Multiple { conds, necessary: 1, reduction:
	 * SetReduction::Union }.
	 *
	 * Always None if there are no conditions.
	 */
	Or(Vec<Condition<I>>),

	/**
	 * Always returns Some(all ids).
	 */
	SomeAll,

	/**
	 * Always returns Some(empty set).
	 */
	SomeEmpty,

	/**
	 * The game never ends, always returns None.
	 */
	#[default] None,
}

fn filter_ids<I>(scores: &BTreeMap<I, i32>, cond: &Comparison<i32>) -> BTreeSet<I>
where
	I: Copy + Ord,
{
	scores.iter().filter_map(|(&id, &score)| cond.test(score).then_some(id)).collect()
}

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum ExtremumType {
	Minimum,
	Maximum,
}

#[derive(Clone, Copy, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum SetReduction {
	Union,
	Intersection,
}

impl<I> Condition<I> where I: Copy + Ord {
	/**
	 * Clears the state of the `LastWith` condition.
	 */
	pub fn reset(&mut self) {
		match self {
			Condition::LastWith(_, prev_with_cond) => prev_with_cond.clear(),
			Condition::Multiple { conds, .. } | Condition::And(conds) | Condition::Or(conds) => conds.iter_mut().for_each(Condition::reset),
			_ => (),
		}
	}

	/**
	 * Returns a set of ids which are satisfying the condition.
	 *
	 * This is used in the context of both the win and elimination conditions.
	 * For the win condition, Some(empty set) means that the game ends with no
	 * winners while None means that the game is still going. For elimination,
	 * Some(empty set) and None are the same.
	 */
	pub fn satisfying(&mut self, scores: &BTreeMap<I, i32>, time: Option<f64>) -> Option<BTreeSet<I>> {
		match self {
			Condition::FirstTo(cond) => {
				let achieved = filter_ids(scores, cond);
				(!achieved.is_empty()).then_some(achieved)
			},
			Condition::LastWith(cond, prev_with_cond) => {
				let with_cond = filter_ids(scores, cond);

				let ret = match with_cond.len() {
					1 => Some(with_cond.clone()),
					0 => Some(prev_with_cond.clone()),
					_ => None,
				};

				if !with_cond.is_empty() {
					*prev_with_cond = with_cond;
				}

				ret
			}
			Condition::Extremum { typ, exclusive } => {
				let Some(extremum) = match typ {
					ExtremumType::Minimum => scores.values().min(),
					ExtremumType::Maximum => scores.values().max(),
				}.copied() else {
					return None; // If no ids
				};

				let achieved: BTreeSet<I> = scores.iter().filter_map(|(&id, &score)| (score == extremum).then_some(id)).collect();
				debug_assert!(!achieved.is_empty());

				if *exclusive && achieved.len() > 1 {
					return None;
				}

				Some(achieved)
			},
			Condition::Timer(cond) => time.and_then(|time| cond.test(time).then(|| scores.keys().copied().collect())),
			Condition::Multiple { conds, necessary, reduction } => Condition::multiple_conds(scores, time, conds, *necessary, *reduction),
			Condition::And(conds) => {
				let len = NonZeroUsize::new(conds.len())?;
				Condition::multiple_conds(scores, time, conds, len, SetReduction::Intersection)
			},
			Condition::Or(conds) => Condition::multiple_conds(scores, time, conds, const { NonZeroUsize::new(1).unwrap() }, SetReduction::Union),
			Condition::SomeAll => Some(scores.keys().copied().collect()),
			Condition::SomeEmpty => Some(BTreeSet::new()),
			Condition::None => None,
		}
	}

	fn multiple_conds(scores: &BTreeMap<I, i32>, time: Option<f64>, conds: &mut [Condition<I>], necessary: NonZeroUsize, reduction: SetReduction) -> Option<BTreeSet<I>> {
		let achieved_sets = conds.iter_mut().filter_map(|cond| cond.satisfying(scores, time)).collect::<Vec<_>>();

		match reduction {
			_ if achieved_sets.len() < necessary.get() => None,
			SetReduction::Union => achieved_sets.into_iter().reduce(|a, b| a.union(&b).copied().collect()),
			SetReduction::Intersection => achieved_sets.into_iter().reduce(|a, b| a.intersection(&b).copied().collect()),
		}
	}
}

#[derive(Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum AnyCondition {
	Player(Condition<PlayerId>),
	Team(Condition<TeamId>),
}

impl Default for AnyCondition {
	fn default() -> AnyCondition {
		AnyCondition::Player(Condition::default())
	}
}

impl AnyCondition {
	pub fn reset(&mut self) {
		match self {
			AnyCondition::Player(cond) => cond.reset(),
			AnyCondition::Team(cond) => cond.reset(),
		}
	}
}
