use pest::Parser;
use pest_derive::Parser;

pub mod ast;
pub mod error;
pub mod parser;
pub mod rev_mapper;
pub mod semantics;

use ast::*;

use crate::error::{IntoErr, ParseError};

#[derive(Parser)]
#[grammar = "./src/grammar.pest"]
pub struct MistParser;

pub struct Program {
    pub mod_attributes: Vec<TopLevel>,
    pub items: Vec<TopLevel>,
}

pub fn parse<'a>(source: &'a str) -> Result<Program, ParseError<'a, Vec<TopLevel>>> {
    let mut pairs = MistParser::parse(Rule::program, source)?;

    let mut statements = vec![];

    let mut analyzer = error::AstErrorAnalyzer(None);

    for pair in pairs.next().unwrap().into_inner() {
        if pair.as_rule() != Rule::EOI {
            statements.push(analyzer.get(TopLevel::try_from(pair)).get()?);
        }
    }

    match analyzer.build(statements) {
        Ok(v) => {
            let (mod_attributes, items): (Vec<_>, Vec<_>) = v
                .into_iter()
                .partition(|item| matches!(item.0.item, TopLevelKind::ModAttribute));

            Ok(Program {
                items,
                mod_attributes,
            })
        }
        Err(e) => {
            let rec = e.recovered.clone();

            let mut e2 = e.get();

            e2.recovered = rec;

            Err(ParseError::Ast(e2))
        }
    }
}

pub fn parse_module<'a>(
    source: &'a str,
) -> Result<Option<(Visibility, Identifier)>, ParseError<'a, Option<(Visibility, Identifier)>>> {
    let mut pairs = MistParser::parse(Rule::module_program, source)?;

    if let Some(v) = pairs
        .next()
        .unwrap()
        .into_inner()
        .next()
        .map(TopLevel::try_from)
        .transpose()
        .get()?
    {
        if let TopLevelKind::DeclareModule(vis, name) = &v.0.item {
            Ok(Some((vis.clone(), name.clone())))
        } else {
            Ok(None)
        }
    } else {
        Ok(None)
    }
}

#[macro_export]
macro_rules! ast_ensure {
    ($pair:expr, $rule:expr $(, $rules:expr)* => $body:block) => {
        if $pair.as_rule() == $rule $(|| $pair.as_rule() == $rules)*
            $body
        else {
            Err(AstError {
                span: $pair.as_span(),
                error_code: crate::error::ErrorCode::AstGenBug,
                error_message: format!("Possible bug: expected {:?}, got {:?}", $rule, $pair.as_rule()),
                recovered: None,
            })
        }
    };
}

#[macro_export]
macro_rules! ast_expr {
    (use $r:expr, $($v:expr),* $(,)?) => {{
        let mut analyzer = $crate::error::AstErrorAnalyzer(None);

        $(
            analyzer.get($v).get()?;
        )*

        analyzer.build($r)
    }};

    ($($item:ident)::+ { $($k:ident: $v:expr),* $(,)? }) => {{
        let mut analyzer = $crate::error::AstErrorAnalyzer(None);

        let v = $($item)::+ { $($k: analyzer.get($v).get()?),* };

        analyzer.build(v)
    }};

    ($($item:ident)::+ ( $($v:expr),* $(,)? )) => {{
        let mut analyzer = $crate::error::AstErrorAnalyzer(None);

        let v = $($item)::+ ( $(analyzer.get($v).get()?),* );

        analyzer.build(v)
    }};

    (( $($v:expr),* $(,)? )) => {{
        let mut analyzer = $crate::error::AstErrorAnalyzer(None);

        let v = ( $(analyzer.get($v).get()?),* );

        analyzer.build(v)
    }};

    ($($item:ident)::+) => {
        $($item)::+
    };
}
