#[derive(Debug)]
struct CpuId {
    vendor: String,
    brand: String,
    features: Vec<String>,
}

impl CpuId {
    fn new() -> Self {
        let raw = util::cpuid_raw(0);
        let vendor = raw.vendor();

        let mut brand = Vec::with_capacity(std::mem::size_of::<CpuIdRaw>());
        let leaves = 0x80000002..=0x80000005;
        for leaf in leaves {
            let raw = util::cpuid_raw(leaf);
            brand.extend_from_slice(&raw.eax.to_le_bytes());
            brand.extend_from_slice(&raw.ebx.to_le_bytes());
            brand.extend_from_slice(&raw.ecx.to_le_bytes());
            brand.extend_from_slice(&raw.edx.to_le_bytes());
        }
        let brand = util::bytes_to_string(&brand);

        let features = raw.features();

        Self {
            vendor,
            brand,
            features,
        }
    }
}

#[derive(Default)]
#[repr(C)]
struct CpuIdRaw {
    eax: u32,
    ebx: u32,
    ecx: u32,
    edx: u32,
}

impl CpuIdRaw {
    fn vendor(&self) -> String {
        let v = [
            self.ebx.to_le_bytes(),
            self.ecx.to_le_bytes(),
            self.edx.to_le_bytes(),
        ]
        .concat();

        util::bytes_to_string(&v)
    }

    fn features(&self) -> Vec<String> {
        vec![]
    }
}

mod util {
    use super::CpuIdRaw;

    pub fn bytes_to_string(bytes: &[u8]) -> String {
        String::from_utf8_lossy(bytes)
            .trim_end_matches('\0')
            .trim()
            .to_string()
    }

    pub fn cpuid_raw(leaf: u32) -> CpuIdRaw {
        let mut result = CpuIdRaw::default();

        #[cfg(target_arch = "x86_64")]
        unsafe {
            asm_att::asm_att!(
                "cpuid",
                "movl %ebx, {0:e}",
                out(reg) result.ebx,
                inout("eax") leaf => result.eax,
                out("ecx") result.ecx,
                out("edx") result.edx
            )
        }

        result
    }
}

fn main() {
    let cpuid = CpuId::new();
    println!("{:#?}", cpuid);
}
