use std::{
    env, fs,
    io::{self, Write},
    path::{Path, PathBuf},
    process::Command,
};

use abi_generator::{
    abi::{ty::TypeWidth, AbiBuilder},
    syntax::UnwrapFancy,
    TargetEnv,
};

fn build_x86_64() {
    const DEFAULT_8086_AS: &str = "nasm";
    const AP_BOOTSTRAP_S: &str = "src/arch/x86_64/boot/ap_boot.S";

    println!("cargo:rerun-if-changed={AP_BOOTSTRAP_S}");

    let out_dir = env::var("OUT_DIR").unwrap();
    let assembler = env::var("AS8086").unwrap_or(DEFAULT_8086_AS.to_owned());

    let ap_bootstrap_out = PathBuf::from(out_dir).join("__x86_64_ap_boot.bin");

    // Assemble the code
    let output = Command::new(assembler.as_str())
        .args([
            "-fbin",
            "-o",
            ap_bootstrap_out.to_str().unwrap(),
            AP_BOOTSTRAP_S,
        ])
        .output()
        .unwrap();

    if !output.status.success() {
        io::stderr().write_all(&output.stderr).ok();
        panic!("{assembler}: could not assemble {AP_BOOTSTRAP_S}");
    }
}

fn load_abi_definitions<P: AsRef<Path>>(path: P) -> String {
    let mut content = String::new();
    for file in fs::read_dir(path).unwrap() {
        let file = file.unwrap();
        if file.path().extension().map(|x| x == "abi").unwrap_or(false) {
            println!("cargo:rerun-if-changed={}", file.path().display());
            content.extend(fs::read_to_string(file.path()));
        }
    }
    content
}

fn generate_syscall_dispatcher<A: AsRef<Path>, P: AsRef<Path>>(
    target_is_64bit: bool,
    abi_path: A,
    out_dir: P,
) {
    let target_env = if target_is_64bit {
        TargetEnv {
            thin_pointer_width: TypeWidth::U64,
            fat_pointer_width: TypeWidth::U128,
        }
    } else {
        TargetEnv {
            thin_pointer_width: TypeWidth::U32,
            fat_pointer_width: TypeWidth::U64,
        }
    };
    let abi = load_abi_definitions(abi_path);
    let abi: AbiBuilder = AbiBuilder::from_string(&abi, target_env).unwrap_fancy(&abi);

    let generated_dispatcher = out_dir.as_ref().join("generated_dispatcher.rs");
    let file = prettyplease::unparse(
        &abi.emit_syscall_dispatcher("handle_syscall", "imp")
            .expect("Could not generate syscall dispatcher"),
    );

    fs::write(generated_dispatcher, file.as_bytes()).unwrap();
}

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
    let target_is_64bit = env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap() == "64";

    generate_syscall_dispatcher(target_is_64bit, "../lib/abi/def", out_dir);

    println!("cargo:rerun-if-changed=build.rs");

    match arch.as_str() {
        "x86" => (),
        "x86_64" => build_x86_64(),
        "aarch64" => (),
        "riscv64" => (),
        _ => panic!("Unknown target arch: {arch:?}"),
    }
}
