use libk_mm::device::DeviceMemoryIo;
use tock_registers::{
    interfaces::Readable,
    registers::{ReadOnly, WriteOnly},
};
use ygg_driver_pci::{
    capability::{
        VirtioCapabilityData, VirtioCommonConfigCapability, VirtioDeviceConfigCapability,
        VirtioInterruptStatusCapability, VirtioNotifyConfigCapability,
    },
    PciCommandRegister, PciConfigurationSpace,
};

use crate::{error::Error, CommonConfiguration};

use super::Transport;

pub struct PciTransport {
    common_cfg: DeviceMemoryIo<'static, CommonConfiguration>,
    device_cfg: DeviceMemoryIo<'static, [u8]>,
    notify_cfg: DeviceMemoryIo<'static, [WriteOnly<u16>]>,
    isr: DeviceMemoryIo<'static, ReadOnly<u32>>,
    notify_cfg_mul: usize,
}

impl Transport for PciTransport {
    fn common_cfg(&self) -> &CommonConfiguration {
        &self.common_cfg
    }

    fn notify_cfg(&self) -> &[WriteOnly<u16>] {
        &self.notify_cfg
    }

    fn notify_off_mul(&self) -> usize {
        self.notify_cfg_mul
    }

    fn supports_msix(&self) -> bool {
        true
    }

    fn device_cfg(&self) -> Option<&DeviceMemoryIo<'_, [u8]>> {
        Some(&self.device_cfg)
    }

    fn read_interrupt_status(&self) -> (bool, bool) {
        let value = self.isr.get();
        (value & 1 != 0, value & 2 != 0)
    }
}

impl PciTransport {
    pub fn from_config_space<S: PciConfigurationSpace + ?Sized>(space: &S) -> Result<Self, Error> {
        // Transitional devices MUST have a PCI Revision ID of 0.
        // Transitional devices MUST have the PCI Subsystem Device ID
        //   matching the Virtio Device ID, as indicated in section 5.
        // Transitional devices MUST have the Transitional PCI
        //   Device ID in the range 0x1000 to 0x103f.
        // TODO check PCI subsystem ID
        // if space.rev_id() != 0 {
        //     log::error!("VirtIO PCI rev ID != 0");
        //     return Err(Error::InvalidPciConfiguration);
        // }

        let mut cmd = PciCommandRegister::from_bits_retain(space.command());
        cmd &= !(PciCommandRegister::DISABLE_INTERRUPTS | PciCommandRegister::ENABLE_IO);
        cmd |= PciCommandRegister::ENABLE_MEMORY | PciCommandRegister::BUS_MASTER;
        space.set_command(cmd.bits());

        // Extract capabilities

        let common_cfg_cap = space
            .capability::<VirtioCommonConfigCapability>()
            .ok_or(Error::NoCommonConfigCapability)?;
        // TODO this is not mandatory
        let device_cfg_cap = space
            .capability::<VirtioDeviceConfigCapability>()
            .ok_or(Error::NoDeviceConfigCapability)?;
        let notify_cfg_cap = space
            .capability::<VirtioNotifyConfigCapability>()
            .ok_or(Error::NoNotifyConfigCapability)?;
        let isr_cap = space
            .capability::<VirtioInterruptStatusCapability>()
            .ok_or(Error::NoInterruptStatusCapability)?;

        // TODO MSI/MSI-X

        // Map the regions

        let common_cfg_base = space
            .bar(common_cfg_cap.bar_index().unwrap())
            .unwrap()
            .as_memory()
            .unwrap()
            .add(common_cfg_cap.bar_offset());
        let device_cfg_base = space
            .bar(device_cfg_cap.bar_index().unwrap())
            .unwrap()
            .as_memory()
            .unwrap()
            .add(device_cfg_cap.bar_offset());
        let device_cfg_len = device_cfg_cap.length();
        let notify_cfg_base = space
            .bar(notify_cfg_cap.bar_index().unwrap())
            .unwrap()
            .as_memory()
            .unwrap()
            .add(notify_cfg_cap.bar_offset());
        let notify_cfg_len = notify_cfg_cap.length();
        let notify_cfg_mul = notify_cfg_cap.offset_multiplier();
        let isr_base = space
            .bar(isr_cap.bar_index().unwrap())
            .unwrap()
            .as_memory()
            .unwrap()
            .add(isr_cap.bar_offset());
        let isr_len = isr_cap.length();

        assert!(isr_len >= 4);
        assert_eq!(notify_cfg_len % 2, 0);

        let common_cfg =
            unsafe { DeviceMemoryIo::map(common_cfg_base, Default::default()) }.unwrap();
        let device_cfg = unsafe {
            DeviceMemoryIo::map_slice(device_cfg_base, device_cfg_len, Default::default())
        }
        .unwrap();
        let notify_cfg = unsafe {
            DeviceMemoryIo::map_slice(notify_cfg_base, notify_cfg_len / 2, Default::default())
        }
        .unwrap();
        let isr = unsafe { DeviceMemoryIo::map(isr_base, Default::default()) }.unwrap();

        Ok(Self {
            common_cfg,
            device_cfg,
            notify_cfg,
            notify_cfg_mul,
            isr,
        })
    }
}

// TODO sort this out
unsafe impl Sync for PciTransport {}
