use libdrm_amdgpu_sys::*;
use std::fs::File;

fn info(libdrm_amdgpu: &LibDrmAmdgpu, pci_bus: &PCI::BUS_INFO) {
    let Ok(device_path) = pci_bus.get_drm_render_path() else { return };
    let f = File::open(device_path).unwrap();
    let (amdgpu_dev, _major, _minor) = libdrm_amdgpu.init_device_handle_with_fd(f).unwrap();

    if let Ok(drm_ver) = amdgpu_dev.get_drm_version_struct() {
        println!("{drm_ver:#?}");
    }

    if let Ok(ext_info) = amdgpu_dev.device_info() {
        use AMDGPU::GPU_INFO;

        println!("Marketing Name: [{}]", ext_info.find_device_name_or_default());
        // println!("\n{ext_info:#X?}\n");
        let gpu_type = if ext_info.is_apu() { "APU" } else { "dGPU" };
        let asic = ext_info.get_asic_name();

        println!(
            "DeviceID.RevID: {:#0X}.{:#0X}",
            ext_info.device_id(),
            ext_info.pci_rev_id()
        );

        println!();
        println!("Family:\t\t{}", ext_info.get_family_name());
        println!("ASIC Name:\t{asic}");
        println!("Chip class:\t{}", ext_info.get_chip_class());
        println!("GPU Type:\t{gpu_type}");

        if let Some(gfx_ver) = ext_info.get_gfx_target_version() {
            println!("gfx_target_version: {gfx_ver}");
        }

        let max_good_cu_per_sa = ext_info.get_max_good_cu_per_sa();
        let min_good_cu_per_sa = ext_info.get_min_good_cu_per_sa();

        println!();
        println!("Shader Engine (SE):\t\t{:3}", ext_info.max_se());
        println!("Shader Array (SA/SH) per SE:\t{:3}", ext_info.max_sa_per_se());
        if max_good_cu_per_sa != min_good_cu_per_sa {
            println!("CU per SA[0]:\t\t\t{:3}", max_good_cu_per_sa);
            println!("CU per SA[1]:\t\t\t{:3}", min_good_cu_per_sa);
        } else {
            println!("CU per SA:\t\t\t{:3}", max_good_cu_per_sa);
        }
        println!("Total Compute Unit:\t\t{:3}", ext_info.cu_active_number());

        if let Some((min, max)) = amdgpu_dev.get_min_max_gpu_clock() {
            println!("Engine Clock:\t\t{min}-{max} MHz");
        }

        println!("Peak FP32:\t\t{} GFLOPS", ext_info.peak_gflops());

        println!();
        println!("VRAM Type:\t\t{}", ext_info.get_vram_type());
        println!("VRAM Bit Width:\t\t{}-bit", ext_info.vram_bit_width);

        if let Some((min, max)) = amdgpu_dev.get_min_max_memory_clock() {
            println!("Memory Clock:\t\t{min}-{max} MHz");
        }

        println!("Peak Memory BW:\t\t{} GB/s", ext_info.peak_memory_bw_gb());

        println!();
        println!("L1cache (per CU):\t{:4} KiB", ext_info.get_l1_cache_size() >> 10);
        if 0 < ext_info.sqc_data_cache_size {
            println!("SQC Data Cache:\t\t{:4} KiB", ext_info.sqc_data_cache_size);
        }
        if 0 < ext_info.sqc_inst_cache_size {
            println!("SQC Inst Cache:\t\t{:4} KiB", ext_info.sqc_inst_cache_size);
        }
        let gl1_cache_size = ext_info.get_gl1_cache_size();
        let l3_cache_size = ext_info.calc_l3_cache_size_mb();
        if 0 < gl1_cache_size {
            println!("GL1cache (per SA/SH):\t{:4} KiB", gl1_cache_size >> 10);
        }
        if 0 < ext_info.gl1c_cache_size {
            println!("Total GL1cache:\t\t{gl1_cache_size:4} KiB");
        }
        println!(
            "L2cache:\t\t{:4} KiB ({} Banks)",
            ext_info.calc_l2_cache_size() >> 10,
            ext_info.get_actual_num_tcc_blocks(),
        );
        if 0 < l3_cache_size {
            println!("L3cache:\t\t{l3_cache_size:4} MiB");
        }
    }

    if let Ok(info) = amdgpu_dev.memory_info() {
        println!();
        println!(
            "VRAM Usage:\t\t\t{usage}/{total} MiB",
            usage = info.vram.heap_usage >> 20,
            total = info.vram.total_heap_size >> 20,
        );
        println!(
            "CPU Accessible VRAM Usage:\t{usage}/{total} MiB",
            usage = info.cpu_accessible_vram.heap_usage >> 20,
            total = info.cpu_accessible_vram.total_heap_size >> 20,
        );
        println!(
            "GTT Usage:\t\t\t{usage}/{total} MiB",
            usage = info.gtt.heap_usage >> 20,
            total = info.gtt.total_heap_size >> 20,
        );
        let re_bar = if info.check_resizable_bar() { "Enabled" } else { "Disabled" };
        println!("ResizableBAR:\t\t\t{re_bar}");

        if let Ok(moved) = amdgpu_dev.num_bytes_moved() {
            println!("Number of bytes moved for TTM migration: {moved}");
        }

        if let Ok(fault_count) = amdgpu_dev.num_vram_cpu_page_faults() {
            println!("Number of VRAM page faults on CPU access: {fault_count}");
        }

        if let Ok(e) = amdgpu_dev.num_evictions() {
            println!("Number of TTM buffer evictions: {e}");
        }

        if let Ok(lost) = amdgpu_dev.vram_lost_counter() {
            println!("VRAM lost counter: {lost}");
        }

        if let Ok(ras) = amdgpu_dev.ras_enabled_features() {
            use AMDGPU::RasBlock;

            println!("ECC Memory: {}", if ras.is_supported(RasBlock::UMC) {
                "supported"
            } else {
                "not supported"
            });
        }
    }

    {
        use AMDGPU::HW_IP::*;

        let ip_list = [
            HW_IP_TYPE::GFX,
            HW_IP_TYPE::COMPUTE,
            HW_IP_TYPE::DMA,
            HW_IP_TYPE::UVD,
            HW_IP_TYPE::VCE,
            HW_IP_TYPE::UVD_ENC,
            HW_IP_TYPE::VCN_DEC,
            HW_IP_TYPE::VCN_ENC,
            HW_IP_TYPE::VCN_JPEG,
            HW_IP_TYPE::VPE,
        ];

        println!("\nHardware IP info:");

        for ip_type in &ip_list {
            if let (Ok(ip_info), Ok(ip_count)) = (
                amdgpu_dev.query_hw_ip_info(*ip_type, 0),
                amdgpu_dev.query_hw_ip_count(*ip_type),
            ) {
                let (major, minor) = ip_info.version();
                let queues = ip_info.num_queues();

                if queues == 0 {
                    continue;
                }

                println!(
                    "{ip_type:8} count: {ip_count}, ver: {major:2}.{minor}, queues: {queues}",
                    ip_type = ip_type.to_string(),
                );
            }
        }
    }

    {
        use AMDGPU::FW_VERSION::*;

        let fw_list = [
            FW_TYPE::VCE,
            FW_TYPE::UVD,
            FW_TYPE::GMC,
            FW_TYPE::GFX_ME,
            FW_TYPE::GFX_PFP,
            FW_TYPE::GFX_CE,
            FW_TYPE::GFX_RLC,
            FW_TYPE::GFX_MEC,
            FW_TYPE::SMC,
            FW_TYPE::SDMA,
            FW_TYPE::SOS,
            FW_TYPE::ASD,
            FW_TYPE::VCN,
            FW_TYPE::GFX_RLC_RESTORE_LIST_CNTL,
            FW_TYPE::GFX_RLC_RESTORE_LIST_GPM_MEM,
            FW_TYPE::GFX_RLC_RESTORE_LIST_SRM_MEM,
            FW_TYPE::DMCU,
            FW_TYPE::TA,
            FW_TYPE::DMCUB,
            FW_TYPE::TOC,
            FW_TYPE::CAP,
            FW_TYPE::GFX_RLCP,
            FW_TYPE::GFX_RLCV,
            FW_TYPE::MES_KIQ,
            FW_TYPE::MES,
            FW_TYPE::IMU,
            FW_TYPE::VPE,
        ];

        println!("\nFirmware info:");

        for fw_type in &fw_list {
            let fw_info = match amdgpu_dev.query_firmware_version(*fw_type, 0, 0) {
                Ok(v) => v,
                Err(_) => continue,
            };

            let (ver, ftr) = (fw_info.version, fw_info.feature);

            if ver == 0 {
                continue;
            }

            println!(
                "{fw_type:<8} ver: {ver:>#10X}, feature: {ftr:>3}",
                fw_type = fw_type.to_string(),
            );
        }
    }

    {
        use AMDGPU::VIDEO_CAPS::CAP_TYPE;

        println!("\nVideo caps:");
        if let Ok(codec_info) = amdgpu_dev.get_video_caps_info(CAP_TYPE::DECODE) {
            println!("{codec_info:#?}");
        }
        if let Ok(codec_info) = amdgpu_dev.get_video_caps_info(CAP_TYPE::ENCODE) {
            println!("{codec_info:#?}");
        }
    }

    if let Ok(bus_info) = amdgpu_dev.get_pci_bus_info() {
        println!("\nPCI (domain:bus:dev.func): {bus_info}");

        if let Ok(render) = bus_info.get_drm_render_path() {
            println!("Render: {render:?}");
        }
        if let Ok(card) = bus_info.get_drm_card_path() {
            println!("Card: {card:?}");
        }
    }

    match amdgpu_dev.get_min_max_link_info_from_dpm() { Some([min, max]) => {
        println!(
            "PCIe Link Speed     (DPM)    : Gen{}x{} - Gen{}x{}",
            min.r#gen,
            min.width,
            max.r#gen,
            max.width,
        );

        if let Some(max_gpu_link) = amdgpu_dev.get_max_gpu_link() {
            println!(
                "PCIe Link Speed (GPU, Max)   : Gen{}x{}",
                max_gpu_link.r#gen,
                max_gpu_link.width,
            );
        }

        if let Some(max_system_link) = amdgpu_dev.get_max_system_link() {
            println!(
                "PCIe Link Speed (System, Max): Gen{}x{}",
                max_system_link.r#gen,
                max_system_link.width,
            );
        }
    } _ => {
        println!("PCIe Link Speed     (DPM)    : None");
    }}

    if let Ok(vbios) = amdgpu_dev.get_vbios_info() {
        println!("\nVBIOS info:");
        println!("name: [{}]", vbios.name);
        println!("pn: [{}]", vbios.pn);
        println!("ver: [{}]", vbios.ver);
        println!("date: [{}]", vbios.date);
    }

/*
    if let Ok(vce_clock) = amdgpu_dev.vce_clock_info() {
        println!("\n{vce_clock:#?}");
    }
*/

    {
        use AMDGPU::SENSOR_INFO::*;

        let sensors = [
            SENSOR_TYPE::GFX_SCLK,
            SENSOR_TYPE::GFX_MCLK,
            SENSOR_TYPE::GPU_TEMP,
            SENSOR_TYPE::GPU_LOAD,
            SENSOR_TYPE::GPU_AVG_POWER,
            SENSOR_TYPE::GPU_INPUT_POWER,
            SENSOR_TYPE::VDDNB,
            SENSOR_TYPE::VDDGFX,
            SENSOR_TYPE::STABLE_PSTATE_GFX_SCLK,
            SENSOR_TYPE::STABLE_PSTATE_GFX_MCLK,
            SENSOR_TYPE::PEAK_PSTATE_GFX_SCLK,
            SENSOR_TYPE::PEAK_PSTATE_GFX_MCLK,
        ];

        println!("\nSensors:");

        for s in &sensors {
            match amdgpu_dev.sensor_info(*s) { Ok(val) => {
                println!("{s:?}: {val}");
            } _ => {
                println!("{s:?}: not supported");
            }}
        }
    }

    if let Ok(sysfs) = amdgpu_dev.get_sysfs_path() {
        // let f = AMDGPU::DpmClockRange::from_sysfs(AMDGPU::DpmClockType::FCLK, &sysfs);
        // println!("{f:?}");
        use AMDGPU::{DpmForcedLevel, PowerProfile};

        let profiles: Vec<String> = PowerProfile::get_all_supported_profiles_from_sysfs(&sysfs)
            .iter()
            .map(|p| p.to_string())
            .collect();

        println!("Supported Power Profiles: {profiles:?}");

        if let Some(profiles) = PowerProfile::get_current_profile_from_sysfs(&sysfs) {
            println!("Current Power Profiles: {profiles}");
        }

        if let Ok(level) = DpmForcedLevel::get_from_sysfs(&sysfs) {
            println!("power_dpm_force_performance_level: {level:?}");
        }

        use AMDGPU::{RasErrorCount, RasBlock};

        if let Ok(cnt) = RasErrorCount::get_from_sysfs_with_ras_block(&sysfs, RasBlock::UMC) {
            println!(
                "Memory Error Count: uncorrected {}, corrected {}",
                cnt.uncorrected,
                cnt.corrected,
            );
        }
    }

    if let Some(hwmon) = amdgpu_dev.get_hwmon_path() {
        println!("hwmon: {hwmon:?}");

        use AMDGPU::{HwmonTemp, HwmonTempType, PowerCap};
        if let Some(power_cap) = PowerCap::from_hwmon_path(&hwmon) {
            let PowerCap { type_, current, default, min, max } = power_cap;
            println!("PowerCap ({type_}): {current} W (Current), {default} W (Default), {min}-{max} W (Range)");
        }
        if let Some(edge_temp) = HwmonTemp::from_hwmon_path(&hwmon, HwmonTempType::Edge) {
            println!("{edge_temp:?}");
        }
        if let Some(junction_temp) = HwmonTemp::from_hwmon_path(&hwmon, HwmonTempType::Junction) {
            println!("{junction_temp:?}");
        }
        if let Some(mem_temp) = HwmonTemp::from_hwmon_path(&hwmon, HwmonTempType::Memory) {
            println!("{mem_temp:?}");
        }
    }

    println!();
}

fn main() {
    let libdrm_amdgpu = LibDrmAmdgpu::new().unwrap();
    let pci_devs = AMDGPU::get_all_amdgpu_pci_bus();

    if pci_devs.is_empty() {
        panic!("No AMDGPU devices.");
    }

    for pci_bus in &pci_devs {
        info(&libdrm_amdgpu, pci_bus);
    }
}
