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

enum TailState {
    Origin,
    Tail,
}

/// An iterator places events at the end of the original one.
///
/// This `struct` is created by [`tail`] method on [`MarkdownExt`].
///
/// [`tail`]: trait.MarkdownExt.html#method.tail
/// [`MarkdownExt`]: trait.MarkdownExt.html
#[derive(new)]
pub struct Tail<I, H, F, G, T>
where
    G: IntoIterator<Item = T, IntoIter = H>,
    H: Iterator<Item = T>,
    F: Fn(Ref<Option<FrontMatter>>) -> G,
{
    frontmatter: Share<RefCell<Option<FrontMatter>>>,
    origin: I,
    f: F,

    #[new(default)]
    tail: Option<H>,

    #[new(value = "TailState::Origin")]
    state: TailState,
}

impl<I, H, F, G, T> Iterator for Tail<I, H, F, G, T>
where
    I: Iterator<Item = T>,
    H: Iterator<Item = T>,
    G: IntoIterator<Item = T, IntoIter = H>,
    F: Fn(Ref<Option<FrontMatter>>) -> G,
{
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        match self.state {
            TailState::Tail => {
                if self.tail.is_none() {
                    self.tail = Some((self.f)(self.frontmatter.upgrade().unwrap().borrow()).into_iter());
                }
                self.tail.as_mut().unwrap().next()
            }
            TailState::Origin => match self.origin.next() {
                node @ Some(_) => node,
                None => {
                    self.state = TailState::Tail;
                    self.next()
                }
            },
        }
    }
}

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

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

    #[test]
    fn normal() {
        assert_eq!(
            Tail::new(Rc::new(RefCell::new(None)).into(), 1..3, |_| 3..6).collect::<Vec<_>>(),
            vec![1, 2, 3, 4, 5]
        );
    }
}
