use crate::{
    generate::{Generate, State},
    Shrink,
};

#[derive(Clone, Debug, Default)]
pub struct Flatten<T: ?Sized>(pub T);

#[derive(Clone, Debug)]
pub struct Shrinker<I, O> {
    state: State,
    inner: I,
    outer: O,
}

impl<G: Generate<Item = impl Generate> + ?Sized> Generate for Flatten<G> {
    type Item = <G::Item as Generate>::Item;
    type Shrink = Shrinker<<G::Item as Generate>::Shrink, G::Shrink>;

    fn generate(&self, state: &mut State) -> Self::Shrink {
        let old = state.clone();
        let outer = self.0.generate(state);
        let item = outer.item();
        state.count += 1;
        state.depth += 1;
        let inner = item.generate(state);
        state.depth -= 1;
        Shrinker {
            state: old,
            inner,
            outer,
        }
    }
}

impl<I: Shrink, O: Shrink<Item = impl Generate<Shrink = I>>> Shrink for Shrinker<I, O> {
    type Item = I::Item;

    fn item(&self) -> Self::Item {
        self.inner.item()
    }

    fn shrink(&mut self) -> Option<Self> {
        if let Some(outer) = self.outer.shrink() {
            let mut state = self.state.clone();
            let inner = outer.item().generate(&mut state);
            return Some(Self {
                state,
                outer,
                inner,
            });
        }

        if let Some(inner) = self.inner.shrink() {
            return Some(Self {
                state: self.state.clone(),
                outer: self.outer.clone(),
                inner,
            });
        }

        None
    }
}
