rs-fsrs 1.2.1

Rust-based Scheduler for FSRS
Documentation
use chrono::{DateTime, Duration, Utc};

use crate::{Card, ImplScheduler, Parameters, Rating, Scheduler, SchedulingInfo};
use crate::{Rating::*, State::*};

pub struct LongtermScheduler {
    scheduler: Scheduler,
}

impl LongtermScheduler {
    pub fn new(parameters: Parameters, card: Card, now: DateTime<Utc>) -> Self {
        Self {
            scheduler: Scheduler::new(parameters, card, now),
        }
    }

    fn new_state(&mut self, rating: Rating) -> SchedulingInfo {
        if let Some(exist) = self.scheduler.next.get(&rating) {
            return exist.clone();
        }

        let next = self.scheduler.current.clone();
        self.scheduler.current.scheduled_days = 0;
        self.scheduler.current.elapsed_days = 0;

        let mut next_again = next.clone();
        let mut next_hard = next.clone();
        let mut next_good = next.clone();
        let mut next_easy = next;

        self.init_difficulty_stability(
            &mut next_again,
            &mut next_hard,
            &mut next_good,
            &mut next_easy,
        );
        self.next_interval(
            &mut next_again,
            &mut next_hard,
            &mut next_good,
            &mut next_easy,
            0,
        );
        self.next_state(
            &mut next_again,
            &mut next_hard,
            &mut next_good,
            &mut next_easy,
        );
        self.update_next(&next_again, &next_hard, &next_good, &next_easy);

        self.scheduler.next.get(&rating).unwrap().to_owned()
    }

    fn learning_state(&mut self, rating: Rating) -> SchedulingInfo {
        self.review_state(rating)
    }

    fn review_state(&mut self, rating: Rating) -> SchedulingInfo {
        if let Some(exist) = self.scheduler.next.get(&rating) {
            return exist.clone();
        }

        let next = self.scheduler.current.clone();
        let interval = self.scheduler.current.elapsed_days;
        let stability = self.scheduler.last.stability;
        let difficulty = self.scheduler.last.difficulty;
        let retrievability = self.scheduler.last.get_retrievability(self.scheduler.now);

        let mut next_again = next.clone();
        let mut next_hard = next.clone();
        let mut next_good = next.clone();
        let mut next_easy = next;

        self.next_difficulty_stability(
            &mut next_again,
            &mut next_hard,
            &mut next_good,
            &mut next_easy,
            difficulty,
            stability,
            retrievability,
        );
        self.next_interval(
            &mut next_again,
            &mut next_hard,
            &mut next_good,
            &mut next_easy,
            interval,
        );
        self.next_state(
            &mut next_again,
            &mut next_hard,
            &mut next_good,
            &mut next_easy,
        );
        next_again.lapses += 1;

        self.update_next(&next_again, &next_hard, &next_good, &next_easy);
        self.scheduler.next.get(&rating).unwrap().to_owned()
    }

    fn init_difficulty_stability(
        &self,
        next_again: &mut Card,
        next_hard: &mut Card,
        next_good: &mut Card,
        next_easy: &mut Card,
    ) {
        next_again.difficulty = self.scheduler.parameters.init_difficulty(Again);
        next_again.stability = self.scheduler.parameters.init_stability(Again);

        next_hard.difficulty = self.scheduler.parameters.init_difficulty(Hard);
        next_hard.stability = self.scheduler.parameters.init_stability(Hard);

        next_good.difficulty = self.scheduler.parameters.init_difficulty(Good);
        next_good.stability = self.scheduler.parameters.init_stability(Good);

        next_easy.difficulty = self.scheduler.parameters.init_difficulty(Easy);
        next_easy.stability = self.scheduler.parameters.init_stability(Easy);
    }

