use std::ops::Not;

use crate::solve::Solver;

pub mod proxy;
pub mod simulation;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InfoKind {
	There,
	Elsewhere,
	Abscent,
}

impl InfoKind {
	pub fn to_printable(self) -> char {
		match self {
			InfoKind::Abscent => '.',
			InfoKind::Elsewhere => '-',
			InfoKind::There => '+',
		}
	}
	pub fn from_printable(printable: char) -> Option<Self> {
		Some(match printable {
			'.' => InfoKind::Abscent,
			'-' => InfoKind::Elsewhere,
			'+' => InfoKind::There,
			_ => None?,
		})
	}
}

#[derive(Debug, Clone, Copy)]
pub struct Info {
	pub kind: InfoKind,
	pub character: char,
}

impl Info {
	pub fn new(kind: InfoKind, character: char) -> Self {
		Self { kind, character }
	}

	pub fn empty() -> Self {
		Self::new(InfoKind::Abscent, '#')
	}

	pub fn as_printable(infos: impl Iterator<Item = Self>) -> Vec<char> {
		infos.map(|i| i.kind.to_printable()).collect()
	}

	pub fn from_printable(
		printables: impl Iterator<Item = char> + Clone,
		characters: impl Iterator<Item = char>,
	) -> Option<Vec<Self>> {
		let result = printables.map(InfoKind::from_printable);
		let success = result.clone().any(|o| o.is_none()).not();
		success.then(|| result.zip(characters).map(|(r, c)| Self::new(r.unwrap(), c)).collect())
	}

	pub fn from_init(characters: impl Iterator<Item = char>) -> Vec<Self> {
		characters
			.map(|c| match c {
				'.' => Self::new(InfoKind::Abscent, '#'),
				_ => Self::new(InfoKind::There, c),
			})
			.collect()
	}

	pub fn to_word(infos: impl Iterator<Item = Self>) -> String {
		infos.map(|i| i.character).collect()
	}
}

pub trait Game {
	fn length(&mut self) -> usize;
	fn guess(&mut self, word: &str) -> Result<(), Vec<Info>>;

	fn play_all(&mut self, mut solver: impl Solver, mut limit: Option<usize>) -> Result<(String, usize), String> {
		for try_ in 0.. {
			if let Some(limit) = limit {
				if limit == try_ {
					return Err("Reached limit.".into());
				}
			}
			let guess = solver.guess();
			match guess {
				None => return Err("".into()),
				Some(guess) => match self.guess(dbg!(&guess)) {
					Ok(()) => return Ok((guess, try_)),
					Err(infos) => {
						let printable = Info::as_printable(infos.iter().cloned())
							.into_iter()
							.collect::<String>();
						dbg!(printable);
						solver.learn(infos);
					}
				},
			}
		}
		unreachable!()
	}
}
