use crate::MarkdownExt;
use equt_md_frontmatter::FrontMatter;

/// An iterator places a pseudo events iterator after the first occurence of certain event.
/// This `struct` is created by [`after`] method on [`MarkdownExt`].
///
/// [`after`]: trait.MarkdownExt.html#method.after
/// [`MarkdownExt`]: trait.MarkdownExt.html
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct After<I, S, P> {
    iter: I,
    frontmatter: Option<FrontMatter>,
    pseudo: S,
    after: P,
    return_pseudo: bool,
    consumed: bool,
}

impl<I, S, P, T> After<I, S, P>
where
    I: Iterator<Item = T>,
    S: Iterator<Item = T>,
    P: Fn(&T) -> bool,
{
    pub(crate) fn new(
        iter: I,
        frontmatter: Option<FrontMatter>,
        after: P,
        pseudo: impl IntoIterator<Item = T, IntoIter = S>,
    ) -> Self {
        Self {
            iter,
            frontmatter,
            pseudo: pseudo.into_iter(),
            after,
            return_pseudo: false,
            consumed: false,
        }
    }
}

impl<I, S, P, T> MarkdownExt<T> for After<I, S, P>
where
    I: Iterator<Item = T>,
    S: Iterator<Item = T>,
    P: Fn(&T) -> bool,
{
    fn frontmatter(&mut self) -> Option<FrontMatter> {
        std::mem::replace(&mut self.frontmatter, None)
    }
}

impl<I, S, P, T> Iterator for After<I, S, P>
where
    I: Iterator<Item = T>,
    S: Iterator<Item = T>,
    P: Fn(&T) -> bool,
{
    type Item = I::Item;

    fn next(&mut self) -> Option<Self::Item> {
        if self.return_pseudo {
            if let Some(next) = self.pseudo.next() {
                return Some(next);
            } else {
                self.return_pseudo = false;
                self.consumed = true;
            }
        }
        let next = self.iter.next()?;
        if !self.consumed && (self.after)(&next) {
            self.return_pseudo = true;
            Some(next)
        } else {
            Some(next)
        }
    }
}

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

    #[test]
    fn after_iter() {
        let result: Vec<u8> =
            After::new((1..4).chain(7..10).chain(1..4), None, |&n| n == 3, 4..7).collect();
        assert_eq!(result, (1..10).chain(1..4).collect::<Vec<_>>());
    }
}
