// Copyright (c) ZeroC, Inc.

//! This module pulls in the parsing code generated by LALRPOP and contains private helper functions used by it.
//!
//! While many of these functions could be written directly into the parser rules, we implement them here instead, to
//! keep the rules focused on grammar instead of implementation details, making the grammar easier to read and modify.

use super::super::common::SourceBlock;
use super::parser::Preprocessor;
use super::tokens::{Error, TokenKind};
use super::Location;
use std::collections::HashSet;

use lalrpop_util::{lalrpop_mod, ErrorRecovery};

// Place the code generated by LALRPOP into a submodule named 'lalrpop'.
lalrpop_mod!(
    #[allow(unused, clippy::all)] // LALRPOP generates stuff we don't use, and isn't worth linting.
    pub lalrpop,
    "/parsers/preprocessor/grammar.rs"
);

pub type Recovery<'a> = ErrorRecovery<Location, TokenKind<'a>, Error>;

// Preprocessor AST

pub enum Node<'a> {
    SourceBlock(SourceBlock<'a>),
    DefineDirective(&'a str),
    UndefineDirective(&'a str),
    Conditional(Conditional<'a>),
}

pub struct Conditional<'a> {
    pub if_section: (Expression<'a>, Vec<Node<'a>>),
    pub elif_sections: Vec<(Expression<'a>, Vec<Node<'a>>)>,
    pub else_section: Option<Vec<Node<'a>>>,
}

impl<'a> Conditional<'a> {
    pub fn evaluate(self, defined_symbols: &HashSet<String>) -> Vec<Node<'a>> {
        let (if_condition, if_block) = self.if_section;
        if if_condition.evaluate(defined_symbols) {
            return if_block;
        }

        for (elif_condition, elif_block) in self.elif_sections {
            if elif_condition.evaluate(defined_symbols) {
                return elif_block;
            }
        }

        self.else_section.unwrap_or_default()
    }
}

pub enum Expression<'a> {
    Term(Term<'a>),
    Not(Term<'a>),
    And(Box<Expression<'a>>, Term<'a>),
    Or(Box<Expression<'a>>, Term<'a>),
}

impl Expression<'_> {
    pub fn evaluate(self, defined_symbols: &HashSet<String>) -> bool {
        match self {
            Self::Term(term) => term.evaluate(defined_symbols),
            Self::Not(term) => !term.evaluate(defined_symbols),
            Self::And(expression, term) => expression.evaluate(defined_symbols) && term.evaluate(defined_symbols),
            Self::Or(expression, term) => expression.evaluate(defined_symbols) || term.evaluate(defined_symbols),
        }
    }
}

pub enum Term<'a> {
    Symbol(&'a str),
    Expression(Box<Expression<'a>>),
}

impl Term<'_> {
    pub fn evaluate(self, defined_symbols: &HashSet<String>) -> bool {
        match self {
            Self::Symbol(symbol) => defined_symbols.contains(symbol),
            Self::Expression(expression) => expression.evaluate(defined_symbols),
        }
    }
}

// Grammar Rule Functions

pub fn process_nodes<'a>(
    nodes: Vec<Node<'a>>,
    source_blocks: &mut Vec<SourceBlock<'a>>,
    preprocessor: &mut Preprocessor<'_>,
) {
    for node in nodes {
        match node {
            Node::SourceBlock(source_block) => source_blocks.push(source_block),
            Node::DefineDirective(symbol) => {
                preprocessor.defined_symbols.insert(symbol.to_owned());
            }
            Node::UndefineDirective(symbol) => {
                preprocessor.defined_symbols.remove(symbol);
            }
            Node::Conditional(conditional) => {
                let conditional_nodes = conditional.evaluate(preprocessor.defined_symbols);
                process_nodes(conditional_nodes, source_blocks, preprocessor);
            }
        }
    }
}

fn recover_from_error(preprocessor: &mut Preprocessor, recovery: Recovery) {
    // Report the syntax error.
    let diagnostic = super::construct_error_from(recovery.error, preprocessor.file_name);
    diagnostic.push_into(preprocessor.diagnostics);
}
