use crate::camel::{Dice, Face, Race, Roll};
use std::collections::HashMap;

pub struct Tree {
    nodes: Vec<Node>,
    roots: Vec<usize>,
}

impl Tree {
    pub fn singleton(value: Race) -> Self {
        let root = Node::new(value);
        let nodes = vec![root];
        let roots = vec![0];

        Self { nodes, roots }
    }

    pub fn expand(&mut self, dice: &Dice) {
        self.expand_roots(dice);
    }

    fn expand_roots(&mut self, dice: &Dice) {
        let root_indices: Vec<usize> = self.roots.iter().copied().collect();
        for index in root_indices {
            self.expand_node(index, dice);
        }
    }

    fn expand_node(&mut self, index: usize, dice: &Dice) {
        for camel in dice.clone() {
            let remaining_dice = dice.remove(camel);
            for face in Face::values() {
                let roll = Roll::from((camel, face));
                let race = self.perform_on(index, roll);
                let child_index = self.add_child(index, roll, race);
                self.expand_node(child_index, &remaining_dice);
            }
        }
    }

    fn perform_on(&mut self, index: usize, roll: Roll) -> Race {
        self.nodes[index].race.perform(roll)
    }

    fn add_child(&mut self, index: usize, roll: Roll, race: Race) -> usize {
        let child = Node::new(race);
        self.nodes.push(child);
        let child_index = self.nodes.len() - 1;

        self.nodes[index].register_child(roll, child_index);

        child_index
    }

    pub fn visit_leaves(&self, visitor: &mut dyn LeafVisitor) {
        for candidate in &self.nodes {
            if candidate.is_leaf() {
                visitor.visit(&candidate.race);
            }
        }
    }
}

struct Node {
    race: Race,
    children: HashMap<Roll, usize>,
}

impl Node {
    fn new(race: Race) -> Self {
        Self {
            race,
            children: HashMap::new(),
        }
    }
    fn register_child(&mut self, roll: Roll, child_index: usize) {
        self.children.insert(roll, child_index);
    }

    fn is_leaf(&self) -> bool {
        self.children.is_empty()
    }
}

pub trait LeafVisitor {
    fn visit(&mut self, race: &Race);
}
