// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

use std::collections::HashMap;
use std::iter::Peekable;

use anki_proto::decks::deck::normal::DayLimit;
use id_tree::InsertBehavior;
use id_tree::Node;
use id_tree::NodeId;
use id_tree::Tree;

use super::Deck;
use super::NormalDeck;
use crate::deckconfig::DeckConfig;
use crate::deckconfig::DeckConfigId;
use crate::prelude::*;

#[derive(Debug, Clone, Copy)]
pub(crate) enum LimitKind {
    Review,
    New,
}

/// The deck's review limit for today, or its regular one, if any is
/// configured.
pub fn current_review_limit(deck: &NormalDeck, today: u32) -> Option<u32> {
    review_limit_today(deck, today).or(deck.review_limit)
}

/// The deck's new limit for today, or its regular one, if any is
/// configured.
pub fn current_new_limit(deck: &NormalDeck, today: u32) -> Option<u32> {
    new_limit_today(deck, today).or(deck.new_limit)
}

/// The deck's review limit for today.
pub fn review_limit_today(deck: &NormalDeck, today: u32) -> Option<u32> {
    deck.review_limit_today
        .and_then(|day_limit| limit_if_today(day_limit, today))
}

/// The deck's new limit for today.
pub fn new_limit_today(deck: &NormalDeck, today: u32) -> Option<u32> {
    deck.new_limit_today
        .and_then(|day_limit| limit_if_today(day_limit, today))
}

pub fn limit_if_today(limit: DayLimit, today: u32) -> Option<u32> {
    (limit.today == today).then_some(limit.limit)
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct RemainingLimits {
    pub(crate) review: u32,
    pub(crate) new: u32,
    pub(crate) cap_new_to_review: bool,
}

impl RemainingLimits {
    pub(crate) fn new(
        deck: &Deck,
        config: Option<&DeckConfig>,
        today: u32,
        new_cards_ignore_review_limit: bool,
    ) -> Self {
        if let Ok(normal) = deck.normal() {
            if let Some(config) = config {
                return Self::new_for_normal_deck(
                    deck,
                    today,
                    new_cards_ignore_review_limit,
                    normal,
                    config,
                );
            }
        }
        Self::default()
    }

    fn new_for_normal_deck(
        deck: &Deck,
        today: u32,
        new_cards_ignore_review_limit: bool,
        normal: &NormalDeck,
        config: &DeckConfig,
    ) -> RemainingLimits {
        Self::new_for_normal_deck_v3(deck, today, new_cards_ignore_review_limit, normal, config)
    }

    fn new_for_normal_deck_v3(
        deck: &Deck,
        today: u32,
        new_cards_ignore_review_limit: bool,
        normal: &NormalDeck,
        config: &DeckConfig,
    ) -> RemainingLimits {
        let mut review_limit =
            current_review_limit(normal, today).unwrap_or(config.inner.reviews_per_day) as i32;
        let mut new_limit =
            current_new_limit(normal, today).unwrap_or(config.inner.new_per_day) as i32;
        let (new_today_count, review_today_count) = deck.new_rev_counts(today);

        review_limit -= review_today_count;
        new_limit -= new_today_count;
        if !new_cards_ignore_review_limit {
            review_limit -= new_today_count;
            new_limit = new_limit.min(review_limit);
        }

        Self {
            review: review_limit.max(0) as u32,
            new: new_limit.max(0) as u32,
            cap_new_to_review: !new_cards_ignore_review_limit,
        }
    }

    pub(crate) fn get(&self, kind: LimitKind) -> u32 {
        match kind {
            LimitKind::Review => self.review,
            LimitKind::New => self.new,
        }
    }

    pub(crate) fn cap_to(&mut self, limits: RemainingLimits) {
        self.review = self.review.min(limits.review);
        self.new = self.new.min(limits.new);
    }

    /// True if some limit was decremented to 0.
    fn decrement(&mut self, kind: LimitKind) -> DecrementResult {
        let before = *self;
        match kind {
            LimitKind::Review => {
                self.review = self.review.saturating_sub(1);
                if self.cap_new_to_review {
                    self.new = self.new.min(self.review);
                }
            }
            LimitKind::New => self.new = self.new.saturating_sub(1),
        }
        DecrementResult::new(&before, self)
    }
}

struct DecrementResult {
    count_reached_zero: bool,
}

impl DecrementResult {
    fn new(before: &RemainingLimits, after: &RemainingLimits) -> Self {
        Self {
            count_reached_zero: before.review > 0 && after.review == 0
                || before.new > 0 && after.new == 0,
        }
    }
}

impl Default for RemainingLimits {
    fn default() -> Self {
        RemainingLimits {
            review: 9999,
            new: 9999,
            cap_new_to_review: false,
        }
    }
}

pub(crate) fn remaining_limits_map<'a>(
    decks: impl Iterator<Item = &'a Deck>,
    config: &'a HashMap<DeckConfigId, DeckConfig>,
    today: u32,
    new_cards_ignore_review_limit: bool,
) -> HashMap<DeckId, RemainingLimits> {
    decks
        .map(|deck| {
            (
                deck.id,
                RemainingLimits::new(
                    deck,
                    deck.config_id().and_then(|id| config.get(&id)),
                    today,
                    new_cards_ignore_review_limit,
                ),
            )
        })
        .collect()
}

