use crate::{MarkdownExt, Share, FrontMatter};
use std::cell::{Ref, RefCell};

enum BeforeState {
    Origin,
    Pseudo,
}

/// An iterator places events before the first occurence of certain event.
///
/// This `struct` is created by [`before`] method on [`MarkdownExt`].
///
/// [`before`]: trait.MarkdownExt.html#method.before
/// [`MarkdownExt`]: trait.MarkdownExt.html
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(new)]
pub struct Before<I, S, G, F, P, T>
where
    I: Iterator<Item = T>,
    S: Iterator<Item = T>,
    G: IntoIterator<Item = T, IntoIter = S>,
    F: Fn(Ref<Option<FrontMatter>>) -> G,
    P: Fn(&T) -> bool,
{
    frontmatter: Share<RefCell<Option<FrontMatter>>>,
    iter: I,
    before: P,
    // Create pseudo iter
    f: F,

    #[new(default)]
    pseudo: Option<S>,
    #[new(value = "BeforeState::Origin")]
    state: BeforeState,
    #[new(default)]
    holded: Option<I::Item>,
}

impl<I, S, G, F, P, T> MarkdownExt<T> for Before<I, S, G, F, P, T>
where
    I: Iterator<Item = T>,
    S: Iterator<Item = T>,
    G: IntoIterator<Item = T, IntoIter = S>,
    F: Fn(Ref<Option<FrontMatter>>) -> G,
    P: Fn(&T) -> bool,
{
    fn frontmatter(&mut self) -> &mut Share<RefCell<Option<FrontMatter>>> {
        &mut self.frontmatter
    }
}

impl<I, S, G, F, P, T> Iterator for Before<I, S, G, F, P, T>
where
    I: Iterator<Item = T>,
    S: Iterator<Item = T>,
    G: IntoIterator<Item = T, IntoIter = S>,
    F: Fn(Ref<Option<FrontMatter>>) -> G,
    P: Fn(&T) -> bool,
{
    type Item = I::Item;

    fn next(&mut self) -> Option<Self::Item> {
        match self.state {
            BeforeState::Origin => match self.iter.next()? {
                // Current item match the predication, hold it and call self.
                t if (self.before)(&t) => {
                    self.state = BeforeState::Pseudo;
                    self.holded = Some(t);

                    // Since we've already hand over to Pseudo, it's safe to call itself.
                    self.next()
                }
                otherwise => Some(otherwise),
            },
            BeforeState::Pseudo => match self.pseudo.as_mut() {
                Some(iter) => match iter.next() {
                    node @ Some(_) => node,
                    // We've consumed the pseudo iter, now take the holded value.
                    None => {
                        self.state = BeforeState::Origin;
                        self.holded.take()
                    }
                },
                None => {
                    self.pseudo = Some(
                        (self.f)(self.frontmatter.upgrade().unwrap().borrow()).into_iter(),
                    );
                    // Unwrap the previous created iter and call next on it.
                    // NOTE: If that iter is empty, it might caused unexpected
                    // termination for our iterator.
                    self.pseudo.as_mut().unwrap().next().or_else(|| {
                        self.state = BeforeState::Origin;
                        self.holded.take()
                    })
                }
            },
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::iter::{empty, once};
    use std::rc::Rc;

    #[test]
    fn only_once() {
        assert_eq!(
            Before::new(
                Rc::new(RefCell::new(None)).into(),
                1..5,
                |&n| n % 2 == 0,
                |_| once(0)
            )
            .collect::<Vec<_>>(),
            vec![1, 0, 2, 3, 4],
        );
    }

    #[test]
    fn empty_pseudo() {
        assert_eq!(
            Before::new(
                Rc::new(RefCell::new(None)).into(),
                1..5,
                |&n| n % 2 == 0,
                |_| empty()
            )
            .collect::<Vec<_>>(),
            vec![1, 2, 3, 4],
        );
    }

    #[test]
    fn never_match() {
        assert_eq!(
            Before::new(
                Rc::new(RefCell::new(None)).into(),
                1..5,
                |&n| n % 2 == 5,
                |_| empty()
            )
            .collect::<Vec<_>>(),
            vec![1, 2, 3, 4],
        );
    }

    #[test]
    fn lazy() {
        assert!(Before::new(
            Rc::new(RefCell::new(None)).into(),
            1..5,
            |&n| n % 2 == 5,
            |_| empty()
        )
        .pseudo
        .is_none());
    }
}
