pub mod symbol;
use logid::capturing::MappedLogId;
use symbol::Symbol;
use crate::{
    config::Config,
    document::Document,
    elements::{
        atomic::{Heading, Paragraph},
        enclosed::Verbatim,
        Blocks,
    },
    metadata::{Metadata, MetadataKind},
    security,
};
use self::symbol::{IntoSymbols, SymbolKind};
pub type ParserFn = for<'i> fn(&'i [Symbol<'i>]) -> Option<(Blocks, &'i [Symbol<'i>])>;
pub(crate) struct TokenizeOutput<'a, T>
where
    T: 'a,
{
    pub(crate) tokens: Vec<T>,
    pub(crate) rest_of_input: &'a [Symbol<'a>],
}
pub(crate) trait ElementParser {
    type Token<'a>;
    fn tokenize<'i>(input: &'i [Symbol<'i>]) -> Option<TokenizeOutput<'i, Self::Token<'i>>>;
    fn parse(input: Vec<Self::Token<'_>>) -> Option<Blocks>;
}
mod private {
    pub trait Sealed {}
    impl<'a, T> Sealed for T where T: super::ElementParser + 'a + 'static {}
}
pub trait ParserGenerator: private::Sealed {
    fn generate_parser() -> ParserFn;
}
impl<'a, T> ParserGenerator for T
where
    T: ElementParser + 'a + 'static,
{
    fn generate_parser() -> ParserFn {
        |input| {
            let tokenize_output = T::tokenize(input)?;
            let blocks = T::parse(tokenize_output.tokens)?;
            Some((blocks, tokenize_output.rest_of_input))
        }
    }
}
#[derive(Clone)]
pub struct MainParser {
    parsers: Vec<ParserFn>,
    default_parser: ParserFn,
}
impl Default for MainParser {
    fn default() -> Self {
        tracing::info!("Initializing MainParser");
        let default = Paragraph::generate_parser();
        let mut parser = Self {
            parsers: Vec::with_capacity(2),
            default_parser: default,
        };
        parser.register_parser(Heading::generate_parser());
        parser.register_parser(Verbatim::generate_parser());
        tracing::info!("MainParser initialized");
        parser
    }
}
impl MainParser {
    fn register_parser(&mut self, parser: ParserFn) {
        self.parsers.push(parser);
    }
    pub fn parse<'s>(&self, input: impl IntoSymbols<'s, &'s [Symbol<'s>]>) -> Blocks {
        let mut input = input.into_symbols();
        let mut blocks = Vec::default();
        #[cfg(debug_assertions)]
        let mut input_len = input.len();
        'outer: while let Some(sym) = input.first() {
            match sym.kind {
                SymbolKind::Blankline => input = &input[1..],
                SymbolKind::EOI => break,
                _ if sym.is_not_keyword() => {
                    let (mut res_blocks, rest_of_input) = (self.default_parser)(input)
                        .expect("Default parser could not parse content!");
                    blocks.append(&mut res_blocks);
                    input = rest_of_input;
                }
                _ => {
                    for parser_fn in &self.parsers {
                        if let Some((mut res_blocks, rest_of_input)) = parser_fn(input) {
                            blocks.append(&mut res_blocks);
                            input = rest_of_input;
                            continue 'outer; }
                    }
                    let (mut res_blocks, rest_of_input) = (self.default_parser)(input)
                        .expect("Default parser could not parse content!");
                    blocks.append(&mut res_blocks);
                    input = rest_of_input;
                }
            }
            #[cfg(debug_assertions)]
            {
                assert_ne!(input.len(), input_len);
                input_len = input.len();
            }
        }
        blocks
    }
}
pub fn parse_unimarkup(um_content: &str, config: &mut Config) -> Result<Document, MappedLogId> {
    let parser = MainParser::default();
    let symbols = um_content.into_symbols();
    let blocks = parser.parse(&symbols);
    let mut unimarkup = Document {
        config: config.clone(),
        blocks,
        ..Default::default()
    };
    let metadata = Metadata {
        file: config.um_file.clone(),
        contenthash: security::get_contenthash(um_content),
        preamble: String::new(),
        kind: MetadataKind::Root,
        namespace: ".".to_string(),
    };
    unimarkup.metadata.push(metadata);
    Ok(unimarkup)
}