// Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use std::{collections::HashMap, iter::Peekable}; use id_tree::{InsertBehavior, Node, NodeId, Tree}; use super::{Deck, NormalDeck}; use crate::{ deckconfig::{DeckConfig, DeckConfigId}, pb::decks::deck::normal::DayLimit, prelude::*, }; impl NormalDeck { /// The deck's review limit for today, or its regular one, if any is configured. pub fn current_review_limit(&self, today: u32) -> Option { self.review_limit_today(today).or(self.review_limit) } /// The deck's new limit for today, or its regular one, if any is configured. pub fn current_new_limit(&self, today: u32) -> Option { self.new_limit_today(today).or(self.new_limit) } /// The deck's review limit for today. pub fn review_limit_today(&self, today: u32) -> Option { self.review_limit_today .and_then(|day_limit| day_limit.limit(today)) } /// The deck's new limit for today. pub fn new_limit_today(&self, today: u32) -> Option { self.new_limit_today .and_then(|day_limit| day_limit.limit(today)) } } impl DayLimit { pub fn limit(&self, today: u32) -> Option { (self.today == today).then_some(self.limit) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) struct RemainingLimits { pub review: u32, pub new: u32, } impl RemainingLimits { pub(crate) fn new(deck: &Deck, config: Option<&DeckConfig>, today: u32, v3: bool) -> Self { if let Ok(normal) = deck.normal() { if let Some(config) = config { return Self::new_for_normal_deck(deck, today, v3, normal, config); } } Self::default() } fn new_for_normal_deck( deck: &Deck, today: u32, v3: bool, normal: &NormalDeck, config: &DeckConfig, ) -> RemainingLimits { let review_limit = normal .current_review_limit(today) .unwrap_or(config.inner.reviews_per_day); let new_limit = normal .current_new_limit(today) .unwrap_or(config.inner.new_per_day); let (new_today, mut rev_today) = deck.new_rev_counts(today); if v3 { // any reviewed new cards contribute to the review limit rev_today += new_today; } Self { review: (review_limit as i32 - rev_today).max(0) as u32, new: (new_limit as i32 - new_today).max(0) as u32, } } pub(crate) fn cap_to(&mut self, limits: RemainingLimits) { self.review = self.review.min(limits.review); self.new = self.new.min(limits.new); } } impl Default for RemainingLimits { fn default() -> Self { RemainingLimits { review: 9999, new: 9999, } } } pub(crate) fn remaining_limits_map<'a>( decks: impl Iterator, config: &'a HashMap, today: u32, v3: bool, ) -> HashMap { decks .map(|deck| { ( deck.id, RemainingLimits::new( deck, deck.config_id().and_then(|id| config.get(&id)), today, v3, ), ) }) .collect() } /// Wrapper of [RemainingLimits] with some additional meta data. #[derive(Debug, Clone, Copy)] struct NodeLimits { deck_id: DeckId, /// absolute level in the deck hierarchy level: usize, limits: RemainingLimits, } impl NodeLimits { fn new(deck: &Deck, config: &HashMap, today: u32) -> Self { Self { deck_id: deck.id, level: deck.name.components().count(), limits: RemainingLimits::new( deck, deck.config_id().and_then(|id| config.get(&id)), today, true, ), } } } #[derive(Debug, Clone)] pub(crate) struct LimitTreeMap { /// A tree representing the remaining limits of the active deck hierarchy. // // As long as we never (1) allow a tree without a root, (2) remove nodes, // and (3) have more than 1 tree, it's safe to unwrap on Tree::get() and // Tree::root_node_id(), even if we clone Nodes. tree: Tree, /// A map to access the tree node of a deck. Only decks with a remaining /// limit above zero are included. map: HashMap, } impl LimitTreeMap { /// Child [Deck]s must be sorted by name. pub(crate) fn build( root_deck: &Deck, child_decks: Vec, config: &HashMap, today: u32, ) -> Self { let root_limits = NodeLimits::new(root_deck, config, today); let mut tree = Tree::new(); let root_id = tree .insert(Node::new(root_limits), InsertBehavior::AsRoot) .unwrap(); let mut map = HashMap::new(); if root_limits.limits.review > 0 { map.insert(root_deck.id, root_id.clone()); } let mut limits = Self { tree, map }; let mut remaining_decks = child_decks.into_iter().peekable(); limits.add_child_nodes(root_id, &mut remaining_decks, config, today); limits } /// Recursively appends descendants to the provided parent [Node], and adds /// them to the [HashMap]. /// Given [Deck]s are assumed to arrive in depth-first order. /// The tree-from-deck-list logic is taken from [crate::decks::tree::add_child_nodes]. fn add_child_nodes( &mut self, parent_node_id: NodeId, remaining_decks: &mut Peekable>, config: &HashMap, today: u32, ) { let parent = *self.tree.get(&parent_node_id).unwrap().data(); while let Some(deck) = remaining_decks.peek() { match deck.name.components().count() { l if l <= parent.level => { // next item is at a higher level break; } l if l == parent.level + 1 => { // next item is an immediate descendent of parent self.insert_child_node(deck, parent_node_id.clone(), config, today); remaining_decks.next(); } _ => { // next item is at a lower level if let Some(last_child_node_id) = self .tree .get(&parent_node_id) .unwrap() .children() .last() .cloned() { self.add_child_nodes(last_child_node_id, remaining_decks, config, today) } else { // immediate parent is missing, skip the deck until a DB check is run remaining_decks.next(); } } } } } fn insert_child_node( &mut self, child_deck: &Deck, parent_node_id: NodeId, config: &HashMap, today: u32, ) { let mut child_limits = NodeLimits::new(child_deck, config, today); child_limits .limits .cap_to(self.tree.get(&parent_node_id).unwrap().data().limits); let child_node_id = self .tree .insert( Node::new(child_limits), InsertBehavior::UnderNode(&parent_node_id), ) .unwrap(); if child_limits.limits.review > 0 { self.map.insert(child_deck.id, child_node_id); } } pub(crate) fn root_limit_reached(&self) -> bool { self.map.is_empty() } pub(crate) fn limit_reached(&self, deck_id: DeckId) -> bool { self.map.get(&deck_id).is_none() } pub(crate) fn active_decks(&self) -> Vec { self.tree .traverse_pre_order(self.tree.root_node_id().unwrap()) .unwrap() .map(|node| node.data().deck_id) .collect() } pub(crate) fn remaining_node_id(&self, deck_id: DeckId) -> Option { self.map.get(&deck_id).map(Clone::clone) } pub(crate) fn decrement_node_and_parent_limits(&mut self, node_id: &NodeId, new: bool) { let node = self.tree.get_mut(node_id).unwrap(); let parent = node.parent().cloned(); let limit = &mut node.data_mut().limits; if if new { limit.new = limit.new.saturating_sub(1); limit.new } else { limit.review = limit.review.saturating_sub(1); limit.review } == 0 { self.remove_node_and_descendants_from_map(node_id); }; if let Some(parent_id) = parent { self.decrement_node_and_parent_limits(&parent_id, new) } } pub(crate) fn remove_node_and_descendants_from_map(&mut self, node_id: &NodeId) { let node = self.tree.get(node_id).unwrap(); self.map.remove(&node.data().deck_id); for child_id in node.children().clone() { self.remove_node_and_descendants_from_map(&child_id); } } pub(crate) fn cap_new_to_review(&mut self) { self.cap_new_to_review_rec(&self.tree.root_node_id().unwrap().clone(), 9999); } fn cap_new_to_review_rec(&mut self, node_id: &NodeId, parent_limit: u32) { let node = self.tree.get_mut(node_id).unwrap(); let mut limits = &mut node.data_mut().limits; limits.new = limits.new.min(limits.review).min(parent_limit); // clone because of borrowing rules let node_limit = limits.new; let children = node.children().clone(); if node_limit == 0 { self.remove_node_and_descendants_from_map(node_id); } for child_id in children { self.cap_new_to_review_rec(&child_id, node_limit); } } }