pub struct Requirement<F, A, V> {
	_arg: std::marker::PhantomData<(A, V)>,
	finalized: bool,
	func: F,
	default: V,
	possible_values: std::collections::HashSet<V>,
}

impl<
	A,
	V: Clone + std::cmp::Eq + std::hash::Hash,
	F: Fn(&A) -> V,
> Requirement<F, A, V> {
	pub fn new(
		func: F,
		init: &A,
	) -> Self {
		let default = func(init);
		Requirement {
			_arg: std::marker::PhantomData,
			finalized: false,
			func,
			default,
			possible_values: Default::default(),
		}
	}

	/// Add a definite match.
	///
	/// Returns: true iff this decided the value.
	pub fn add_definite_match(&mut self, v: &Option<A>) -> bool {
		if self.finalized { return false }
		let Some(v) = v else { return false };
		self.possible_values.insert((self.func)(v));
		self.finalized = true;
		true
	}

	pub fn add_possible_match(&mut self, v: &Option<A>) {
		if self.finalized { return }
		let Some(v) = v else { return };
		self.possible_values.insert((self.func)(v));
	}

	// Assuming we have seen all the rules if there is a single possible value return it.
	pub fn get_definite(&self) -> Option<V> {
		match self.possible_values.len() {
			0 => Some(self.default.clone()),
			1 => {
				let v = self.possible_values.iter().next().unwrap();
				if self.finalized {
					Some(v.clone())
				} else {
					if *v == self.default {
						Some(self.default.clone())
					} else {
						None
					}
				}
			}
			_ => None,
		}
	}

	// If the rule has been decided and is a single value return that value.
	pub fn get_finalized(&self) -> Option<V> {
		if !self.finalized { return None }
		if self.possible_values.len() > 1 { return None }

		Some(self.possible_values.iter().next()
			.unwrap_or(&self.default)
			.clone())
	}

	pub fn is_possible(&self, v: V) -> bool {
		(!self.finalized && self.default == v)
		|| self.possible_values.contains(&v)
	}

	pub fn is_relevant(&self, v: &Option<A>) -> bool {
		!self.finalized && v.is_some()
	}

	pub fn reset(&mut self) {
		let Self {
			_arg,
			ref mut finalized,
			func: _,
			default: _,
			ref mut possible_values,
		} = *self;
		*finalized = false;
		possible_values.clear();
	}
}

impl<F, A, V: std::fmt::Debug> std::fmt::Debug for Requirement<F, A, V> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		let Self {
			_arg,
			default,
			finalized,
			func: _,
			possible_values,
		} = self;

		f.debug_struct("Requirement")
			.field("finalized", &finalized)
			.field("default", default)
			.field("possible_values", &possible_values)
			.finish()
	}
}
