deckster 0.2.1

Tui to study flashcards in the terminal
Documentation
use crate::{ sm2::Rating, records::Record };
use tui::widgets::{Block,Borders,Gauge};
use tui::style::{Color,Style};

pub struct Card {
    record: Record,
    is_mastered: bool,
    failed: bool,
}

impl Card {
    fn from(record: Record) -> Self {
        Self {
            record,
            is_mastered: false,
            failed: false,
        }
    }
    fn into_inner(self) -> Record {
        self.record
    }
    fn mastered(&mut self) {
        self.is_mastered = true;
    }
    pub fn review(&mut self,rating: Rating) {
        if let Rating::Again = rating {
            self.failed = true
        }
        else if  self.failed {
            self.record.review(Rating::Again);
            self.is_mastered = true;
        }
        else {
            self.record.review(rating);
            self.is_mastered = true;
        }
    }
}

pub struct Cards {
    cards: Vec<Card>,
    position: usize,
    selected: usize,
}


impl Cards {
    fn cards_left(&self) -> Option<usize> {
        let cards = self.cards.iter().filter(|c| !c.is_mastered).count();
        if cards != 0 { Some(cards) }
        else { None }
    }
    pub fn from(records: Vec<Record>) -> Self {
        let records = sort_records(records);
        let cards = records.into_iter()
            .map(Card::from)
            .collect::<Vec<Card>>();
        Self { cards, position: 0, selected: 0 }
    }
    pub fn select(mut self,n: usize) -> Self {
        self.cards.iter_mut().enumerate()
            .filter(|(i,_)| *i >= n )
            .for_each(|(_,r)| r.mastered());

        if n <= self.cards.len() { self.selected = n }
        else { self.selected = self.cards.len() }

        self
    }
    pub fn records(self) -> Vec<Record> {
        self.cards.into_iter()
            .map(|c| c.into_inner())
            .collect()
    }
    pub fn current_record(&mut self) -> &Record {
        &mut self.cards[self.position].record
    }
    pub fn review_current_card(&mut self,rating: Rating) {
        let card = &mut self.cards[self.position];
        card.review(rating.clone());

        if let Rating::Again = rating {
            let hack = self.cards.remove(self.position);
            self.cards.push(hack);
        }
    }
    pub fn status(&self) -> Gauge {
        let cards = self.cards_left().unwrap();
        let label = cards.to_string();
        let percent = cards * 100;
        let percent = percent / self.selected;
        let block = Block::default()
            .borders(Borders::ALL)
            .title("Cards Left");
        let style = Style::default()
            .fg(Color::Blue)
            .bg(Color::Black);

        Gauge::default()
            .block(block)
            .gauge_style(style)
            .percent(percent as u16)
            .label(label)
    }
}

impl Iterator for Cards {
    type Item = ();
    fn next(&mut self) -> Option<Self::Item> {
        self.cards_left()?;

        let cards = self.cards.iter()
            .enumerate()
            .cycle();

        let mut pos = self.position;
        let last = self.cards.len() - 1;
        for (i,card) in cards {
            if i >= pos && !card.is_mastered { pos = i; break; }
            if i == last { pos = 0 }
        };

        self.position = pos;
        Some(())
    }
}

fn sort_records(records: Vec<Record>) -> Vec<Record> {
    let mut records = records;
    let mut new = Vec::new();
    let mut old = Vec::new();

    for record in records.drain(..) {
        if record.last_date.is_none() { new.push(record) }
        else { old.push(record) }
    };

    old.sort_by_key(|r| r.percent_overdue().unwrap());
    old.reverse();
    new.append(&mut old);
    new
}