use rayon::prelude::*;

use crate::game::Info;

use super::{Game, InfoKind};

pub struct Simulation {
	target: String,
}

impl Simulation {
	pub fn new(target: impl ToString) -> Self {
		let target = target.to_string();
		Self { target }
	}
}

impl Game for Simulation {
	fn guess(&mut self, word: &str) -> Result<(), Vec<Info>> {
		if word == self.target {
			Ok(())
		} else {
			Err(wordle(&self.target, word))
		}
	}

	fn length(&mut self) -> usize {
		self.target.len()
	}
}

pub fn wordle(target: &str, word: &str) -> Vec<Info> {
	let mut buffs = wordle_buffs_for(target);
	wordle_inner(target, word, &mut buffs);
	buffs.2
}

pub fn wordle_buffs_for(target: &str) -> (Vec<Option<InfoKind>>, Vec<Option<char>>, Vec<Info>) {
	let infos = target.chars().map(|_| None).collect();
	let letters = target.chars().map(Some).collect();
	let result = target.chars().map(|_| Info::empty()).collect();
	(infos, letters, result)
}

#[inline]
pub fn wordle_inner(target: &str, word: &str, buffs: &mut (Vec<Option<InfoKind>>, Vec<Option<char>>, Vec<Info>)) {
	#[inline]
	fn to_info(letter: &mut Option<char>, info: &mut Option<InfoKind>, kind: InfoKind) {
		*letter = None;
		info.insert(kind);
	}

	let (infos, letters, result) = buffs;
	let d = drop;
	infos.iter_mut().for_each(|i| d(i.take()));
	let d = drop;
	letters.iter_mut().zip(target.chars()).for_each(|(l, c)| d(l.insert(c)));

	word.chars()
		.zip(letters.iter_mut())
		.zip(infos.iter_mut())
		.filter_map(|((w, l), i)| l.is_some_and(|t| w == t).then_some((l, i)))
		.for_each(|(r, i)| to_info(r, i, InfoKind::There));

	word.chars()
		.zip(infos.iter_mut())
		.filter(|(r, i)| i.is_none())
		.for_each(|(l, i)| {
			let r = letters.iter_mut().find(|e| e.as_ref().is_some_and(|e| *e == l));
			if let Some(r) = r {
				to_info(r, i, InfoKind::Elsewhere);
			}
		});

	infos
		.iter()
		.map(|i| i.unwrap_or(InfoKind::Abscent))
		.zip(word.chars())
		.map(|(i, c)| Info::new(i, c))
		.zip(result.iter_mut())
		.for_each(|(i, r)| *r = i);
}
