use std::{io, path::PathBuf};

use clap::ValueEnum;

const DEFAULT_PORTS: &[&str] = &["meta-llvm", "meta-libc", "openlibm", "compiler-rt"];

#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct ToolchainConfig {
    pub branch: String,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct BuildComponents {
    pub ports: Vec<String>,
}

#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct AArch64TargetConfig {
    pub components: BuildComponents,
}

#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct Riscv64TargetConfig {
    pub components: BuildComponents,
}

#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct X86_64TargetConfig {
    pub components: BuildComponents,
}

#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct TargetConfig {
    pub aarch64: AArch64TargetConfig,
    pub riscv64: Riscv64TargetConfig,
    pub x86_64: X86_64TargetConfig,
}

#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct XTaskConfig {
    pub toolchain: ToolchainConfig,
    pub target: TargetConfig,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Profile {
    #[default]
    Debug,
    Release,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, ValueEnum)]
#[allow(non_camel_case_types)]
#[clap(rename_all = "verbatim")]
pub enum Arch {
    #[default]
    aarch64,
    riscv64,
    x86_64,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, ValueEnum)]
#[allow(non_camel_case_types)]
#[clap(rename_all = "verbatim")]
pub enum Board {
    #[default]
    default,

    // Generic aarch64/riscv board
    virt,

    // AArch64 boards
    raspi4b,

    // RISC-V boards
    jh7110,
}

#[derive(Debug)]
pub struct BuildEnv {
    pub config: XTaskConfig,
    pub verbose: bool,

    pub profile: Profile,
    pub arch: Arch,
    pub board: Board,
    pub kernel_triple: &'static str,
    pub host_triple: String,

    pub workspace_root: PathBuf,

    pub kernel_output_dir: PathBuf,
    pub userspace_output_dir: PathBuf,

    pub ports_sysroot: PathBuf,
}

impl Default for ToolchainConfig {
    fn default() -> Self {
        Self {
            branch: "alnyan/yggdrasil-1.84.0".into(),
        }
    }
}

impl Default for BuildComponents {
    fn default() -> Self {
        Self {
            ports: DEFAULT_PORTS.iter().copied().map(String::from).collect(),
        }
    }
}

impl BuildEnv {
    pub fn new(
        config: XTaskConfig,
        verbose: bool,
        profile: Profile,
        arch: Arch,
        board: Board,
        workspace_root: PathBuf,
    ) -> Self {
        let kernel_triple = match arch {
            Arch::aarch64 => "aarch64-unknown-none",
            Arch::riscv64 => "riscv64-unknown-none",
            Arch::x86_64 => "x86_64-unknown-none",
        };
        let kernel_output_dir =
            workspace_root.join(format!("target/{}/{}", kernel_triple, profile.dir()));
        let userspace_output_dir = workspace_root.join(format!(
            "userspace/target/{}-unknown-yggdrasil/{}",
            arch.name(),
            profile.dir()
        ));
        let host = env!("TARGET");

        let ports_sysroot = workspace_root.join(
            workspace_root
                .join("userspace/target")
                .join(arch.user_triple())
                .join("ports-sysroot"),
        );

        Self {
            config,
            verbose,

            profile,
            arch,
            board,
            kernel_triple,
            host_triple: host.into(),

            workspace_root,

            kernel_output_dir,
            userspace_output_dir,

            ports_sysroot,
        }
    }

    pub fn dump<W: io::Write>(&self, output: &mut W) {
        match toml::to_string_pretty(&self.config) {
            Ok(string) => {
                writeln!(output, "{string}").ok();
            }
            Err(error) => {
                log::error!("Config serialization error: {error}");
            }
        }
    }
}

impl XTaskConfig {
    pub fn components(&self, env: &BuildEnv) -> &BuildComponents {
        match env.arch {
            Arch::riscv64 => &self.target.riscv64.components,
            Arch::aarch64 => &self.target.aarch64.components,
            Arch::x86_64 => &self.target.x86_64.components,
        }
    }
}

impl Profile {
    pub fn dir(&self) -> &str {
        match self {
            Self::Debug => "debug",
            Self::Release => "release",
        }
    }

    pub fn name(&self) -> &str {
        self.dir()
    }
}

impl Arch {
    pub fn all() -> impl Iterator<Item = Self> {
        [Self::aarch64, Self::x86_64, Self::riscv64].into_iter()
    }

    pub fn user_triple(&self) -> &str {
        match self {
            Self::riscv64 => "riscv64-unknown-yggdrasil",
            Self::aarch64 => "aarch64-unknown-yggdrasil",
            Self::x86_64 => "x86_64-unknown-yggdrasil",
        }
    }

    pub fn name(&self) -> &str {
        match self {
            Self::riscv64 => "riscv64",
            Self::aarch64 => "aarch64",
            Self::x86_64 => "x86_64",
        }
    }
}
