use libk::{dma::BusAddress, error::Error};
use ygg_driver_net_core::util::{GenericRxDescriptor, GenericTxDescriptor};

#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct TxDescriptor {
    tdes0: u32,
    tdes1: u32,
    tdes2: u32,
    tdes3: u32,
}

#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct RxDescriptor {
    rdes0: u32,
    rdes1: u32,
    rdes2: u32,
    rdes3: u32,
}

impl TxDescriptor {
    const TDES3_OWN: u32 = 1 << 31;
    const TDES3_FD: u32 = 1 << 29;
    const TDES3_LD: u32 = 1 << 28;
    const TDES2_IOC: u32 = 1 << 31;
}

impl GenericTxDescriptor for TxDescriptor {
    const EMPTY: Self = Self {
        tdes0: 0,
        tdes1: 0,
        tdes2: 0,
        tdes3: 0,
    };

    fn consume(&mut self) -> Option<Result<(), Error>> {
        if self.tdes3 & Self::TDES3_OWN == 0 {
            // Some(self.tdes3 & !(0xFFFF << 16))
            Some(Ok(()))
        } else {
            None
        }
    }

    fn setup_tx(
        &mut self,
        buffer_address: BusAddress,
        size: usize,
        _index: usize,
        _capacity: usize,
    ) -> Result<(), Error> {
        if size & !0x3FFF != 0 {
            return Err(Error::InvalidArgument);
        }
        // TODO ioc only on quarter buffer
        self.tdes0 = buffer_address.try_into_u32()?;
        self.tdes1 = 0;
        self.tdes2 = size as u32 | Self::TDES2_IOC;
        self.tdes3 = Self::TDES3_OWN | Self::TDES3_LD | Self::TDES3_FD;
        Ok(())
    }
}

impl RxDescriptor {
    const RDES3_OWN: u32 = 1 << 31;
    const RDES3_IOC: u32 = 1 << 30;
    const RDES3_BUF1V: u32 = 1 << 24;
}

impl GenericRxDescriptor for RxDescriptor {
    fn consume(&mut self) -> Option<Result<usize, Error>> {
        if self.rdes3 & Self::RDES3_OWN == 0 {
            Some(Ok((self.rdes3 & 0x7FFF) as usize))
        } else {
            None
        }
    }

    fn setup_rx(
        buffer_address: BusAddress,
        _buffer_size: usize,
        _last: bool,
    ) -> Result<Self, Error> {
        let rdes0 = buffer_address.try_into_u32()?;
        let rdes1 = 0;
        let rdes2 = 0;
        // TODO ioc
        let rdes3 = Self::RDES3_BUF1V | Self::RDES3_IOC | Self::RDES3_OWN;
        Ok(Self {
            rdes0,
            rdes1,
            rdes2,
            rdes3,
        })
    }
}
