// Copyright (c) ZeroC, Inc.

pub mod ast;
pub mod compilation_state;
pub mod diagnostic_emitter;
pub mod diagnostics;
pub mod grammar;
pub mod slice_file;
pub mod slice_options;
pub mod supported_encodings;
pub mod test_helpers;
pub mod utils;
pub mod visitor;

mod parsers;
mod patchers;
mod validators;

use compilation_state::CompilationState;
use slice_file::SliceFile;
use slice_options::SliceOptions;
use std::collections::HashSet;
use utils::file_util;

pub fn compile_from_options(
    options: &SliceOptions,
    patcher: unsafe fn(&mut CompilationState),
    validator: fn(&mut CompilationState),
) -> CompilationState {
    // Create an instance of `CompilationState` for holding all the compiler's state.
    let mut state = CompilationState::create();

    // Recursively resolve any Slice files contained in the paths specified by the user.
    state.files = file_util::resolve_files_from(options, &mut state.diagnostics);

    // If any files were unreadable, return without parsing. Otherwise, parse the files normally.
    if !state.diagnostics.has_errors() {
        compile_files(&mut state, options, patcher, validator);
    }
    state
}

pub fn compile_from_strings(
    inputs: &[&str],
    options: Option<&SliceOptions>,
    patcher: unsafe fn(&mut CompilationState),
    validator: fn(&mut CompilationState),
) -> CompilationState {
    // Create an instance of `CompilationState` for holding all the compiler's state.
    let mut state = CompilationState::create();

    // Create a Slice file from each of the strings.
    for (i, &input) in inputs.iter().enumerate() {
        let slice_file = SliceFile::new(format!("string-{i}"), input.to_owned(), false);
        state.files.push(slice_file);
    }

    match options {
        Some(slice_options) => compile_files(&mut state, slice_options, patcher, validator),
        None => compile_files(&mut state, &SliceOptions::default(), patcher, validator),
    }

    state
}

fn compile_files(
    state: &mut CompilationState,
    options: &SliceOptions,
    patcher: unsafe fn(&mut CompilationState),
    validator: fn(&mut CompilationState),
) {
    // Retrieve any preprocessor symbols defined by the compiler itself, or by the user on the command line.
    let defined_symbols = HashSet::from_iter(options.defined_symbols.clone());

    // There are several phases of compilation handled by `slicec`:
    // 1) Parse the files passed in by the user.
    // 2) Patch the abstract syntax tree generated by the parser.
    // 3) Apply the user-provided patching function.
    // 4) Validate the AST, checking for language-mapping agnostic errors.
    // 5) Apply the user-provided validation function.
    parsers::parse_files(state, &defined_symbols);

    unsafe { state.apply_unsafe(patchers::patch_ast) };
    unsafe { state.apply_unsafe(patcher) };

    state.apply(validators::validate_ast);
    state.apply(validator);
}
