use crate::{build::toolchain, env::Arch, error::Error};

#[derive(Clone, Copy)]
pub struct CommandsOk;
#[derive(Clone, Copy)]
pub struct UserToolchainOk;
#[derive(Clone, Copy)]
pub struct SourcesOk;

#[derive(Clone, Copy)]
pub struct AllOk(CommandsOk, UserToolchainOk);

fn check_user_toolchain() -> Result<UserToolchainOk, Error> {
    if !toolchain::is_toolchain_linked() {
        return Err(Error::MissingTools(vec![(
            "toolchain",
            "Use `cargo xtask toolchain` command to build and install the toolchain",
        )]));
    }
    Ok(UserToolchainOk)
}

fn check_command_list<I: IntoIterator<Item = (&'static str, &'static str)>>(
    list: I,
) -> Result<CommandsOk, Error> {
    let mut missing = vec![];
    for (command, guide) in list {
        if which::which(command).is_err() {
            missing.push((command, guide));
        }
    }
    if missing.is_empty() {
        Ok(CommandsOk)
    } else {
        Err(Error::MissingTools(missing))
    }
}

fn check_commands_x86_64() -> Result<CommandsOk, Error> {
    check_command_list([
        ("mtools", "Install the `mtools` package"),
        ("mkfs.vfat", "Install the `dosfstools` package"),
        ("ld64.lld", "Install LLVM"),
        ("cmake", "Install cmake"),
    ])
}

fn check_commands_aarch64() -> Result<CommandsOk, Error> {
    check_command_list([
        ("llvm-objcopy", "Install LLVM"),
        ("ld64.lld", "Install LLVM"),
    ])
}

fn check_commands_riscv64() -> Result<CommandsOk, Error> {
    check_command_list([("ld64.lld", "Install LLVM")])
}

pub fn check_build_env(arch: Arch) -> Result<AllOk, Error> {
    let user_toolchain = check_user_toolchain()?;
    let commands = match arch {
        Arch::x86_64 => check_commands_x86_64()?,
        Arch::aarch64 => check_commands_aarch64()?,
        Arch::riscv64 => check_commands_riscv64()?,
    };
    Ok(AllOk(commands, user_toolchain))
}