/// Wrapper of [RemainingLimits] with some additional meta data.
#[derive(Debug, Clone, Copy)]
struct NodeLimits {
    /// absolute level in the deck hierarchy
    level: usize,
    limits: RemainingLimits,
}

impl NodeLimits {
    fn new(
        deck: &Deck,
        config: &HashMap<DeckConfigId, DeckConfig>,
        today: u32,
        new_cards_ignore_review_limit: bool,
    ) -> Self {
        Self {
            level: deck.name.components().count(),
            limits: RemainingLimits::new(
                deck,
                deck.config_id().and_then(|id| config.get(&id)),
                today,
                new_cards_ignore_review_limit,
            ),
        }
    }
}

#[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<NodeLimits>,
    /// A map to access the tree node of a deck.
    map: HashMap<DeckId, NodeId>,
}

impl LimitTreeMap {
    /// [Deck]s must be sorted by name.
    pub(crate) fn build(
        decks: &[Deck],
        config: &HashMap<DeckConfigId, DeckConfig>,
        today: u32,
        new_cards_ignore_review_limit: bool,
    ) -> Self {
        let root_limits = NodeLimits::new(&decks[0], config, today, new_cards_ignore_review_limit);
        let mut tree = Tree::new();
        let root_id = tree
            .insert(Node::new(root_limits), InsertBehavior::AsRoot)
            .unwrap();

        let mut map = HashMap::new();
        map.insert(decks[0].id, root_id.clone());

        let mut limits = Self { tree, map };
        let mut remaining_decks = decks[1..].iter().peekable();
        limits.add_child_nodes(
            root_id,
            &mut remaining_decks,
            config,
            today,
            new_cards_ignore_review_limit,
        );

        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<'d>(
        &mut self,
        parent_node_id: NodeId,
        remaining_decks: &mut Peekable<impl Iterator<Item = &'d Deck>>,
        config: &HashMap<DeckConfigId, DeckConfig>,
        today: u32,
        new_cards_ignore_review_limit: bool,
    ) {
        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,
                        new_cards_ignore_review_limit,
                    );
                    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,
                            new_cards_ignore_review_limit,
                        )
                    } 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<DeckConfigId, DeckConfig>,
        today: u32,
        new_cards_ignore_review_limit: bool,
    ) {
        let mut child_limits =
            NodeLimits::new(child_deck, config, today, new_cards_ignore_review_limit);
        child_limits
            .limits
            .cap_to(self.get_node_limits(&parent_node_id));
        let child_node_id = self
            .tree
            .insert(
                Node::new(child_limits),
                InsertBehavior::UnderNode(&parent_node_id),
            )
            .unwrap();
        self.map.insert(child_deck.id, child_node_id);
    }

    fn get_node_id(&self, deck_id: DeckId) -> Result<&NodeId> {
        self.map
            .get(&deck_id)
            .or_invalid("deck not found in limits map")
    }

    fn get_node_limits(&self, node_id: &NodeId) -> RemainingLimits {
        self.tree.get(node_id).unwrap().data().limits
    }

    fn get_deck_limits(&self, deck_id: DeckId) -> Result<RemainingLimits> {
        self.get_node_id(deck_id)
            .map(|node_id| self.get_node_limits(node_id))
    }

    fn get_root_limits(&self) -> RemainingLimits {
        self.get_node_limits(self.tree.root_node_id().unwrap())
    }

    pub(crate) fn root_limit_reached(&self, kind: LimitKind) -> bool {
        self.get_root_limits().get(kind) == 0
    }

    pub(crate) fn limit_reached(&self, deck_id: DeckId, kind: LimitKind) -> Result<bool> {
        Ok(self.get_deck_limits(deck_id)?.get(kind) == 0)
    }

    pub(crate) fn decrement_deck_and_parent_limits(
        &mut self,
        deck_id: DeckId,
        kind: LimitKind,
    ) -> Result<()> {
        let node_id = self.get_node_id(deck_id)?.clone();
        self.decrement_node_and_parent_limits(&node_id, kind);
        Ok(())
    }

    fn decrement_node_and_parent_limits(&mut self, node_id: &NodeId, kind: LimitKind) {
        let node = self.tree.get_mut(node_id).unwrap();
        let parent = node.parent().cloned();

        let limits = &mut node.data_mut().limits;
        if limits.decrement(kind).count_reached_zero {
            let limits = *limits;
            self.cap_node_and_descendants(node_id, limits);
        };

        if let Some(parent_id) = parent {
            self.decrement_node_and_parent_limits(&parent_id, kind)
        }
    }

    fn cap_node_and_descendants(&mut self, node_id: &NodeId, limits: RemainingLimits) {
        let node = self.tree.get_mut(node_id).unwrap();
        node.data_mut().limits.cap_to(limits);
        for child_id in node.children().clone() {
            self.cap_node_and_descendants(&child_id, limits);
        }
    }
}
