//-
// Copyright 2017 Jason Lingle
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Support for combining strategies into tuples.
//!
//! There is no explicit "tuple strategy"; simply make a tuple containing the
//! strategy and that tuple is itself a strategy.

use crate::strategy::*;
use crate::test_runner::*;

/// Common `ValueTree` implementation for all tuple strategies.
#[derive(Clone, Copy, Debug)]
pub struct TupleValueTree<T> {
    tree: T,
    shrinker: u32,
    prev_shrinker: Option<u32>,
}

impl<T> TupleValueTree<T> {
    /// Create a new `TupleValueTree` wrapping `inner`.
    ///
    /// It only makes sense for `inner` to be a tuple of an arity for which the
    /// type implements `ValueTree`.
    pub fn new(inner: T) -> Self {
        TupleValueTree {
            tree: inner,
            shrinker: 0,
            prev_shrinker: None,
        }
    }
}

macro_rules! tuple {
    ($($fld:tt : $typ:ident),*) => {
        impl<$($typ : Strategy),*> Strategy for ($($typ,)*) {
            type Tree = TupleValueTree<($($typ::Tree,)*)>;
            type Value = ($($typ::Value,)*);

            fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
                let values = ($(self.$fld.new_tree(runner)?,)*);
                Ok(TupleValueTree::new(values))
            }
        }

        impl<$($typ : ValueTree),*> ValueTree
        for TupleValueTree<($($typ,)*)> {
            type Value = ($($typ::Value,)*);

            fn current(&self) -> Self::Value {
                ($(self.tree.$fld.current(),)*)
            }

            fn simplify(&mut self) -> bool {
                $(
                    if $fld == self.shrinker {
                        if self.tree.$fld.simplify() {
                            self.prev_shrinker = Some(self.shrinker);
                            return true;
                        } else {
                            self.shrinker += 1;
                        }
                    }
                )*
                false
            }

            fn complicate(&mut self) -> bool {
                if let Some(shrinker) = self.prev_shrinker {$(
                    if $fld == shrinker {
                        if self.tree.$fld.complicate() {
                            self.shrinker = shrinker;
                            return true;
                        } else {
                            self.prev_shrinker = None;
                            return false;
                        }
                    }
                )*}
                false
            }
        }
    }
}

tuple!(0: A);
tuple!(0: A, 1: B);
tuple!(0: A, 1: B, 2: C);
tuple!(0: A, 1: B, 2: C, 3: D);
tuple!(0: A, 1: B, 2: C, 3: D, 4: E);
tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G);
tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H);
tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I);
tuple!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J);

#[cfg(test)]
mod test {
    use crate::strategy::*;

    use super::*;

    #[test]
    fn shrinks_fully_ltr() {
        fn pass(a: (i32, i32)) -> bool {
            a.0 * a.1 <= 9
        }

        let input = (0..32, 0..32);
        let mut runner = TestRunner::default();

        let mut cases_tested = 0;
        for _ in 0..256 {
            // Find a failing test case
            let mut case = input.new_tree(&mut runner).unwrap();
            if pass(case.current()) {
                continue;
            }

            loop {
                if pass(case.current()) {
                    if !case.complicate() {
                        break;
                    }
                } else {
                    if !case.simplify() {
                        break;
                    }
                }
            }

            let last = case.current();
            assert!(!pass(last));
            // Maximally shrunken
            assert!(pass((last.0 - 1, last.1)));
            assert!(pass((last.0, last.1 - 1)));

            cases_tested += 1;
        }

        assert!(cases_tested > 32, "Didn't find enough test cases");
    }

    #[test]
    fn test_sanity() {
        check_strategy_sanity((0i32..100, 0i32..1000, 0i32..10000), None);
    }
}