    #[allow(clippy::too_many_arguments)]
    fn next_difficulty_stability(
        &self,
        next_again: &mut Card,
        next_hard: &mut Card,
        next_good: &mut Card,
        next_easy: &mut Card,
        difficulty: f64,
        stability: f64,
        retrievability: f64,
    ) {
        next_again.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Again);
        next_again.stability =
            self.scheduler
                .parameters
                .next_forget_stability(difficulty, stability, retrievability);

        next_hard.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Hard);
        next_hard.stability = self.scheduler.parameters.next_recall_stability(
            difficulty,
            stability,
            retrievability,
            Hard,
        );

        next_good.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Good);
        next_good.stability = self.scheduler.parameters.next_recall_stability(
            difficulty,
            stability,
            retrievability,
            Good,
        );

        next_easy.difficulty = self.scheduler.parameters.next_difficulty(difficulty, Easy);
        next_easy.stability = self.scheduler.parameters.next_recall_stability(
            difficulty,
            stability,
            retrievability,
            Easy,
        );
    }

    fn next_interval(
        &self,
        next_again: &mut Card,
        next_hard: &mut Card,
        next_good: &mut Card,
        next_easy: &mut Card,
        elapsed_days: i64,
    ) {
        let mut again_interval = self
            .scheduler
            .parameters
            .next_interval(next_again.stability, elapsed_days);
        let mut hard_interval = self
            .scheduler
            .parameters
            .next_interval(next_hard.stability, elapsed_days);
        let mut good_interval = self
            .scheduler
            .parameters
            .next_interval(next_good.stability, elapsed_days);
        let mut easy_interval = self
            .scheduler
            .parameters
            .next_interval(next_easy.stability, elapsed_days);

        again_interval = again_interval.min(hard_interval);
        hard_interval = hard_interval.max(again_interval + 1.0);
        good_interval = good_interval.max(hard_interval + 1.0);
        easy_interval = easy_interval.max(good_interval + 1.0);

        next_again.scheduled_days = again_interval as i64;
        next_again.due = self.scheduler.now + Duration::days(again_interval as i64);

        next_hard.scheduled_days = hard_interval as i64;
        next_hard.due = self.scheduler.now + Duration::days(hard_interval as i64);

        next_good.scheduled_days = good_interval as i64;
        next_good.due = self.scheduler.now + Duration::days(good_interval as i64);

        next_easy.scheduled_days = easy_interval as i64;
        next_easy.due = self.scheduler.now + Duration::days(easy_interval as i64);
    }

    fn next_state(
        &self,
        next_again: &mut Card,
        next_hard: &mut Card,
        next_good: &mut Card,
        next_easy: &mut Card,
    ) {
        next_again.state = Review;
        next_hard.state = Review;
        next_good.state = Review;
        next_easy.state = Review;
    }

    fn update_next(
        &mut self,
        next_again: &Card,
        next_hard: &Card,
        next_good: &Card,
        next_easy: &Card,
    ) {
        let item_again = SchedulingInfo {
            card: next_again.clone(),
            review_log: self.scheduler.build_log(Again),
        };
        let item_hard = SchedulingInfo {
            card: next_hard.clone(),
            review_log: self.scheduler.build_log(Hard),
        };
        let item_good = SchedulingInfo {
            card: next_good.clone(),
            review_log: self.scheduler.build_log(Good),
        };
        let item_easy = SchedulingInfo {
            card: next_easy.clone(),
            review_log: self.scheduler.build_log(Easy),
        };

        self.scheduler.next.insert(Again, item_again);
        self.scheduler.next.insert(Hard, item_hard);
        self.scheduler.next.insert(Good, item_good);
        self.scheduler.next.insert(Easy, item_easy);
    }
}

impl ImplScheduler for LongtermScheduler {
    fn review(&mut self, rating: Rating) -> SchedulingInfo {
        match self.scheduler.last.state {
            New => self.new_state(rating),
            Learning | Relearning => self.learning_state(rating),
            Review => self.review_state(rating),
        }
    }
}