use crate::{
dummy_storage::{DummyStorage, DynStorage},
item::{Fact, Item, ItemGuard},
storage::Storage,
};
use rand::{thread_rng, Rng};
use std::{
collections::HashMap,
marker::PhantomData,
time::{Duration, SystemTime},
};
pub type SeeAgainGaps = HashMap<u32, Duration>;
pub type AnkiDB = Vec<Item>;
mod private_trait {
pub trait CannotExternallyImplement {}
}
pub trait AnkiCardReturnType: private_trait::CannotExternallyImplement {}
pub struct GiveItemGuards;
pub struct GiveFacts;
impl private_trait::CannotExternallyImplement for GiveItemGuards {}
impl private_trait::CannotExternallyImplement for GiveFacts {}
impl AnkiCardReturnType for GiveItemGuards {}
impl AnkiCardReturnType for GiveFacts {}
#[must_use]
pub fn default_sag() -> SeeAgainGaps {
let mut hm = HashMap::new();
for i in 1..11 {
hm.insert(i, Duration::from_secs(u64::from(i) * 30));
}
hm
}
pub struct AnkiGame<S: Storage, T: AnkiCardReturnType> {
v: AnkiDB,
pub(crate) storage: S,
sag: SeeAgainGaps,
current: Option<(usize, bool)>,
_pd: PhantomData<T>,
}
impl<S: Storage, T: AnkiCardReturnType> AnkiGame<S, T> {
pub fn new(storage: S, sat: SeeAgainGaps) -> Result<Self, S::ErrorType> {
Ok(Self {
v: storage.read_db()?,
storage,
sag: sat,
current: None,
_pd: PhantomData,
})
}
pub fn add_card(&mut self, f: Fact) {
self.v.push(f.into());
self.storage.write_db(&self.v).unwrap();
}
#[must_use]
pub fn get_elgible(&self) -> Vec<Fact> {
let indicies = get_eligible(&self.v, &self.sag);
indicies
.into_iter()
.map(|index| &self.v[index].fact)
.cloned()
.collect()
}
#[must_use]
pub fn get_eligible_no(&self) -> usize {
get_eligible(&self.v, &self.sag).len()
}
#[must_use]
pub fn get_all_facts(&self) -> Vec<Fact> {
self.v.clone().into_iter().map(|i| i.fact).collect()
}
pub fn write_to_db(&mut self) -> Result<(), S::ErrorType> {
self.storage.write_db(&self.v)
}
fn get_an_index(&self) -> Option<(usize, bool)> {
let eligible = get_eligible(&self.v, &self.sag);
if eligible.is_empty() {
if self.v.is_empty() {
None
} else {
Some((thread_rng().gen_range(0..self.v.len()), false))
}
} else {
Some((eligible[thread_rng().gen_range(0..eligible.len())], true))
}
}
}
impl<S: Storage> AnkiGame<S, GiveItemGuards> {
pub fn get_item_guard(&mut self) -> Option<(ItemGuard<S>, bool)> {
if let Some((index, was_e)) = self.get_an_index() {
Some((ItemGuard::new(&mut self.v, index, &mut self.storage), was_e))
} else {
None
}
}
}
impl<S: Storage> AnkiGame<S, GiveFacts> {
pub fn get_fact(&mut self) -> Option<(Fact, bool)> {
if let Some((cu, was_e)) = self.current {
Some((self.v[cu].fact.clone(), was_e))
} else if self.v.is_empty() {
None
} else {
self.set_new_fact();
self.get_fact()
}
}
pub fn get_new_fact(&mut self) -> Option<(Fact, bool)> {
self.set_new_fact();
self.get_fact()
}
pub fn set_new_fact(&mut self) {
if let Some((index, we)) = self.get_an_index() {
self.current = Some((index, we));
}
}
pub fn finish_current_fact(&mut self, correct: bool) {
if let Some((cu, _)) = self.current {
self.v[cu].history.push(correct);
self.v[cu].last_tested = Some(SystemTime::now());
self.storage
.write_db(&self.v)
.expect("unable to write to db");
}
self.current = None;
}
}
impl<E: std::fmt::Debug, T: AnkiCardReturnType> DynStorage<E> for AnkiGame<DummyStorage, T> {
fn read_custom(&mut self, s: &dyn Storage<ErrorType = E>) -> Result<(), E> {
self.v = s.read_db()?;
Ok(())
}
fn write_custom(&mut self, s: &mut dyn Storage<ErrorType = E>) -> Result<(), E> {
s.write_db(&self.v)
}
fn exit_custom(&mut self, s: &mut dyn Storage<ErrorType = E>) {
s.exit_application();
}
}
#[must_use]
pub fn get_eligible(items: &[Item], sag: &SeeAgainGaps) -> Vec<usize> {
items
.iter()
.enumerate()
.filter_map(|(index, item)| {
item.time_since_last_test()
.map_or(Some(index), |last_seen| {
sag.get(&item.get_streak()).map_or(Some(index), |distance| {
if &last_seen > distance {
Some(index)
} else {
None
}
})
})
})
.collect()
}