use riscv_target::Target;
use std::env;
use std::fmt;

#[derive(Clone, Copy)]
enum PolyfillLevel {
    // Native, ie no polyfill. Just reexport from core::sync::atomic
    Native,
    // CAS polyfill: use AtomicXX from core::sync::atomic, add CAS polyfills with critical sections
    Cas,
    // Full polyfill: polyfill both load/store and CAS with critical sections
    Full,
}

impl fmt::Display for PolyfillLevel {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match *self {
            Self::Native => "native",
            Self::Cas => "cas",
            Self::Full => "full",
        };
        write!(f, "{}", s)
    }
}

fn main() {
    use PolyfillLevel::*;

    let target = env::var("TARGET").unwrap();

    let (level, level64) = if let Some(levels) = from_features() {
        levels
    } else if target == "riscv32imc-esp-espidf"
        || target == "xtensa-esp32-espidf"
        || target == "xtensa-esp32s2-espidf"
        || target == "xtensa-esp32s3-espidf"
    {
        (Native, Full)
    } else if is_riscv_without_atomic_ext(&target) {
        (Full, Full)
    } else if target.starts_with("riscv32") {
        (Native, Full)
    } else if target.starts_with("thumbv4") {
        (Full, Full)
    } else if target.starts_with("thumbv6m-") {
        (Cas, Full)
    } else if target.starts_with("thumb") {
        (Native, Full)
    } else {
        (Native, Native)
    };

    println!("cargo:rustc-cfg=u8_{}", level);
    println!("cargo:rustc-cfg=u16_{}", level);
    println!("cargo:rustc-cfg=u32_{}", level);
    println!("cargo:rustc-cfg=u64_{}", level64);
    println!("cargo:rustc-cfg=usize_{}", level);
    println!("cargo:rustc-cfg=i8_{}", level);
    println!("cargo:rustc-cfg=i16_{}", level);
    println!("cargo:rustc-cfg=i32_{}", level);
    println!("cargo:rustc-cfg=i64_{}", level64);
    println!("cargo:rustc-cfg=isize_{}", level);
    println!("cargo:rustc-cfg=ptr_{}", level);
    println!("cargo:rustc-cfg=bool_{}", level);
}

fn is_riscv_without_atomic_ext(target: &str) -> bool {
    if target.starts_with("riscv") {
        let target = Target::from_target_str(target);

        !target.has_extension('a')
    } else {
        false
    }
}

fn from_features() -> Option<(PolyfillLevel, PolyfillLevel)> {
    use PolyfillLevel::*;

    const ALL: &[PolyfillLevel] = &[Native, Cas, Full];

    let get = |suffix| {
        ALL.iter()
            .filter(|level| {
                env::var(format!("CARGO_FEATURE_{}{}", level, suffix).to_uppercase())
                    .ok()
                    .is_some()
            })
            .copied()
            .collect::<Vec<_>>()
    };

    let (polyfills, polyfills64) = (get(""), get("64"));

    let get_first = |polyfills: &[PolyfillLevel], other: &[PolyfillLevel], suffix, other_suffix| {
        let list = |suffix| {
            ALL.iter()
                .map(|level| format!("`{}{}`", level, suffix))
                .collect::<Vec<_>>()
                .join(", ")
        };

        if polyfills.len() > 1 {
            panic!("Only one of features [{}] can be enabled", list(suffix));
        }

        if let Some(level) = polyfills.first() {
            if other.is_empty() {
                panic!(
                    "Enabling `{}{}` requires enabling one of [{}] as well",
                    level,
                    suffix,
                    list(other_suffix),
                );
            }

            Some(*level)
        } else {
            None
        }
    };

    let level = get_first(&polyfills, &polyfills64, "", "64");
    let level64 = get_first(&polyfills64, &polyfills, "64", "");

    level.and_then(|level| level64.map(|level64| (level, level64)))
}
