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

pub const RX_BUFFER_SIZE: usize = 4096;

static_assertions::const_assert_eq!(RX_BUFFER_SIZE % super::RX_BUF_SIZE_MUL, 0);
static_assertions::const_assert_ne!(RX_BUFFER_SIZE / super::RX_BUF_SIZE_MUL, 0);

#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub(crate) struct Descriptor {
    address: u32,
    control: u32,
}

impl GenericTxDescriptor for Descriptor {
    // USED = 0: owned by hardware
    // USED = 1: owned by software
    const EMPTY: Self = Self {
        address: 0,
        control: Self::DESC_1_USED,
    };
    const EMPTY_LAST: Self = Self {
        address: 0,
        control: Self::DESC_1_USED | Self::DESC_1_TX_WRAP,
    };

    fn consume(&mut self) -> Option<Result<(), Error>> {
        if self.control & Self::DESC_1_USED != 0 && self.address != 0 {
            Some(Ok(()))
        } else {
            None
        }
    }

    fn setup_tx(
        &mut self,
        buffer_address: BusAddress,
        size: usize,
        index: usize,
        capacity: usize,
    ) -> Result<(), Error> {
        let buffer_address = buffer_address.try_into_u32()?;
        if !(8..8192).contains(&size) {
            return Err(Error::InvalidArgument);
        }
        let mut control = size as u32 | Self::DESC_1_TX_LAST;
        if index == capacity - 1 {
            control |= Self::DESC_1_TX_WRAP;
        }
        self.address = buffer_address;
        self.control = control;
        Ok(())
    }
}

impl GenericRxDescriptor for Descriptor {
    fn consume(&mut self) -> Option<Result<usize, Error>> {
        if self.address & Self::DESC_0_RX_OWNERSHIP != 0 {
            Some(Ok((self.control & Self::LENGTH) as usize))
        } else {
            None
        }
    }

    fn setup_rx(buffer_address: BusAddress, _size: usize, last: bool) -> Result<Self, Error> {
        let mut address = buffer_address.try_into_u32()?;
        if last {
            address |= Self::DESC_0_RX_WRAP;
        }
        Ok(Self {
            control: 0,
            address,
        })
    }
}

impl Descriptor {
    const DESC_1_USED: u32 = 1 << 31;
    const LENGTH: u32 = 0x1FFF;

    const DESC_1_TX_WRAP: u32 = 1 << 30;
    const DESC_1_TX_LAST: u32 = 1 << 15;

    const DESC_0_RX_OWNERSHIP: u32 = 1 << 0;
    const DESC_0_RX_WRAP: u32 = 1 << 1;
}
