use std::{
    array,
    hash::{Hash, RandomState},
};

use crate::{Die, Limit, Prng, dice};

/// Provides a [`Die`] implementation that generats pseudorandom values of `Self`.
pub trait Dice: Sized {
    /// Indicates whether the [`Die`] uses [`Limit`].
    ///
    /// If the value is `false`, the caller may always pass a zero [`Limit`] to the [`Die`].
    /// This is useful for structs which want to split the [`Limit`] for their fields,
    /// but only for fields that actually use the [`Limit`].
    const USES_LIMIT: bool;

    /// Returns a [`Die`] for `Self` with a reasonable value distribution for many use cases.
    ///
    /// Ideally, it...
    /// - generates the full range of possible values. Generating known edge
    ///   cases is more important than uniform distribution.
    /// - repects [`Limit`] to a feasible extend so that the overall size can
    ///   be controlled via [`Limit`].
    fn die() -> impl Die<Self>;
}

/// Summons a [`Die`] for `T` based on [`Dice`].
///
/// # Examples
///
/// ```
/// use dicetest::prelude::*;
/// use dicetest::{Prng, Limit};
///
/// let mut prng = Prng::from_seed(0x5EED.into());
/// let limit = Limit::default();
/// let mut fate = Fate::new(&mut prng, limit);
///
/// let _byte: u8 = fate.roll(die());
/// ```
pub fn die<T: Dice>() -> impl Die<T> {
    T::die()
}

macro_rules! impl_dice_for_primitve {
    ($type:ty, $die:expr) => {
        impl Dice for $type {
            const USES_LIMIT: bool = false;

            fn die() -> impl Die<Self> {
                $die
            }
        }
    };
}

