use alloc::{format, string::String, sync::Arc};
use libk::{
    error::Error,
    fs::sysfs::{
        attribute::{StringAttribute, StringAttributeOps},
        object::KObject,
    },
};
use libk_util::sync::IrqSafeSpinlock;

use crate::{device::PciBusDevice, PciBaseAddress, PciCapabilityId, PciConfigurationSpace};

pub(crate) fn make_sysfs_object(
    device: PciBusDevice,
) -> Arc<KObject<IrqSafeSpinlock<PciBusDevice>>> {
    struct Resources;
    struct Capabilities;
    struct Driver;
    struct Class;
    struct Id;

    impl StringAttributeOps for Driver {
        type Data = IrqSafeSpinlock<PciBusDevice>;
        const NAME: &'static str = "driver";

        fn read(state: &Self::Data) -> Result<String, Error> {
            let state = state.lock();
            if let Some(driver) = state.driver.map(|driver| driver.driver_name()) {
                Ok(driver.into())
            } else {
                Ok("".into())
            }
        }
    }

    impl StringAttributeOps for Id {
        type Data = IrqSafeSpinlock<PciBusDevice>;
        const NAME: &'static str = "id";

        fn read(state: &Self::Data) -> Result<String, Error> {
            let state = state.lock();
            Ok(format!(
                "{:04x}:{:04x}",
                state.info.vendor_id, state.info.device_id
            ))
        }
    }

    impl StringAttributeOps for Class {
        type Data = IrqSafeSpinlock<PciBusDevice>;
        const NAME: &'static str = "class";

        fn read(state: &Self::Data) -> Result<String, Error> {
            let state = state.lock();
            Ok(format!(
                "{:02x}:{:02x}:{:02x}",
                state.info.class, state.info.subclass, state.info.prog_if
            ))
        }
    }

    impl StringAttributeOps for Resources {
        type Data = IrqSafeSpinlock<PciBusDevice>;
        const NAME: &'static str = "resources";
        const NEWLINE: bool = false;

        fn read(state: &Self::Data) -> Result<String, Error> {
            use core::fmt::Write;

            let state = state.lock();
            let mut output = String::new();
            for i in 0..6 {
                if let Some(bar) = state.info.config_space.bar(i) {
                    if bar.is_zero() {
                        continue;
                    }

                    match bar {
                        PciBaseAddress::Io(base) => {
                            writeln!(output, "{i}:pio:{base:#06x}").ok();
                        }
                        PciBaseAddress::Memory32(base) => {
                            writeln!(output, "{i}:m32:{base:#010x}").ok();
                        }
                        PciBaseAddress::Memory64(base) => {
                            writeln!(output, "{i}:m64:{base:#018x}").ok();
                        }
                    }
                }
            }
            if output.is_empty() {
                output.push('\n');
            }
            Ok(output)
        }
    }

    impl StringAttributeOps for Capabilities {
        type Data = IrqSafeSpinlock<PciBusDevice>;
        const NAME: &'static str = "capabilities";
        const NEWLINE: bool = false;

        fn read(state: &Self::Data) -> Result<String, Error> {
            use core::fmt::Write;
            let state = state.lock();
            let mut output = String::new();
            for (capability, offset, _) in state.info.config_space.capability_iter() {
                write!(output, "{offset:04x}:").ok();
                match capability {
                    Some(PciCapabilityId::Msi) => write!(output, "msi").ok(),
                    Some(PciCapabilityId::MsiX) => write!(output, "msix").ok(),
                    Some(PciCapabilityId::VendorSpecific) => write!(output, "vendor-specific").ok(),
                    Some(PciCapabilityId::PciExpress) => write!(output, "pcie").ok(),
                    Some(PciCapabilityId::PowerManagement) => {
                        write!(output, "power-management").ok()
                    }
                    None => write!(output, "unknown").ok(),
                };
                writeln!(output).ok();
            }
            if output.is_empty() {
                output.push('\n');
            }
            Ok(output)
        }
    }

    let object = KObject::new(IrqSafeSpinlock::new(device));

    object
        .add_attribute(StringAttribute::from(Capabilities))
        .ok();
    object.add_attribute(StringAttribute::from(Resources)).ok();
    object.add_attribute(StringAttribute::from(Driver)).ok();
    object.add_attribute(StringAttribute::from(Class)).ok();
    object.add_attribute(StringAttribute::from(Id)).ok();

    object
}
