use crate::common::intrinsic::Intrinsic;

use super::argument::Argument;
use super::compare::INTRINSIC_DELIMITER;
use super::indentation::Indentation;
use super::intrinsic_helpers::IntrinsicTypeDefinition;

// The number of times each intrinsic will be called.
const PASSES: u32 = 20;
const COMMON_HEADERS: [&str; 7] = [
    "iostream",
    "string",
    "cstring",
    "iomanip",
    "sstream",
    "type_traits",
    "cassert",
];

pub fn generate_c_test_loop<T: IntrinsicTypeDefinition + Sized>(
    w: &mut impl std::io::Write,
    intrinsic: &Intrinsic<T>,
    indentation: Indentation,
    additional: &str,
    passes: u32,
) -> std::io::Result<()> {
    let body_indentation = indentation.nested();
    writeln!(
        w,
        "{indentation}for (int i=0; i<{passes}; i++) {{\n\
            {loaded_args}\
            {body_indentation}auto __return_value = {intrinsic_call}({args});\n\
            {print_result}\n\
        {indentation}}}",
        loaded_args = intrinsic.arguments.load_values_c(body_indentation),
        intrinsic_call = intrinsic.name,
        args = intrinsic.arguments.as_call_param_c(),
        print_result = intrinsic
            .results
            .print_result_c(body_indentation, additional)
    )
}

pub fn generate_c_constraint_blocks<'a, T: IntrinsicTypeDefinition + 'a>(
    w: &mut impl std::io::Write,
    intrinsic: &Intrinsic<T>,
    indentation: Indentation,
    constraints: &mut (impl Iterator<Item = &'a Argument<T>> + Clone),
    name: String,
) -> std::io::Result<()> {
    let Some(current) = constraints.next() else {
        return generate_c_test_loop(w, intrinsic, indentation, &name, PASSES);
    };

    let body_indentation = indentation.nested();
    for i in current.constraint.iter().flat_map(|c| c.iter()) {
        let ty = current.ty.c_type();

        writeln!(w, "{indentation}{{")?;

        // TODO: Move to actually specifying the enum value
        // instead of typecasting integers, for better clarity
        // of generated code.
        writeln!(
            w,
            "{body_indentation}const {ty} {} = ({ty}){i};",
            current.generate_name()
        )?;

        generate_c_constraint_blocks(
            w,
            intrinsic,
            body_indentation,
            &mut constraints.clone(),
            format!("{name}-{i}"),
        )?;

        writeln!(w, "{indentation}}}")?;
    }

    Ok(())
}

// Compiles C test programs using specified compiler
pub fn create_c_test_function<T: IntrinsicTypeDefinition>(
    w: &mut impl std::io::Write,
    intrinsic: &Intrinsic<T>,
) -> std::io::Result<()> {
    let indentation = Indentation::default();

    writeln!(w, "int run_{}() {{", intrinsic.name)?;

    // Define the arrays of arguments.
    let arguments = &intrinsic.arguments;
    arguments.gen_arglists_c(w, indentation.nested(), PASSES)?;

    generate_c_constraint_blocks(
        w,
        intrinsic,
        indentation.nested(),
        &mut arguments.iter().rev().filter(|&i| i.has_constraint()),
        Default::default(),
    )?;

    writeln!(w, "    return 0;")?;
    writeln!(w, "}}")?;

    Ok(())
}

pub fn write_mod_cpp<T: IntrinsicTypeDefinition>(
    w: &mut impl std::io::Write,
    notice: &str,
    platform_headers: &[&str],
    forward_declarations: &str,
    intrinsics: &[Intrinsic<T>],
) -> std::io::Result<()> {
    write!(w, "{notice}")?;

    for header in COMMON_HEADERS.iter().chain(platform_headers.iter()) {
        writeln!(w, "#include <{header}>")?;
    }

    writeln!(w, "{}", forward_declarations)?;

    for intrinsic in intrinsics {
        create_c_test_function(w, intrinsic)?;
    }

    Ok(())
}

pub fn write_main_cpp<'a>(
    w: &mut impl std::io::Write,
    arch_specific_definitions: &str,
    arch_specific_headers: &[&str],
    intrinsics: impl Iterator<Item = &'a str> + Clone,
) -> std::io::Result<()> {
    for header in COMMON_HEADERS.iter().chain(arch_specific_headers.iter()) {
        writeln!(w, "#include <{header}>")?;
    }

    // NOTE: It's assumed that this value contains the required `ifdef`s.
    writeln!(w, "{arch_specific_definitions }")?;

    for intrinsic in intrinsics.clone() {
        writeln!(w, "extern int run_{intrinsic}(void);")?;
    }

    writeln!(w, "int main(int argc, char **argv) {{")?;

    for intrinsic in intrinsics {
        writeln!(
            w,
            "    std::cout << \"{INTRINSIC_DELIMITER}\" << std::endl;"
        )?;
        writeln!(w, "    std::cout << \"{intrinsic}\" << std::endl;")?;
        writeln!(w, "    run_{intrinsic}();\n")?;
    }

    writeln!(w, "    return 0;")?;

    writeln!(w, "}}")?;

    Ok(())
}
