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

enum WithinState {
    External,
    Internal,
}

/// An iterator maps function upon events within two specific events.
///
/// This `struct` is created by [`within`] method on [`MarkdownExt`].
///
/// [`within`]: trait.MarkdownExt.html#method.within
/// [`MarkdownExt`]: trait.MarkdownExt.html
#[derive(new)]
pub struct Within<I, P, Q, F, T>
where
    I: Iterator<Item = T>,
    P: Fn(&T) -> bool,
    Q: Fn(&T) -> bool,
    F: FnMut(Ref<Option<FrontMatter>>, T) -> Option<T>,
{
    frontmatter: Share<RefCell<Option<FrontMatter>>>,
    iter: I,
    start: P,
    end: Q,
    f: F,

    #[new(value = "WithinState::External")]
    state: WithinState,
}

impl<I, P, Q, F, T> Iterator for Within<I, P, Q, F, T>
where
    I: Iterator<Item = T>,
    P: Fn(&T) -> bool,
    Q: Fn(&T) -> bool,
    F: FnMut(Ref<Option<FrontMatter>>, T) -> Option<T>,
{
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        match self.state {
            WithinState::External => match self.iter.next() {
                Some(e) if (self.start)(&e) => {
                    self.state = WithinState::Internal;
                    Some(e)
                }
                node => node,
            },
            WithinState::Internal => match self.iter.next() {
                Some(e) if (self.end)(&e) => {
                    self.state = WithinState::External;
                    Some(e)
                }
                Some(e) => match (self.f)(self.frontmatter.upgrade().unwrap().borrow(), e) {
                    node @ Some(_) => node,
                    None => self.next(),
                },
                node => node,
            },
        }
    }
}

impl<I, P, Q, F, T> MarkdownExt<T> for Within<I, P, Q, F, T>
where
    I: Iterator<Item = T>,
    P: Fn(&T) -> bool,
    Q: Fn(&T) -> bool,
    F: FnMut(Ref<Option<FrontMatter>>, T) -> Option<T>,
{
    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!(
            Within::new(
                Rc::new(RefCell::new(None)).into(),
                1..9,
                |&n| n == 3,
                |&n| n == 6,
                |_, n| Some(n + 1)
            ).collect::<Vec<_>>(),
            vec![1, 2, 3, 5, 6, 6, 7, 8]
        );
    }
}
