use alloc::{boxed::Box, string::String, vec::Vec};

use ax_errno::AxError;
use rdif_vsock::Interface;
use rdrive::{DriverGeneric, probe::OnProbeError};

use crate::{
    BindingInfo, binding_info_from_acpi, binding_info_from_fdt,
    registration::{BoundDevice, TakeRegistered, register_bound_device, take_registered_device},
};
#[cfg(feature = "pci")]
use crate::{PciIrqRequirement, binding_info_from_pci};

pub struct PlatformVsockDevice {
    name: String,
    info: BindingInfo,
    vsock: Option<Box<dyn Interface>>,
}

impl PlatformVsockDevice {
    fn new(name: String, vsock: Box<dyn Interface>, info: BindingInfo) -> Self {
        Self {
            name,
            info,
            vsock: Some(vsock),
        }
    }

    pub fn binding_info(&self) -> &BindingInfo {
        &self.info
    }

    pub fn irq_num(&self) -> Option<usize> {
        self.info.irq_num()
    }
}

impl DriverGeneric for PlatformVsockDevice {
    fn name(&self) -> &str {
        &self.name
    }
}

impl BoundDevice for PlatformVsockDevice {
    fn binding_info(&self) -> &BindingInfo {
        &self.info
    }
}

impl TakeRegistered for PlatformVsockDevice {
    type Output = Box<dyn Interface>;

    fn take_registered(&mut self) -> Option<Self::Output> {
        self.vsock.take()
    }
}

pub trait PlatformDeviceVsock {
    fn register_vsock<T>(self, dev: T) -> Option<usize>
    where
        T: Interface + 'static;

    fn register_vsock_with_info<T>(self, dev: T, info: BindingInfo) -> Option<usize>
    where
        T: Interface + 'static;
}

impl PlatformDeviceVsock for rdrive::PlatformDevice {
    fn register_vsock<T>(self, dev: T) -> Option<usize>
    where
        T: Interface + 'static,
    {
        self.register_vsock_with_info(dev, BindingInfo::empty())
    }

    fn register_vsock_with_info<T>(self, dev: T, info: BindingInfo) -> Option<usize>
    where
        T: Interface + 'static,
    {
        register_vsock_with_info(self, dev, info)
    }
}

pub trait ProbeFdtVsock {
    fn register_vsock<T>(self, dev: T) -> Result<Option<usize>, OnProbeError>
    where
        T: Interface + 'static;
}

impl ProbeFdtVsock for rdrive::probe::fdt::ProbeFdt<'_> {
    fn register_vsock<T>(self, dev: T) -> Result<Option<usize>, OnProbeError>
    where
        T: Interface + 'static,
    {
        let info = binding_info_from_fdt(self.info())?;
        Ok(register_vsock_with_info(
            self.into_platform_device(),
            dev,
            info,
        ))
    }
}

pub trait ProbeAcpiVsock {
    fn register_vsock<T>(self, dev: T) -> Result<Option<usize>, OnProbeError>
    where
        T: Interface + 'static;
}

impl ProbeAcpiVsock for rdrive::probe::acpi::ProbeAcpi<'_> {
    fn register_vsock<T>(self, dev: T) -> Result<Option<usize>, OnProbeError>
    where
        T: Interface + 'static,
    {
        let info = binding_info_from_acpi(self.info())?;
        Ok(register_vsock_with_info(
            self.into_platform_device(),
            dev,
            info,
        ))
    }
}

#[cfg(feature = "pci")]
pub trait ProbePciVsock {
    fn register_vsock<T>(
        self,
        dev: T,
        requirement: PciIrqRequirement,
    ) -> Result<Option<usize>, OnProbeError>
    where
        T: Interface + 'static;
}

#[cfg(feature = "pci")]
impl ProbePciVsock for rdrive::probe::pci::ProbePci<'_> {
    fn register_vsock<T>(
        self,
        dev: T,
        requirement: PciIrqRequirement,
    ) -> Result<Option<usize>, OnProbeError>
    where
        T: Interface + 'static,
    {
        let info = binding_info_from_pci(self.info(), requirement)?;
        Ok(register_vsock_with_info(
            self.into_platform_device(),
            dev,
            info,
        ))
    }
}

fn register_vsock_with_info<T>(
    plat_dev: rdrive::PlatformDevice,
    dev: T,
    info: BindingInfo,
) -> Option<usize>
where
    T: Interface + 'static,
{
    let name = dev.name().into();
    register_bound_device(
        plat_dev,
        PlatformVsockDevice::new(name, Box::new(dev), info),
    )
}

pub fn take_vsock_devices() -> Result<Vec<Box<dyn Interface>>, AxError> {
    let mut devices = Vec::new();
    for dev in rdrive::get_list::<PlatformVsockDevice>() {
        devices.push(take_vsock_device(dev)?);
    }
    Ok(devices)
}

fn take_vsock_device(
    device: rdrive::Device<PlatformVsockDevice>,
) -> Result<Box<dyn Interface>, AxError> {
    take_registered_device(device).ok_or(AxError::BadState)
}

#[cfg(test)]
mod tests {
    extern crate std;

    use rdif_vsock::{VsockConnId, VsockError, VsockEvent};

    use super::*;
    use crate::BindingInfo;

    struct TestVsock;

    impl DriverGeneric for TestVsock {
        fn name(&self) -> &str {
            "test-vsock"
        }
    }

    impl Interface for TestVsock {
        fn guest_cid(&self) -> u64 {
            3
        }

        fn listen(&mut self, _port: u32) -> Result<(), VsockError> {
            Ok(())
        }

        fn connect(&mut self, _id: VsockConnId) -> Result<(), VsockError> {
            Ok(())
        }

        fn send(&mut self, _id: VsockConnId, buf: &[u8]) -> Result<usize, VsockError> {
            Ok(buf.len())
        }

        fn recv(&mut self, _id: VsockConnId, _buf: &mut [u8]) -> Result<usize, VsockError> {
            Ok(0)
        }

        fn recv_avail(&mut self, _id: VsockConnId) -> Result<usize, VsockError> {
            Ok(0)
        }

        fn disconnect(&mut self, _id: VsockConnId) -> Result<(), VsockError> {
            Ok(())
        }

        fn abort(&mut self, _id: VsockConnId) -> Result<(), VsockError> {
            Ok(())
        }

        fn poll_event(&mut self) -> Result<Option<VsockEvent>, VsockError> {
            Ok(None)
        }
    }

    #[test]
    fn platform_vsock_device_exposes_binding_info_irq_num() {
        let irq = 44;
        let device = PlatformVsockDevice::new(
            "test-vsock".into(),
            Box::new(TestVsock),
            BindingInfo::with_irq(Some(irq)).unwrap(),
        );

        assert_eq!(device.binding_info().irq_num(), Some(irq));
        assert_eq!(device.irq_num(), Some(irq));
        assert_eq!(BoundDevice::irq_num(&device), Some(irq));
    }

    #[test]
    fn platform_vsock_device_empty_binding_has_no_irq_num() {
        let device = PlatformVsockDevice::new(
            "test-vsock".into(),
            Box::new(TestVsock),
            BindingInfo::empty(),
        );

        assert_eq!(device.binding_info().irq_num(), None);
        assert_eq!(device.irq_num(), None);
        assert_eq!(BoundDevice::irq_num(&device), None);
    }
}