impl_dice_for_primitve!((), dice::just(()));
impl_dice_for_primitve!(bool, dice::bool());
impl_dice_for_primitve!(usize, dice::usize(..));
impl_dice_for_primitve!(u8, dice::u8(..));
impl_dice_for_primitve!(u16, dice::u16(..));
impl_dice_for_primitve!(u32, dice::u32(..));
impl_dice_for_primitve!(u64, dice::u64(..));
impl_dice_for_primitve!(u128, dice::u128(..));
impl_dice_for_primitve!(isize, dice::isize(..));
impl_dice_for_primitve!(i8, dice::i8(..));
impl_dice_for_primitve!(i16, dice::i16(..));
impl_dice_for_primitve!(i32, dice::i32(..));
impl_dice_for_primitve!(i64, dice::i64(..));
impl_dice_for_primitve!(i128, dice::i128(..));
impl_dice_for_primitve!(f32, dice::any_f32());
impl_dice_for_primitve!(f64, dice::any_f64());
impl_dice_for_primitve!(char, dice::char());
impl_dice_for_primitve!(
    std::num::NonZeroU8,
    dice::u8(1..).map(|int| std::num::NonZeroU8::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroU16,
    dice::u16(1..).map(|int| std::num::NonZeroU16::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroU32,
    dice::u32(1..).map(|int| std::num::NonZeroU32::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroU64,
    dice::u64(1..).map(|int| std::num::NonZeroU64::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroU128,
    dice::u128(1..).map(|int| std::num::NonZeroU128::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroUsize,
    dice::usize(1..).map(|int| std::num::NonZeroUsize::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroI8,
    dice::one_of_die()
        .two(dice::i8(..=-1), dice::i8(1..))
        .map(|int| std::num::NonZeroI8::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroI16,
    dice::one_of_die()
        .two(dice::i16(..=-1), dice::i16(1..))
        .map(|int| std::num::NonZeroI16::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroI32,
    dice::one_of_die()
        .two(dice::i32(..=-1), dice::i32(1..))
        .map(|int| std::num::NonZeroI32::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroI64,
    dice::one_of_die()
        .two(dice::i64(..=-1), dice::i64(1..))
        .map(|int| std::num::NonZeroI64::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroI128,
    dice::one_of_die()
        .two(dice::i128(..=-1), dice::i128(1..))
        .map(|int| std::num::NonZeroI128::new(int).unwrap())
);
impl_dice_for_primitve!(
    std::num::NonZeroIsize,
    dice::one_of_die()
        .two(dice::isize(..=-1), dice::isize(1..))
        .map(|int| std::num::NonZeroIsize::new(int).unwrap())
);

impl<T: ?Sized> Dice for std::marker::PhantomData<T> {
    const USES_LIMIT: bool = false;

    fn die() -> impl Die<Self> {
        dice::just(Self)
    }
}

impl Dice for std::marker::PhantomPinned {
    const USES_LIMIT: bool = false;

    fn die() -> impl Die<Self> {
        dice::just(Self)
    }
}

impl<T: Dice> Dice for Option<T> {
    const USES_LIMIT: bool = T::USES_LIMIT;

    fn die() -> impl Die<Self> {
        dice::option(T::die())
    }
}

impl<T: Dice, E: Dice> Dice for Result<T, E> {
    const USES_LIMIT: bool = T::USES_LIMIT || E::USES_LIMIT;

    fn die() -> impl Die<Self> {
        dice::result(T::die(), E::die())
    }
}

macro_rules! impl_dice_for_wrapper {
    ($type:ty, $new:expr) => {
        impl<T: Dice> Dice for $type {
            const USES_LIMIT: bool = T::USES_LIMIT;

            fn die() -> impl Die<Self> {
                T::die().map($new)
            }
        }
    };
}

impl_dice_for_wrapper!(Box<T>, Self::new);
impl_dice_for_wrapper!(std::rc::Rc<T>, Self::new);
impl_dice_for_wrapper!(std::sync::Arc<T>, Self::new);
impl_dice_for_wrapper!(std::cell::Cell<T>, Self::new);
impl_dice_for_wrapper!(std::cell::UnsafeCell<T>, Self::new);
impl_dice_for_wrapper!(std::cmp::Reverse<T>, Self);

impl<'a, T> Dice for std::borrow::Cow<'a, T>
where
    T: ToOwned + ?Sized,
    T::Owned: Dice,
{
    const USES_LIMIT: bool = T::Owned::USES_LIMIT;

    fn die() -> impl Die<Self> {
        T::Owned::die().map(Self::Owned)
    }
}

impl<T: Dice> Dice for std::cell::OnceCell<T> {
    const USES_LIMIT: bool = T::USES_LIMIT;

    fn die() -> impl Die<Self> {
        dice::option(T::die()).map(|value| {
            let cell = Self::new();
            if let Some(value) = value {
                let _result = cell.set(value);
            }
            cell
        })
    }
}

impl<T: Dice> Dice for std::task::Poll<T> {
    const USES_LIMIT: bool = T::USES_LIMIT;

    fn die() -> impl Die<Self> {
        dice::option(T::die()).map(|value| value.map_or(Self::Pending, Self::Ready))
    }
}

impl Dice for std::cmp::Ordering {
    const USES_LIMIT: bool = false;

    fn die() -> impl Die<Self> {
        dice::one_of().three(
            std::cmp::Ordering::Less,
            std::cmp::Ordering::Equal,
            std::cmp::Ordering::Greater,
        )
    }
}

impl Dice for String {
    const USES_LIMIT: bool = true;

    fn die() -> impl Die<Self> {
        dice::string(char::die(), ..)
    }
}

macro_rules! impl_dice_for_list_like_collection {
    ($type:ty, $builder:expr, $($constraint:ident),*) => {
        impl<T> Dice for $type
        where
            T: Dice $(+ $constraint)*,
        {
            const USES_LIMIT: bool = true;

            fn die() -> impl Die<Self> {
                let t_die = T::die();

                dice::from_fn(move |mut fate| {
                    let builder = fate.roll($builder);
                    if T::USES_LIMIT {
                        fate.roll(dice::outer_collection(builder, &t_die, ..))
                    } else {
                        let t_die = dice::from_fn(|mut fate| fate.with_limit(Limit(0)).roll(&t_die));
                        fate.roll(dice::collection(builder, &t_die, ..))
                    }
                })
            }
        }
    };
}

macro_rules! impl_dice_for_map_like_collection {
    ($type:ty, $builder:expr, $($constraint:ident),*) => {
        impl<K, V> Dice for $type
        where
            K: Dice $(+ $constraint)*,
            V: Dice,
        {
            const USES_LIMIT: bool = true;

            fn die() -> impl Die<Self> {
                let kv_die = <(K, V) as Dice>::die();

                dice::from_fn(move |mut fate| {
                    let builder = fate.roll($builder);
                    if K::USES_LIMIT || V::USES_LIMIT {
                        fate.roll(dice::outer_collection(builder, &kv_die, ..))
                    } else {
                        let kv_die =
                            dice::from_fn(|mut fate| fate.with_limit(Limit(0)).roll(&kv_die));
                        fate.roll(dice::collection(builder, kv_die, ..))
                    }
                })
            }
        }
    };
}

impl_dice_for_list_like_collection!(Vec<T>, dice::just_once(dice::VecBuilder),);
impl_dice_for_list_like_collection!(
    std::collections::VecDeque<T>,
    dice::just_once(dice::VecDequeBuilder),
);
impl_dice_for_list_like_collection!(
    std::collections::LinkedList<T>,
    dice::just_once(dice::LinkedListBuilder),
);
impl_dice_for_list_like_collection!(
    std::collections::BTreeSet<T>,
    dice::just_once(dice::BTreeSetBuilder),
    Ord
);
impl_dice_for_list_like_collection!(
    std::collections::HashSet<T, Prng>,
    dice::from_fn_once(|mut fate| dice::HashSetBuilder::with_hasher(fate.fork_prng())),
    Eq,
    Hash
);
impl_dice_for_list_like_collection!(
    std::collections::HashSet<T>,
    dice::from_fn_once(|_| dice::HashSetBuilder::with_hasher(RandomState::new())),
    Eq,
    Hash
);
impl_dice_for_list_like_collection!(
    std::collections::BinaryHeap<T>,
    dice::just_once(dice::BinaryHeapBuilder),
    Ord
);
impl_dice_for_map_like_collection!(
    std::collections::BTreeMap<K, V>,
    dice::just_once(dice::BTreeMapBuilder),
    Ord
);
impl_dice_for_map_like_collection!(
    std::collections::HashMap<K, V, Prng>,
    dice::from_fn_once(|mut fate| dice::HashMapBuilder::with_hasher(fate.fork_prng())),
    Eq,
    Hash
);
impl_dice_for_map_like_collection!(
    std::collections::HashMap<K, V>,
    dice::from_fn_once(|_| dice::HashMapBuilder::with_hasher(RandomState::new())),
    Eq,
    Hash
);

impl<T: Dice, const N: usize> Dice for [T; N] {
    const USES_LIMIT: bool = T::USES_LIMIT;

    fn die() -> impl Die<Self> {
        let elem_die = T::die();

        dice::from_fn(move |mut fate| {
            if T::USES_LIMIT {
                let limit_parts: [Limit; N] = fate.roll(dice::split_limit(fate.limit()));
                array::from_fn(|i| fate.with_limit(limit_parts[i]).roll(&elem_die))
            } else {
                array::from_fn(|_| fate.with_limit(Limit(0)).roll(&elem_die))
            }
        })
    }
}

macro_rules! impl_dice_for_tuple {
    ($($Ti:ident, $ti:ident, $die_i:ident)+) => {
        impl<$($Ti: Dice,)*> Dice for ($($Ti,)*) {
            const USES_LIMIT: bool = $($Ti::USES_LIMIT ||)* false;

            fn die() -> impl Die<Self> {
                let limit_part_count: usize = $($Ti::USES_LIMIT as usize +)* 0;
                $(let $die_i = $Ti::die();)*

                dice::from_fn(move |mut fate| {
                    if limit_part_count == 0 {
                        $(
                            let $ti = fate.with_limit(Limit(0)).roll(&$die_i);
                        )*

                        ($($ti,)*)
                    } else {
                        let limit = fate.limit();
                        let limit_parts_die = dice::split_limit_n(limit, limit_part_count);
                        let mut limit_parts = fate.with_limit(Limit(0)).roll(limit_parts_die);

                        $(
                            let $ti = if $Ti::USES_LIMIT {
                                let limit = limit_parts.pop().unwrap();
                                fate.with_limit(limit).roll(&$die_i)
                            } else {
                                fate.with_limit(Limit(0)).roll(&$die_i)
                            };
                        )*

                        ($($ti,)*)
                    }
                })
            }
        }
    };
}

impl_dice_for_tuple! {
    T1, t1, die_1
}
impl_dice_for_tuple! {
    T1, t1, die_1
    T2, t2, die_2
}

impl_dice_for_tuple! {
    T1, t1, die_1
    T2, t2, die_2
    T3, t3, die_3
}

impl_dice_for_tuple! {
    T1, t1, die_1
    T2, t2, die_2
    T3, t3, die_3
    T4, t4, die_4
}

impl_dice_for_tuple! {
    T1, t1, die_1
    T2, t2, die_2
    T3, t3, die_3
    T4, t4, die_4
    T5, t5, die_5
}

impl_dice_for_tuple! {
    T1, t1, die_1
    T2, t2, die_2
    T3, t3, die_3
    T4, t4, die_4
    T5, t5, die_5
    T6, t6, die_6
}

impl_dice_for_tuple! {
    T1, t1, die_1
    T2, t2, die_2
    T3, t3, die_3
    T4, t4, die_4
    T5, t5, die_5
    T6, t6, die_6
    T7, t7, die_7
}

impl_dice_for_tuple! {
    T1, t1, die_1
    T2, t2, die_2
    T3, t3, die_3
    T4, t4, die_4
    T5, t5, die_5
    T6, t6, die_6
    T7, t7, die_7
    T8, t8, die_8
}

impl_dice_for_tuple! {
    T1, t1, die_1
    T2, t2, die_2
    T3, t3, die_3
    T4, t4, die_4
    T5, t5, die_5
    T6, t6, die_6
    T7, t7, die_7
    T8, t8, die_8
    T9, t9, die_9
}

#[cfg(test)]
mod tests {
    use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};

    use crate::{Limit, Prng, prelude::*};

    #[derive(Eq, Debug, PartialEq, PartialOrd, Ord)]
    struct WithoutLimit {
        limit: Limit,
    }

    impl Dice for WithoutLimit {
        const USES_LIMIT: bool = false;

        fn die() -> impl Die<Self> {
            dice::from_fn(|fate| WithoutLimit {
                limit: fate.limit(),
            })
        }
    }

    #[derive(Eq, Debug, PartialEq, PartialOrd, Ord)]
    struct WithLimit {
        limit: Limit,
    }

    impl Dice for WithLimit {
        const USES_LIMIT: bool = true;

        fn die() -> impl Die<Self> {
            dice::from_fn(|fate| WithLimit {
                limit: fate.limit(),
            })
        }
    }

    #[test]
    fn unit_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: () = fate.roll(die());
        });
    }

    #[test]
    fn unit_uses_limit() {
        const { assert!(!<Box<()> as Dice>::USES_LIMIT) };
    }

    #[test]
    fn u8_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: u8 = fate.roll(die());
        });
    }

    #[test]
    fn u8_uses_limit() {
        const { assert!(!<Box<u8> as Dice>::USES_LIMIT) };
    }

    #[test]
    fn box_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: Box<u8> = fate.roll(die());
        });
    }

    #[test]
    fn box_uses_limit() {
        const { assert!(!<Box<WithoutLimit> as Dice>::USES_LIMIT) };
        const { assert!(<Box<WithLimit> as Dice>::USES_LIMIT) };
    }

    #[test]
    fn box_limit() {
        Dicetest::repeatedly().run(|mut fate| {
            let limit = fate.limit();

            {
                let elem: Box<WithoutLimit> = fate.roll(die());
                assert_eq!(elem.limit, limit);
            }

            {
                let elem: Box<WithLimit> = fate.roll(die());
                assert_eq!(elem.limit, limit);
            }
        })
    }

    #[test]
    fn vec_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: Vec<u8> = fate.roll(die());
        });
    }

    #[test]
    fn vec_uses_limit() {
        const { assert!(<Vec<WithoutLimit> as Dice>::USES_LIMIT) };
        const { assert!(<Vec<WithLimit> as Dice>::USES_LIMIT) };
    }

    #[test]
    fn vec_limit() {
        Dicetest::repeatedly().run(|mut fate| {
            let limit = fate.limit();

            {
                let vec: Vec<WithoutLimit> = fate.roll(die());

                assert!(vec.len() <= limit.saturating_to_usize());

                for elem in vec {
                    assert_eq!(elem.limit, Limit(0));
                }
            }

            {
                let vec: Vec<WithLimit> = fate.roll(die());

                if !vec.is_empty() {
                    let total_size: u64 = vec.iter().map(|elem| elem.limit.0).sum();
                    assert_eq!(total_size, limit.0);
                }
            }
        })
    }

    #[test]
    fn btree_set_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: BTreeSet<u8> = fate.roll(die());
        });
    }

    #[test]
    fn btree_set_uses_limit() {
        const { assert!(<BTreeSet<WithoutLimit> as Dice>::USES_LIMIT) };
        const { assert!(<BTreeSet<WithLimit> as Dice>::USES_LIMIT) };
    }

    #[test]
    fn btree_set_limit() {
        Dicetest::repeatedly().run(|mut fate| {
            let limit = fate.limit();

            {
                let set: BTreeSet<WithoutLimit> = fate.roll(die());

                assert!(set.len() <= limit.saturating_to_usize());

                for elem in set {
                    assert_eq!(elem.limit, Limit(0));
                }
            }

            {
                let set: BTreeSet<WithLimit> = fate.roll(die());

                if !set.is_empty() {
                    let total_size: u64 = set.iter().map(|elem| elem.limit.0).sum();
                    assert!(total_size <= limit.0);
                }
            }
        })
    }

    #[test]
    fn hash_set_prng_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: HashSet<u8, Prng> = fate.roll(die());
        });
    }

    #[test]
    fn hash_set_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: HashSet<u8> = fate.roll(die());
        });
    }

    #[test]
    fn btree_map_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: BTreeMap<u8, u8> = fate.roll(die());
        });
    }

    #[test]
    fn btree_map_uses_limit() {
        const { assert!(<BTreeMap<WithoutLimit, WithoutLimit> as Dice>::USES_LIMIT) };
        const { assert!(<BTreeMap<WithoutLimit, WithLimit> as Dice>::USES_LIMIT) };
        const { assert!(<BTreeMap<WithLimit, WithoutLimit> as Dice>::USES_LIMIT) };
        const { assert!(<BTreeMap<WithLimit, WithLimit> as Dice>::USES_LIMIT) };
    }

    #[test]
    fn btree_map_limit() {
        Dicetest::repeatedly().run(|mut fate| {
            let limit = fate.limit();

            {
                let map: BTreeMap<WithoutLimit, WithoutLimit> = fate.roll(die());

                assert!(map.len() <= limit.saturating_to_usize());

                for elem in map {
                    assert_eq!(elem.0.limit, Limit(0));
                    assert_eq!(elem.1.limit, Limit(0));
                }
            }

            {
                let map: BTreeMap<WithoutLimit, WithLimit> = fate.roll(die());

                for elem in &map {
                    assert_eq!(elem.0.limit, Limit(0));
                }

                if !map.is_empty() {
                    let total_size: u64 = map.iter().map(|elem| elem.1.limit.0).sum();
                    assert!(total_size <= limit.0);
                }
            }

            {
                let map: BTreeMap<WithLimit, WithoutLimit> = fate.roll(die());

                for elem in &map {
                    assert_eq!(elem.1.limit, Limit(0));
                }

                if !map.is_empty() {
                    let total_size: u64 = map.iter().map(|elem| elem.0.limit.0).sum();
                    assert!(total_size <= limit.0);
                }
            }

            {
                let map: BTreeMap<WithLimit, WithLimit> = fate.roll(die());

                if !map.is_empty() {
                    let total_size: u64 =
                        map.iter().map(|elem| elem.0.limit.0 + elem.1.limit.0).sum();
                    assert!(total_size <= limit.0);
                }
            }
        })
    }

    #[test]
    fn hash_map_prng_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: HashMap<u8, u8, Prng> = fate.roll(die());
        });
    }

    #[test]
    fn hash_map_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: HashMap<u8, u8> = fate.roll(die());
        });
    }

    #[test]
    fn array_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: [u8; 0] = fate.roll(die());
            let _value: [u8; 1] = fate.roll(die());
            let _value: [u8; 2] = fate.roll(die());
            let _value: [u8; 3] = fate.roll(die());
        });
    }

    #[test]
    fn array_uses_limit() {
        const { assert!(!<[WithoutLimit; 0] as Dice>::USES_LIMIT) };
        const { assert!(<[WithLimit; 0] as Dice>::USES_LIMIT) };
        const { assert!(!<[WithoutLimit; 2] as Dice>::USES_LIMIT) };
        const { assert!(<[WithLimit; 2] as Dice>::USES_LIMIT) };
    }

    #[test]
    fn array_limit() {
        Dicetest::repeatedly().run(|mut fate| {
            let limit = fate.limit();

            {
                let array: [WithoutLimit; 3] = fate.roll(die());

                for elem in array {
                    assert_eq!(elem.limit, Limit(0));
                }
            }

            {
                let array: [WithLimit; 3] = fate.roll(die());

                let total_size: u64 = array.iter().map(|elem| elem.limit.0).sum();
                assert_eq!(total_size, limit.0);
            }
        })
    }

    #[test]
    fn tuple_example() {
        Dicetest::repeatedly().run(|mut fate| {
            let _value: (u8,) = fate.roll(die());
            let _value: (u8, u8) = fate.roll(die());
            let _value: (u8, u8, u8) = fate.roll(die());
        });
    }

    #[test]
    fn tuple_uses_limit() {
        const { assert!(!<(WithoutLimit,) as Dice>::USES_LIMIT) };
        const { assert!(<(WithLimit,) as Dice>::USES_LIMIT) };

        const { assert!(!<(WithoutLimit, WithoutLimit) as Dice>::USES_LIMIT) };
        const { assert!(<(WithoutLimit, WithLimit) as Dice>::USES_LIMIT) };
        const { assert!(<(WithLimit, WithoutLimit) as Dice>::USES_LIMIT) };
        const { assert!(<(WithLimit, WithLimit) as Dice>::USES_LIMIT) };

        const { assert!(!<(WithoutLimit, WithoutLimit, WithoutLimit) as Dice>::USES_LIMIT) };
        const { assert!(<(WithoutLimit, WithLimit, WithoutLimit) as Dice>::USES_LIMIT) };
        const { assert!(<(WithLimit, WithoutLimit, WithoutLimit) as Dice>::USES_LIMIT) };
        const { assert!(<(WithLimit, WithLimit, WithLimit) as Dice>::USES_LIMIT) };
    }

    #[test]
    fn tuple_total_size() {
        Dicetest::repeatedly().run(|mut fate| {
            {
                let (a,): (String,) = fate.roll(die());
                let total_size = a.chars().count();
                assert!(total_size <= fate.limit().saturating_to_usize());
            }

            {
                let (a, b): (String, String) = fate.roll(die());
                let total_size = a.chars().count() + b.chars().count();
                assert!(total_size <= fate.limit().saturating_to_usize());
            }

            {
                let (a, b, c): (String, String, String) = fate.roll(die());
                let total_size = a.chars().count() + b.chars().count() + c.chars().count();
                assert!(total_size <= fate.limit().saturating_to_usize());
            }
        })
    }

    #[test]
    fn tuple_limit() {
        Dicetest::repeatedly().run(|mut fate| {
            let limit = fate.limit();

            {
                let (a, b, c): (WithoutLimit, WithoutLimit, WithoutLimit) = fate.roll(die());

                assert_eq!(a.limit, Limit(0));
                assert_eq!(b.limit, Limit(0));
                assert_eq!(c.limit, Limit(0));
            }

            {
                let (a, b, c, d): (WithLimit, WithoutLimit, WithLimit, WithoutLimit) =
                    fate.roll(die());

                assert!(a.limit <= limit);
                assert_eq!(b.limit, Limit(0));
                assert!(c.limit <= limit);
                assert_eq!(d.limit, Limit(0));

                assert_eq!(a.limit.0 + b.limit.0 + c.limit.0 + d.limit.0, limit.0)
            }
        })
    }
}
