use metrohash::MetroHashMap;
use std::collections::hash_map::Entry;
use std::hash::Hash;
use std::rc::Rc;
use std::sync::Arc;
use crate::implementation::mdd::shallow::utils::{Node, Edge};
use crate::common::{PartialAssignment, Solution, FrontierNode, Completion, Reason, VarSet, Decision, Variable};
use crate::implementation::mdd::MDDType;
use crate::abstraction::mdd::{MDD, Config};
use crate::abstraction::heuristics::SelectableNode;
use crate::implementation::mdd::deep::mdd::DeepMDD;
use crate::implementation::mdd::hybrid::CompositeMDD;
#[derive(Clone)]
pub struct AggressivelyBoundedMDD<T, C>
    where T: Eq + Hash + Clone,
          C: Config<T> + Clone {
    composite: CompositeMDD<T, C, RestrictedOnly<T,C>, DeepMDD<T, C>>
}
impl <T, C> AggressivelyBoundedMDD<T, C>
    where T: Eq + Hash + Clone,
          C: Config<T> + Clone {
    pub fn new(c: C) -> Self {
        Self { 
        composite: CompositeMDD::new(RestrictedOnly::new(c.clone()), DeepMDD::new(c))
        }
    }
}
impl <T, C> MDD<T, C> for AggressivelyBoundedMDD<T, C>
    where T: Eq + Hash + Clone,
          C: Config<T> + Clone {
    fn config(&self) -> &C {
        self.composite.config()
    }
    fn config_mut(&mut self) -> &mut C {
        self.composite.config_mut()
    }
    fn exact(&mut self, root: &FrontierNode<T>, best_lb: isize, ub: isize) -> Result<Completion, Reason> {
        self.composite.exact(root, best_lb, ub)
    }
    fn restricted(&mut self, root: &FrontierNode<T>, best_lb: isize, ub: isize) -> Result<Completion, Reason> {
        self.composite.restricted(root, best_lb, ub)
    }
    fn relaxed(&mut self, root: &FrontierNode<T>, best_lb: isize, ub: isize) -> Result<Completion, Reason> {
        self.composite.relaxed(root, best_lb, ub)
    }
    fn is_exact(&self) -> bool {
        self.composite.is_exact()
    }
    fn best_value(&self) -> isize {
        self.composite.best_value()
    }
    fn best_solution(&self) -> Option<Solution> {
        self.composite.best_solution()
    }
    fn for_each_cutset_node<F>(&self, func: F) where F: FnMut(FrontierNode<T>) {
        self.composite.for_each_cutset_node(func)
    }
}
impl <T, C> From<C> for AggressivelyBoundedMDD<T, C>
    where T: Eq + Hash + Clone,
          C: Config<T> + Clone
{
    fn from(c: C) -> Self {
        Self::new(c)
    }
}
type Layer<T> = MetroHashMap<Rc<T>, Rc<Node<T>>>;
#[derive(Debug, Clone)]
struct RestrictedOnly<T, C>
    where T: Eq + Hash + Clone,
          C: Config<T> + Clone {
    
    
    
    config: C,
    
    
    
    mddtype: MDDType,
    
    
    
    layers: [Layer<T>; 3],
    
    current: usize,
    
    next: usize,
    
    lel: usize,
    
    
    prev: usize,
    
    is_exact: bool,
    
    
    root_pa: Arc<PartialAssignment>,
    
    
    best_lb: isize,
    
    
    max_width: usize,
    
    
    best_node: Option<Rc<Node<T>>>,
    
    
    
    buffer: Vec<Rc<Node<T>>>
}
impl <T, C> MDD<T, C> for RestrictedOnly<T, C>
    where T: Eq + Hash + Clone,
          C: Config<T> + Clone
{
    fn config(&self) -> &C {
        &self.config
    }
    fn config_mut(&mut self) -> &mut C {
        &mut self.config
    }
    fn is_exact(&self) -> bool {
        self.is_exact
            || self.mddtype == MDDType::Relaxed && self.best_node.as_ref().map(|n| n.has_exact_best()).unwrap_or(true)
    }
    fn best_value(&self) -> isize {
        if let Some(node) = &self.best_node {
            node.value
        } else {
            isize::min_value()
        }
    }
    fn best_solution(&self) -> Option<Solution> {
        self.best_node.as_ref().map(|n| Solution::new(self.partial_assignment(n)))
    }
    fn for_each_cutset_node<F>(&self, func: F) where F: FnMut(FrontierNode<T>) {
        if !self.is_exact {
            match self.mddtype {
                MDDType::Exact      => {},
                MDDType::Relaxed    => self.for_each_relaxed_cutset_node(func),
                MDDType::Restricted => self.for_each_restricted_cutset_node(func),
            }
        }
    }
    fn exact(&mut self, root: &FrontierNode<T>, best_lb: isize, ub: isize) -> Result<Completion, Reason> {
        self.clear();
        let free_vars = self.config.load_variables(root);
        self.mddtype  = MDDType::Exact;
        self.max_width= usize::max_value();
        self.develop(root, free_vars, best_lb, ub)
    }
    fn restricted(&mut self, root: &FrontierNode<T>, best_lb: isize, ub: isize) -> Result<Completion, Reason> {
        self.clear();
        let free_vars = self.config.load_variables(root);
        self.mddtype  = MDDType::Restricted;
        self.max_width= self.config.max_width(&free_vars);
        self.develop(root, free_vars, best_lb, ub)
    }
    
    fn relaxed(&mut self, root: &FrontierNode<T>, best_lb: isize, ub: isize) -> Result<Completion, Reason> {
        self.clear();
        let free_vars = self.config.load_variables(root);
        self.mddtype  = MDDType::Relaxed;
        self.max_width= self.config.max_width(&free_vars);
        self.develop(root, free_vars, best_lb, ub)
    }
}
macro_rules! current_layer {
    ($dd:expr) => {
        unsafe { &*$dd.layers.as_ptr().add($dd.current) }
    };
}
impl <T, C> RestrictedOnly<T, C>
    where T: Eq + Hash + Clone,
          C: Config<T> + Clone
{
    
    pub fn new(config: C) -> Self {
        Self {
            config,
            mddtype  : MDDType::Exact,
            layers   : [Default::default(), Default::default(), Default::default()],
            current  : 0,
            next     : 1,
            lel      : 2,
            prev     : 0,
            root_pa  : Arc::new(PartialAssignment::Empty),
            is_exact : true,
            max_width: usize::max_value(),
            best_lb  : isize::min_value(),
            best_node: None,
            buffer   : vec![]
        }
    }
    
    
    pub fn clear(&mut self) {
        self.mddtype   = MDDType::Exact;
        self.current   = 0;
        self.next      = 1;
        self.lel       = 2;
        self.prev      = 0;
        self.is_exact  = true;
        
        self.best_node = None;
        self.best_lb   = isize::min_value();
        self.layers.iter_mut().for_each(|l|l.clear());
        
        self.buffer.clear();
    }
    
    
    
    
    fn develop(&mut self, root: &FrontierNode<T>, mut vars: VarSet, best_lb: isize, ub: isize) -> Result<Completion, Reason> {
        self.root_pa = Arc::clone(&root.path);
        let root     = Node::from(root);
        self.best_lb = best_lb;
        self.layers[self.next].insert(Rc::clone(&root.this_state), Rc::new(root));
        let mut depth = 0;
        while let Some(var) = self.next_var(&vars) {
            
            if self.config.must_stop(best_lb, ub) {
                return Err(Reason::CutoffOccurred);
            }
            self.add_layer();
            vars.remove(var);
            depth += 1;
            let mut next_layer_squashed = false;
            
            
            
            let buffer = unsafe{ &mut *(&mut self.buffer as *mut Vec<Rc<Node<T>>>) };
            buffer.clear();
            current_layer!(self).values().for_each(|n| buffer.push(Rc::clone(&n)));
            buffer.sort_unstable_by(|a, b| self.config.compare(a, b).reverse());
            for node in buffer.iter() {
                
                
                if next_layer_squashed || self.must_squash(depth) {
                    next_layer_squashed = true;
                    break;
                }
                let src_state = node.this_state.as_ref();
                for val in self.config.domain_of(src_state, var) {
                    
                    
                    if next_layer_squashed || self.must_squash(depth) {
                        next_layer_squashed = true;
                        break;
                    }
                    let decision = Decision { variable: var, value: val };
                    let state    = self.config.transition(src_state, &vars, decision);
                    let weight   = self.config.transition_cost(src_state, &vars, decision);
                    self.branch(node, state, decision, weight)
                }
            }
            
            if next_layer_squashed {
                match self.mddtype {
                    MDDType::Exact => {},
                    MDDType::Restricted => self.remember_lel(),
                    MDDType::Relaxed    => {
                        self.remember_lel();
                        
                        unimplemented!("One should add an extra node standing for all the not-explored yet nodes")
                    }
                }
            }
        }
        Ok(self.finalize())
    }
    
    #[inline]
    fn must_squash(&self, depth: usize) -> bool {
        match self.mddtype {
            MDDType::Exact      =>
                false,
            MDDType::Restricted =>
                depth > 1 && self.layers[self.next].len() >= self.max_width,
            MDDType::Relaxed    =>
                depth > 1 && self.layers[self.next].len() >= self.max_width - 1
        }
    }
    
    
    fn next_var(&self, vars: &VarSet) -> Option<Variable> {
        let mut curr_it = self.layers[self.prev].keys().map(|k| k.as_ref());
        let mut next_it = self.layers[self.next].keys().map(|k| k.as_ref());
        self.config.select_var(vars, &mut curr_it, &mut next_it)
    }
    
    
    
    fn add_layer(&mut self) {
        self.swap_current_next();
        self.prev = self.current;
        self.layers[self.next].clear();
    }
    
    
    
    
    
    
    
    
    
    fn branch(&mut self, node: &Rc<Node<T>>, dest: T, decision: Decision, weight: isize) {
        let dst_node = Node {
            this_state: Rc::new(dest),
            value: node.value + weight,
            estimate: isize::max_value(),
            flags: node.flags, 
            best_edge: Some(Edge {
                parent: Rc::clone(&node),
                weight,
                decision
            })
        };
        self.add_node(dst_node)
    }
    
    fn add_node(&mut self, mut node: Node<T>) {
        match self.layers[self.next].entry(Rc::clone(&node.this_state)) {
            Entry::Vacant(re) => {
                node.estimate = self.config.estimate(node.state());
                if node.ub() > self.best_lb {
                    re.insert(Rc::new(node));
                }
            },
            Entry::Occupied(mut re) => {
                Self::merge(re.get_mut(), node);
            }
        }
    }
    
    
    fn swap_current_next(&mut self) {
        let tmp      = self.current;
        self.current = self.next;
        self.next    = tmp;
    }
    
    
    fn swap_current_lel(&mut self) {
        let tmp      = self.current;
        self.current = self.lel;
        self.lel     = tmp;
    }
    
    
    
    
    fn remember_lel(&mut self) {
        if self.is_exact {
            self.is_exact = false;
            self.swap_current_lel();
        }
    }
    
    fn finalize(&mut self) -> Completion {
        self.best_node = self.layers[self.next].values()
            .max_by_key(|n| n.value)
            .cloned();
        Completion{
            is_exact     : self.is_exact(),
            best_value   : self.best_node.as_ref().map(|node| node.value),
        }
    }
    
    fn partial_assignment(&self, n: &Node<T>) -> Arc<PartialAssignment> {
        let root = &self.root_pa;
        let path = n.path();
        Arc::new(PartialAssignment::FragmentExtension {parent: Arc::clone(root), fragment: path})
    }
    
    
    fn merge(old: &mut Rc<Node<T>>, new: Node<T>) {
        if let Some(e) = Rc::get_mut(old) {
            e.merge(new)
        } else {
            let mut cpy = old.as_ref().clone();
            cpy.merge(new);
            *old = Rc::new(cpy)
        }
    }
    fn for_each_restricted_cutset_node<F>(&self, mut func: F) where F: FnMut(FrontierNode<T>) {
        self.layers[self.lel].values().for_each(|n| {
            let frontier_node = n.to_frontier_node(&self.root_pa);
            (func)(frontier_node);
        });
    }
    fn for_each_relaxed_cutset_node<F>(&self, mut func: F) where F: FnMut(FrontierNode<T>) {
        let ub = self.best_value();
        if ub > self.best_lb {
            self.layers[self.lel].values().for_each(|n| {
                let mut frontier_node = n.to_frontier_node(&self.root_pa);
                frontier_node.ub = ub.min(frontier_node.ub);
                (func)(frontier_node);
            });
        }
    }
}
impl <T, C> From<C> for RestrictedOnly<T, C>
    where T: Eq + Hash + Clone,
          C: Config<T> + Clone
{
    fn from(c: C) -> Self {
        Self::new(c)
    }
}
#[cfg(test)]
mod test_restricted_only {
    use std::sync::Arc;
    use crate::abstraction::dp::{Problem, Relaxation};
    use crate::abstraction::mdd::{MDD, Config};
    use crate::common::{Decision, Domain, FrontierNode, PartialAssignment, Reason, Variable, VarSet};
    use crate::implementation::heuristics::FixedWidth;
    use crate::implementation::mdd::config::config_builder;
    use crate::implementation::mdd::MDDType;
    use crate::implementation::mdd::aggressively_bounded::RestrictedOnly;
    use crate::test_utils::{MockConfig, MockCutoff, Proxy};
    use mock_it::Matcher;
    #[test]
    fn by_default_the_mdd_type_is_exact() {
        let config = MockConfig::default();
        let mdd = RestrictedOnly::new(config);
        assert_eq!(MDDType::Exact, mdd.mddtype);
    }
    #[test]
    fn mdd_type_changes_depending_on_the_requested_type_of_mdd() {
        let root_n = FrontierNode {
            state: Arc::new(0),
            lp_len: 0,
            ub: 24,
            path: Arc::new(PartialAssignment::Empty)
        };
        let config = MockConfig::default();
        let mut mdd = RestrictedOnly::new(config);
        
        
        assert!(mdd.restricted(&root_n, 0, 1000).is_ok());
        assert_eq!(MDDType::Restricted, mdd.mddtype);
        assert!(mdd.exact(&root_n, 0, 1000).is_ok());
        assert_eq!(MDDType::Exact, mdd.mddtype);
    }
    #[derive(Copy, Clone)]
    struct DummyProblem;
    impl Problem<usize> for DummyProblem {
        fn nb_vars(&self) -> usize { 3 }
        fn initial_state(&self) -> usize { 0 }
        fn initial_value(&self) -> isize { 0 }
        fn domain_of<'a>(&self, _: &'a usize, _: Variable) -> Domain<'a> {
            (0..=2).into()
        }
        fn transition(&self, state: &usize, _: &VarSet, d: Decision) -> usize {
            *state + d.value as usize
        }
        fn transition_cost(&self, _: &usize, _: &VarSet, d: Decision) -> isize {
            d.value
        }
    }
    #[derive(Copy, Clone)]
    struct DummyRelax;
    impl Relaxation<usize> for DummyRelax {
        fn merge_states(&self, _: &mut dyn Iterator<Item=&usize>) -> usize {
            100
        }
        fn relax_edge(&self, _: &usize, _: &usize, _: &usize, _: Decision, _: isize) -> isize {
            20
        }
        fn estimate(&self, _state: &usize) -> isize {
            50
        }
    }
    #[test]
    fn exact_no_cutoff_completion_must_be_coherent_with_outcome() {
        let pb = DummyProblem;
        let rlx= DummyRelax;
        let cfg= config_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root   = mdd.config().root_node();
        let result = mdd.exact(&root, 0, 1000);
        assert!(result.is_ok());
        let completion = result.unwrap();
        assert_eq!(completion.is_exact  , mdd.is_exact());
        assert_eq!(completion.best_value, Some(mdd.best_value()));
    }
    #[test]
    fn restricted_no_cutoff_completion_must_be_coherent_with_outcome_() {
        let pb = DummyProblem;
        let rlx= DummyRelax;
        let cfg= config_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root   = mdd.config().root_node();
        let result = mdd.restricted(&root, 0, 1000);
        assert!(result.is_ok());
        let completion = result.unwrap();
        assert_eq!(completion.is_exact  , mdd.is_exact());
        assert_eq!(completion.best_value, Some(mdd.best_value()));
    }
    
    #[test]
    fn exact_fails_with_cutoff_when_cutoff_occurs() {
        let pb      = DummyProblem;
        let rlx     = DummyRelax;
        let cutoff  = MockCutoff::default();
        let cfg     = config_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .with_cutoff(Proxy::new(&cutoff))
            .build();
        let mut mdd = RestrictedOnly::from(cfg);
        cutoff.must_stop.given(Matcher::Any).will_return(true);
        let root   = mdd.config().root_node();
        let result = mdd.exact(&root, 0, 1000);
        assert!(result.is_err());
        assert_eq!(Some(Reason::CutoffOccurred), result.err());
    }
    #[test]
    fn restricted_fails_with_cutoff_when_cutoff_occurs() {
        let pb      = DummyProblem;
        let rlx     = DummyRelax;
        let cutoff  = MockCutoff::default();
        let cfg     = config_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .with_cutoff(Proxy::new(&cutoff))
            .build();
        let mut mdd = RestrictedOnly::from(cfg);
        cutoff.must_stop.given(Matcher::Any).will_return(true);
        let root   = mdd.config().root_node();
        let result = mdd.restricted(&root, 0, 1000);
        assert!(result.is_err());
        assert_eq!(Some(Reason::CutoffOccurred), result.err());
    }
    
    
    #[test]
    fn exact_completely_unrolls_the_mdd_no_matter_its_width() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let cfg = config_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 0, 1000).is_ok());
        assert!(mdd.best_solution().is_some());
        assert_eq!(mdd.best_value(), 6);
        assert_eq!(mdd.best_solution().unwrap().iter().collect::<Vec<Decision>>(),
                   vec![
                       Decision { variable: Variable(2), value: 2 },
                       Decision { variable: Variable(1), value: 2 },
                       Decision { variable: Variable(0), value: 2 },
                   ]
        );
    }
    #[test]
    fn restricted_drops_the_less_interesting_nodes() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let cfg = config_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root = mdd.config().root_node();
        assert!(mdd.restricted(&root, 0, 1000).is_ok());
        assert!(mdd.best_solution().is_some());
        assert_eq!(mdd.best_value(), 2);
        assert_eq!(mdd.best_solution().unwrap().iter().collect::<Vec<Decision>>(),
                   vec![
                       Decision { variable: Variable(2), value: 0 },
                       Decision { variable: Variable(1), value: 0 },
                       Decision { variable: Variable(0), value: 2 },
                   ]
        );
    }
    
    #[test]
    fn an_exact_mdd_must_be_exact() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let cfg = config_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 0, 1000).is_ok());
        assert_eq!(true, mdd.is_exact())
    }
    
    #[test]
    fn a_restricted_mdd_is_exact_as_long_as_no_restriction_occurs() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let cfg = config_builder(&pb, rlx).with_max_width(FixedWidth(10)).build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root = mdd.config().root_node();
        assert!(mdd.restricted(&root, 0, 1000).is_ok());
        assert_eq!(true, mdd.is_exact())
    }
    #[test]
    fn a_restricted_mdd_is_not_exact_when_a_restriction_occurred() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let cfg = config_builder(&pb, rlx).with_max_width(FixedWidth(1)).build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root = mdd.config().root_node();
        assert!(mdd.restricted(&root, 0, 1000).is_ok());
        assert_eq!(false, mdd.is_exact())
    }
    #[derive(Clone, Copy)]
    struct DummyInfeasibleProblem;
    impl Problem<usize> for DummyInfeasibleProblem {
        fn nb_vars(&self) -> usize { 3 }
        fn initial_state(&self) -> usize { 0 }
        fn initial_value(&self) -> isize { 0 }
        #[allow(clippy::reversed_empty_ranges)]
        fn domain_of<'a>(&self, _: &'a usize, _: Variable) -> Domain<'a> {
            (0..0).into()
        }
        fn transition(&self, state: &usize, _: &VarSet, d: Decision) -> usize {
            *state + d.value as usize
        }
        fn transition_cost(&self, _: &usize, _: &VarSet, d: Decision) -> isize {
            d.value
        }
    }
    #[test]
    fn when_the_problem_is_infeasible_there_is_no_solution() {
        let pb = DummyInfeasibleProblem;
        let rlx = DummyRelax;
        let cfg = config_builder(&pb, rlx).build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 0, 1000).is_ok());
        assert!(mdd.best_solution().is_none())
    }
    #[test]
    fn when_the_problem_is_infeasible_the_best_value_is_min_infinity() {
        let pb = DummyInfeasibleProblem;
        let rlx = DummyRelax;
        let cfg = config_builder(&pb, rlx).build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 0, 1000).is_ok());
        assert_eq!(isize::min_value(), mdd.best_value())
    }
    #[test]
    fn exact_skips_node_with_an_ub_less_than_best_known_lb() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let cfg = config_builder(&pb, rlx).build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 100, 1000).is_ok());
        assert!(mdd.best_solution().is_none())
    }
    
    #[test]
    fn restricted_skips_node_with_an_ub_less_than_best_known_lb() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let cfg = config_builder(&pb, rlx).build();
        let mut mdd = RestrictedOnly::from(cfg);
        let root = mdd.config().root_node();
        assert!(mdd.restricted(&root, 100, 1000).is_ok());
        assert!(mdd.best_solution().is_none())
    }
}
#[cfg(test)]
mod test_aggressively_bounded_width {
    use std::sync::Arc;
    use crate::abstraction::dp::{Problem, Relaxation};
    use crate::abstraction::mdd::{MDD, Config};
    use crate::common::{Decision, Domain, FrontierNode, PartialAssignment, Reason, Variable, VarSet};
    use crate::implementation::heuristics::FixedWidth;
    use crate::implementation::mdd::config::mdd_builder;
    use crate::implementation::mdd::MDDType;
    use crate::test_utils::{MockConfig, MockCutoff, Proxy};
    use crate::implementation::mdd::aggressively_bounded::AggressivelyBoundedMDD;
    use mock_it::Matcher;
    type DD<T, C> = AggressivelyBoundedMDD<T, C>;
    #[test]
    fn by_default_the_mdd_type_is_exact() {
        let config = MockConfig::default();
        let mdd = DD::new(config);
        assert_eq!(MDDType::Exact, mdd.composite.mddtype);
    }
    #[test]
    fn mdd_type_changes_depending_on_the_requested_type_of_mdd() {
        let root_n = FrontierNode {
            state: Arc::new(0),
            lp_len: 0,
            ub: 24,
            path: Arc::new(PartialAssignment::Empty)
        };
        let config = MockConfig::default();
        let mut mdd = DD::new(config);
        assert!(mdd.relaxed(&root_n, 0, 1000).is_ok());
        assert_eq!(MDDType::Relaxed, mdd.composite.mddtype);
        assert!(mdd.restricted(&root_n, 0, 1000).is_ok());
        assert_eq!(MDDType::Restricted, mdd.composite.mddtype);
        assert!(mdd.exact(&root_n, 0, 1000).is_ok());
        assert_eq!(MDDType::Exact, mdd.composite.mddtype);
    }
    #[derive(Copy, Clone)]
    struct DummyProblem;
    impl Problem<usize> for DummyProblem {
        fn nb_vars(&self) -> usize { 3 }
        fn initial_state(&self) -> usize { 0 }
        fn initial_value(&self) -> isize { 0 }
        fn domain_of<'a>(&self, _: &'a usize, _: Variable) -> Domain<'a> {
            (0..=2).into()
        }
        fn transition(&self, state: &usize, _: &VarSet, d: Decision) -> usize {
            *state + d.value as usize
        }
        fn transition_cost(&self, _: &usize, _: &VarSet, d: Decision) -> isize {
            d.value
        }
    }
    #[derive(Copy, Clone)]
    struct DummyRelax;
    impl Relaxation<usize> for DummyRelax {
        fn merge_states(&self, _: &mut dyn Iterator<Item=&usize>) -> usize {
            100
        }
        fn relax_edge(&self, _: &usize, _: &usize, _: &usize, _: Decision, _: isize) -> isize {
            20
        }
        fn estimate(&self, _state: &usize) -> isize {
            50
        }
    }
    #[test]
    fn exact_no_cutoff_completion_must_be_coherent_with_outcome() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd  = DD::from(config);
        let root     = mdd.config().root_node();
        let result   = mdd.exact(&root, 0, 1000);
        assert!(result.is_ok());
        let completion = result.unwrap();
        assert_eq!(completion.is_exact  , mdd.is_exact());
        assert_eq!(completion.best_value, Some(mdd.best_value()));
    }
    #[test]
    fn restricted_no_cutoff_completion_must_be_coherent_with_outcome() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd  = DD::from(config);
        let root     = mdd.config().root_node();
        let result   = mdd.restricted(&root, 0, 1000);
        assert!(result.is_ok());
        let completion = result.unwrap();
        assert_eq!(completion.is_exact  , mdd.is_exact());
        assert_eq!(completion.best_value, Some(mdd.best_value()));
    }
    #[test]
    fn relaxed_no_cutoff_completion_must_be_coherent_with_outcome() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd  = DD::from(config);
        let root     = mdd.config().root_node();
        let result   = mdd.relaxed(&root, 0, 1000);
        assert!(result.is_ok());
        let completion = result.unwrap();
        assert_eq!(completion.is_exact  , mdd.is_exact());
        assert_eq!(completion.best_value, Some(mdd.best_value()));
    }
    #[test]
    fn exact_fails_with_cutoff_when_cutoff_occurs() {
        let pb      = DummyProblem;
        let rlx     = DummyRelax;
        let cutoff  = MockCutoff::default();
        let config  = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .with_cutoff(Proxy::new(&cutoff))
            .build();
        let mut mdd = DD::from(config);
        cutoff.must_stop.given(Matcher::Any).will_return(true);
        let root   = mdd.config().root_node();
        let result = mdd.exact(&root, 0, 1000);
        assert!(result.is_err());
        assert_eq!(Some(Reason::CutoffOccurred), result.err());
    }
    #[test]
    fn restricted_fails_with_cutoff_when_cutoff_occurs() {
        let pb      = DummyProblem;
        let rlx     = DummyRelax;
        let cutoff  = MockCutoff::default();
        let config  = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .with_cutoff(Proxy::new(&cutoff))
            .build();
        let mut mdd = DD::from(config);
        cutoff.must_stop.given(Matcher::Any).will_return(true);
        let root   = mdd.config().root_node();
        let result = mdd.restricted(&root, 0, 1000);
        assert!(result.is_err());
        assert_eq!(Some(Reason::CutoffOccurred), result.err());
    }
    #[test]
    fn relaxed_fails_with_cutoff_when_cutoff_occurs() {
        let pb      = DummyProblem;
        let rlx     = DummyRelax;
        let cutoff  = MockCutoff::default();
        let config  = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .with_cutoff(Proxy::new(&cutoff))
            .build();
        let mut mdd = DD::from(config);
        cutoff.must_stop.given(Matcher::Any).will_return(true);
        let root   = mdd.config().root_node();
        let result = mdd.relaxed(&root, 0, 1000);
        assert!(result.is_err());
        assert_eq!(Some(Reason::CutoffOccurred), result.err());
    }
    
    #[test]
    fn exact_completely_unrolls_the_mdd_no_matter_its_width() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 0, 1000).is_ok());
        assert!(mdd.best_solution().is_some());
        assert_eq!(mdd.best_value(), 6);
        assert_eq!(mdd.best_solution().unwrap().iter().collect::<Vec<Decision>>(),
                   vec![
                       Decision { variable: Variable(2), value: 2 },
                       Decision { variable: Variable(1), value: 2 },
                       Decision { variable: Variable(0), value: 2 },
                   ]
        );
    }
    #[test]
    fn restricted_drops_the_less_interesting_nodes() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.restricted(&root, 0, 1000).is_ok());
        assert!(mdd.best_solution().is_some());
        assert_eq!(mdd.best_value(), 2);
        assert_eq!(mdd.best_solution().unwrap().iter().collect::<Vec<Decision>>(),
                   vec![
                       Decision { variable: Variable(2), value: 0 },
                       Decision { variable: Variable(1), value: 0 },
                       Decision { variable: Variable(0), value: 2 },
                   ]
        );
    }
    #[test]
    fn relaxed_merges_the_less_interesting_nodes() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.relaxed(&root, 0, 1000).is_ok());
        assert!(mdd.best_solution().is_some());
        assert_eq!(mdd.best_value(), 42);
        assert_eq!(mdd.best_solution().unwrap().iter().collect::<Vec<Decision>>(),
                   vec![
                       Decision { variable: Variable(2), value: 2 },
                       Decision { variable: Variable(1), value: 2 },
                       Decision { variable: Variable(0), value: 2 },
                   ]
        );
    }
    #[test]
    fn relaxed_populates_the_cutset_and_will_not_squash_first_layer() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.relaxed(&root, 0, 1000).is_ok());
        let mut cutset = vec![];
        mdd.for_each_cutset_node(|n| cutset.push(n));
        assert_eq!(cutset.len(), 3); 
    }
    #[test]
    fn an_exact_mdd_must_be_exact() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx)
            .with_max_width(FixedWidth(1))
            .build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 0, 1000).is_ok());
        assert_eq!(true, mdd.is_exact())
    }
    #[test]
    fn a_relaxed_mdd_is_exact_as_long_as_no_merge_occurs() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx).with_max_width(FixedWidth(10)).build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.relaxed(&root, 0, 1000).is_ok());
        assert_eq!(true, mdd.is_exact())
    }
    #[test]
    fn a_relaxed_mdd_is_not_exact_when_a_merge_occurred() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx).with_max_width(FixedWidth(1)).build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.relaxed(&root, 0, 1000).is_ok());
        assert_eq!(false, mdd.is_exact())
    }
    #[test]
    fn a_restricted_mdd_is_exact_as_long_as_no_restriction_occurs() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx).with_max_width(FixedWidth(10)).build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.restricted(&root, 0, 1000).is_ok());
        assert_eq!(true, mdd.is_exact())
    }
    #[test]
    fn a_restricted_mdd_is_not_exact_when_a_restriction_occurred() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx).with_max_width(FixedWidth(1)).build();
        let mut mdd = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.restricted(&root, 0, 1000).is_ok());
        assert_eq!(false, mdd.is_exact())
    }
    #[derive(Clone, Copy)]
    struct DummyInfeasibleProblem;
    impl Problem<usize> for DummyInfeasibleProblem {
        fn nb_vars(&self) -> usize { 3 }
        fn initial_state(&self) -> usize { 0 }
        fn initial_value(&self) -> isize { 0 }
        #[allow(clippy::reversed_empty_ranges)]
        fn domain_of<'a>(&self, _: &'a usize, _: Variable) -> Domain<'a> {
            (0..0).into()
        }
        fn transition(&self, state: &usize, _: &VarSet, d: Decision) -> usize {
            *state + d.value as usize
        }
        fn transition_cost(&self, _: &usize, _: &VarSet, d: Decision) -> isize {
            d.value
        }
    }
    #[test]
    fn when_the_problem_is_infeasible_there_is_no_solution() {
        let pb = DummyInfeasibleProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx).build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 0, 1000).is_ok());
        assert!(mdd.best_solution().is_none())
    }
    #[test]
    fn when_the_problem_is_infeasible_the_best_value_is_min_infinity() {
        let pb = DummyInfeasibleProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx).build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 0, 1000).is_ok());
        assert_eq!(isize::min_value(), mdd.best_value())
    }
    #[test]
    fn exact_skips_node_with_an_ub_less_than_best_known_lb() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx).build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.exact(&root, 100, 1000).is_ok());
        assert!(mdd.best_solution().is_none())
    }
    #[test]
    fn relaxed_skips_node_with_an_ub_less_than_best_known_lb() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx).build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.relaxed(&root, 100, 1000).is_ok());
        assert!(mdd.best_solution().is_none())
    }
    #[test]
    fn restricted_skips_node_with_an_ub_less_than_best_known_lb() {
        let pb = DummyProblem;
        let rlx = DummyRelax;
        let config = mdd_builder(&pb, rlx).build();
        let mut mdd  = DD::from(config);
        let root = mdd.config().root_node();
        assert!(mdd.restricted(&root, 100, 1000).is_ok());
        assert!(mdd.best_solution().is_none())
    }
}