finn 0.1.0

An Ikea shark companion for your Rust project
Documentation
use core::time::Duration;

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum WaniKaniStage {
    Apprentice1,
    Apprentice2,
    Apprentice3,
    Apprentice4,
    Guru1,
    Guru2,
    Master,
    Enlightened,
    Burned,
}

impl WaniKaniStage {
    pub fn srs_penalty_factor(&self) -> u32 {
        use WaniKaniStage::*;
        match self {
            Apprentice1 |
            Apprentice2 |
            Apprentice3 |
            Apprentice4 => 1,
            _ => 2,
        }
    }

    /// For an item which reaches this stage, its review is scheduled this duration in the future.
    pub fn interval(&self) -> Duration {
        const HOUR: Duration = Duration::from_secs(60*60);
        const DAY: Duration = Duration::from_secs(60*60*24);
        const MONTH: Duration = Duration::from_secs(60*60*24*30);

        match self {
            WaniKaniStage::Apprentice1 => 4 * HOUR,
            WaniKaniStage::Apprentice2 => 8 * HOUR,
            WaniKaniStage::Apprentice3 => DAY,
            WaniKaniStage::Apprentice4 => 2 * DAY,
            WaniKaniStage::Guru1 => 7 * DAY,
            WaniKaniStage::Guru2 => 14 * DAY,
            WaniKaniStage::Master => MONTH,
            WaniKaniStage::Enlightened => 4 * MONTH,
            WaniKaniStage::Burned => Duration::MAX,
        }
    }

    /// SRS stage number to variant
    pub fn from_stage_num(stage: u32) -> WaniKaniStage {
        match stage {
            0 => WaniKaniStage::Apprentice1,
            1 => WaniKaniStage::Apprentice2,
            2 => WaniKaniStage::Apprentice3,
            3 => WaniKaniStage::Apprentice4,
            4 => WaniKaniStage::Guru1,
            5 => WaniKaniStage::Guru2,
            6 => WaniKaniStage::Master,
            7 => WaniKaniStage::Enlightened,
            _ => WaniKaniStage::Burned,
        }
    }

    /// Corresponding number for the srs stage.
    pub fn stage_num(&self) -> u32 {
        match self {
            WaniKaniStage::Apprentice1 => 0,
            WaniKaniStage::Apprentice2 => 1,
            WaniKaniStage::Apprentice3 => 2,
            WaniKaniStage::Apprentice4 => 3,
            WaniKaniStage::Guru1 => 4,
            WaniKaniStage::Guru2 => 5,
            WaniKaniStage::Master => 6,
            WaniKaniStage::Enlightened => 7,
            WaniKaniStage::Burned => 8,
        }
    }

    /// New stage after difference is added
    pub fn after_adjustment(&self, amt: i32) -> WaniKaniStage {
        let new_stage_num = ((self.stage_num() as i32) + amt).max(0) as u32;
        WaniKaniStage::from_stage_num(new_stage_num)
    }

}


#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct WanikaniItem {
    /// SRS stage
    pub stage: WaniKaniStage,
    /// Number of times in a row the item has been answered incorrectly.
    pub incorrect_streak: u32,
}

impl Default for WanikaniItem {
    fn default() -> Self {
        WanikaniItem {
            stage: WaniKaniStage::Apprentice1,
            incorrect_streak: 0
        }
    }
}

impl WanikaniItem {
    pub fn new(stage: WaniKaniStage, incorrect_streak: u32) -> Self {
        WanikaniItem {
            stage,
            incorrect_streak,
        }
    }

    pub fn review(&self, correct: bool) -> WanikaniItem {
        wanikani_review(correct, &self)
    }

}

/// Resulting WaniKani item after a correct or incorrect review of an item.
pub fn wanikani_review(correct: bool, item: &WanikaniItem) -> WanikaniItem {
    if correct {
        WanikaniItem {
            stage: item.stage.after_adjustment(1),
            incorrect_streak: 0,
        }
    } else {
        let incorrect_streak = item.incorrect_streak + 1;
        let stage_change = 0 - (incorrect_streak * item.stage.srs_penalty_factor()) as i32;

        WanikaniItem {
            stage: item.stage.after_adjustment(stage_change),
            incorrect_streak,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test1() {
        assert_eq!(
            WanikaniItem::default().review(true),
            WanikaniItem::new(WaniKaniStage::Apprentice2, 0),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Apprentice2, 0).review(true),
            WanikaniItem::new(WaniKaniStage::Apprentice3, 0),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Apprentice3, 0).review(true),
            WanikaniItem::new(WaniKaniStage::Apprentice4, 0),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Apprentice4, 0).review(true),
            WanikaniItem::new(WaniKaniStage::Guru1, 0),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Guru1, 0).review(true),
            WanikaniItem::new(WaniKaniStage::Guru2, 0),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Guru2, 0).review(true),
            WanikaniItem::new(WaniKaniStage::Master, 0),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Master, 0).review(true),
            WanikaniItem::new(WaniKaniStage::Enlightened, 0),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Enlightened, 0).review(true),
            WanikaniItem::new(WaniKaniStage::Burned, 0),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Burned, 0).review(true),
            WanikaniItem::new(WaniKaniStage::Burned, 0),
        );
    }

    #[test]
    fn test2() {
        assert_eq!(
            WanikaniItem::default().review(false),
            WanikaniItem::new(WaniKaniStage::Apprentice1, 1),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Apprentice2, 0).review(false),
            WanikaniItem::new(WaniKaniStage::Apprentice1, 1),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Apprentice3, 0).review(false),
            WanikaniItem::new(WaniKaniStage::Apprentice2, 1),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Apprentice4, 0).review(false),
            WanikaniItem::new(WaniKaniStage::Apprentice3, 1),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Guru1, 0).review(false),
            WanikaniItem::new(WaniKaniStage::Apprentice3, 1),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Guru2, 0).review(false),
            WanikaniItem::new(WaniKaniStage::Apprentice4, 1),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Master, 0).review(false),
            WanikaniItem::new(WaniKaniStage::Guru1, 1),
        );

        assert_eq!(
            WanikaniItem::new(WaniKaniStage::Enlightened, 0).review(false),
            WanikaniItem::new(WaniKaniStage::Guru2, 1),
        );
    }


}