mod after;
mod before;
mod bottom;
mod head;
mod link;
mod parser;
mod raw;
mod share;
mod tail;
mod within;

pub use after::After;
pub use before::Before;
pub use bottom::Bottom;
pub use head::Head;
pub use link::Link;
pub use parser::Parser;
pub use raw::Raw;
pub use tail::Tail;
pub use within::Within;
pub use share::Share;

use crate::FrontMatter;
use std::cell::{Ref, RefCell};
use std::rc::Rc;

/// Extending the original markdown events iterator
///
/// All provided methods in this trait will keep the frontmatter data internally.
///
/// Any closures that needs frontmatter as an argument are lazy. With the laziness, the
/// frontmatter doesn't have to be presented first, which means it could be updated or
/// get replaced during the process.
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub trait MarkdownExt<T>: Iterator<Item = T> + Sized {
    /// Different from a normal iterator, `MarkdownExt` must own the frontmatter
    /// data.
    fn frontmatter(&mut self) -> &mut Share<RefCell<Option<FrontMatter>>>;

    /// Inspect the current frontmatter
    ///
    /// ## Example
    ///
    /// ```
    /// # use mercurius::{Bottom, FrontMatter, MarkdownExt};
    /// # fn e() -> impl MarkdownExt<u8> {
    /// # let events: Bottom<u8> = Bottom::new();
    /// let mut fm: Option<FrontMatter> = None;
    /// events.inspect_frontmatter(&mut fm)
    /// # }
    /// ```
    ///
    /// The result would be a clone so it's still available even after the whole
    /// [`MarkdownExt`] chain has been consumed.
    ///
    /// [`MarkdownExt`]: trait.MarkdownExt.html
    fn inspect_frontmatter(mut self, frontmatter: &mut Option<FrontMatter>) -> Raw<Self, T> {
        // 3. Create a new iterator with data
        let curr = Rc::try_unwrap(self.frontmatter().transfer().unwrap())
            .unwrap()
            .into_inner();
        *frontmatter = curr.clone();
        Raw::new(Rc::new(RefCell::new(curr)).into(), self)
    }

    /// Place events after the _first_ occurence of a certain event.
    ///
    /// ## Example
    ///
    /// ```
    /// # use mercurius::{Bottom, Event, Tag, FrontMatter, MarkdownExt};
    /// # use std::cell::Ref;
    /// # use std::iter::{empty, Empty};
    /// # let events = Bottom::new();
    /// # fn days_since_last_update<'e>(_: Ref<Option<FrontMatter>>) -> Empty<Event<'e>> { empty() }
    /// let outdate = events.after(|e| match &e {
    ///     Event::End(Tag::Heading(1)) => true,
    ///     _ => false,
    /// }, |frontmatter| days_since_last_update(frontmatter));
    /// ```
    ///
    /// Assume the `days_since_last_update` could help create a series of events that notify
    /// the reader of the number of the days since the last update. The `after` could place the
    /// new events right after the first heading.
    fn after<P, F, S, G>(mut self, after: P, f: F) -> After<Self, S, G, F, P, T>
    where
        P: Fn(&T) -> bool,
        S: Iterator<Item = T>,
        G: IntoIterator<Item = T, IntoIter = S>,
        F: Fn(Ref<Option<FrontMatter>>) -> G,
    {
        After::new(
            self.frontmatter().transfer().unwrap().into(),
            self,
            after,
            f,
        )
    }

    /// Place events before the first occurence of a certain event.
    ///
    /// ## Example
    ///
    /// ```
    /// # use mercurius::{Bottom, Event, Tag, FrontMatter, MarkdownExt};
    /// # use std::cell::Ref;
    /// # use std::iter::{empty, Empty};
    /// # let events = Bottom::new();
    /// # fn disclaimer<'e>(_: Ref<Option<FrontMatter>>) -> Empty<Event<'e>> { empty() }
    /// let official = events.before(|e| match &e {
    ///     Event::Start(Tag::Heading(1)) => true,
    ///     _ => false,
    /// }, |frontmatter| disclaimer(frontmatter));
    /// ```
    ///
    /// Assume the `disclaimer` could create some events and disclaim something important.
    /// The `before` here places it before the first heading.
    fn before<P, F, S, G>(mut self, before: P, f: F) -> Before<Self, S, G, F, P, T>
    where
        P: Fn(&T) -> bool,
        S: Iterator<Item = T>,
        G: IntoIterator<Item = T, IntoIter = S>,
        F: Fn(Ref<Option<FrontMatter>>) -> G,
    {
        Before::new(
            self.frontmatter().transfer().unwrap().into(),
            self,
            before,
            f,
        )
    }

    /// Just like the standard [`chain`] for iterator, the frontmatter will get transferred
    /// by the following rules.
    ///
    /// - If both have frontmatter, the latter one would be picked
    /// - If neither has frontmatter, it would be `None`
    /// - Otherwise, the only frontmatter would be used
    ///
    /// [`chain`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.chain
    ///
    /// ## Example
    ///
    /// ```
    /// # use mercurius::{Bottom, Event, Parser, MarkdownExt, Result};
    /// # use std::cell::Ref;
    /// # use std::iter::{empty, Empty};
    /// # fn main() -> Result<()> {
    /// # let events: Bottom<Event<'_>> = Bottom::new();
    /// # let text = "";
    /// let new_events = events.link(Parser::new(text)?);
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// New events parsed from `text` will be placed after the original events.
    fn link<E>(mut self, mut other: E) -> Link<Self, E>
    where
        E: MarkdownExt<T>,
    {
        // Both should be strong
        let right = other.frontmatter();
        let left = self.frontmatter();

        // Set both sides to weak and take the only strong one
        let strong = if right.upgrade().unwrap().borrow().is_some() {
            *left = right.downgrade().into();
            right.transfer().unwrap()
        } else {
            *right = left.downgrade().into();
            left.transfer().unwrap()
        };

        Link::new(strong.into(), self, other)
    }

    /// Like [`link`], but accept an [`Iterator`].
    ///
    /// For dynamically generating events, consider the [`tail`] method.
    ///
    /// [`link`]: trait.MarkdownExt.html#method.link
    /// [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
    /// [`tail`]: trait.MarkdownExt.html#method.tail
    fn iter<I>(self, iter: I) -> Link<Self, Raw<I, T>>
    where
        I: Iterator<Item = T>,
    {
        self.link(Raw::new(Rc::new(RefCell::new(None)).into(), iter))
    }

    /// Place events at the start.
    ///
    /// ## Example
    ///
    /// ```
    /// # use mercurius::{Bottom, Event, FrontMatter, MarkdownExt};
    /// # use std::cell::Ref;
    /// # use std::iter::{empty, Empty};
    /// # let events = Bottom::new();
    /// # fn heading<'e>(_: Ref<Option<FrontMatter>>) -> Empty<Event<'e>> { empty() }
    /// let with_heading = events.head(|frontmatter| heading(frontmatter));
    /// ```
    ///
    /// Assume `heading` reads the `Title` section in the frontmatter and generate a heading
    /// event, the `head` will place it at the start of the **current** iterator.
    fn head<F, G, H>(mut self, f: F) -> Head<Self, H, F, G, T>
    where
        H: Iterator<Item = T>,
        G: IntoIterator<Item = T, IntoIter = H>,
        F: Fn(Ref<Option<FrontMatter>>) -> G,
    {
        Head::new(self.frontmatter().transfer().unwrap().into(), self, f)
    }

    /// Place events at the end.
    ///
    /// ## Example
    ///
    /// ```
    /// # use mercurius::{Bottom, Event, FrontMatter, MarkdownExt};
    /// # use std::cell::Ref;
    /// # use std::iter::{empty, Empty};
    /// # let events = Bottom::new();
    /// # fn copyright<'e>(_: Ref<Option<FrontMatter>>) -> Empty<Event<'e>> { empty() }
    /// let with_copyright = events.tail(|frontmatter| copyright(frontmatter));
    /// ```
    ///
    /// Assume `copyright` reads the meta data in the frontmatter and generate copyright related
    /// event, the `tail` will place it at the end of the **current** iterator.
    fn tail<F, G, H>(mut self, f: F) -> Tail<Self, H, F, G, T>
    where
        H: Iterator<Item = T>,
        G: IntoIterator<Item = T, IntoIter = H>,
        F: Fn(Ref<Option<FrontMatter>>) -> G,
    {
        Tail::new(self.frontmatter().transfer().unwrap().into(), self, f)
    }

    /// Maps function upon events within two specific events.
    ///
    /// ## Example
    ///
    /// ```
    /// # use mercurius::{Bottom, Event, Tag, MarkdownExt};
    /// # let events = Bottom::new();
    /// let uppercase = events.within(|e| match &e {
    ///     Event::Start(Tag::Heading(_)) => true,
    ///     _ => false,
    /// }, |e| match &e {
    ///     Event::End(Tag::Heading(_)) => true,
    ///     _ => false,
    /// }, |_, e| Some(match e {
    ///     Event::Text(s) => Event::Text(s.into_string().to_uppercase().into()),
    ///     event => event,
    /// }));
    /// ```
    ///
    /// The above code will turn all text in heading to uppercase.
    fn within<P, Q, F>(mut self, start: P, end: Q, f: F) -> Within<Self, P, Q, F, T>
    where
        P: Fn(&T) -> bool,
        Q: Fn(&T) -> bool,
        F: Fn(Ref<Option<FrontMatter>>, T) -> Option<T>,
    {
        Within::new(
            self.frontmatter().transfer().unwrap().into(),
            self,
            start,
            end,
            f,
        )
    }
}
