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

#[derive(Debug)]
#[repr(C)]
pub(crate) struct RxDescriptor {
    address: u64,
    length: u16,
    checksum: u16,
    status: u8,
    errors: u8,
    special: u16,
}

#[derive(Debug)]
#[repr(C)]
pub(crate) struct TxDescriptor {
    address: u64,
    length: u16,
    cso: u8,
    cmd: u8,
    sta: u8,
    _0: u8,
    css: u8,
    special: u8,
}

impl GenericTxDescriptor for TxDescriptor {
    const EMPTY: Self = Self {
        address: 0,
        length: 0,
        cso: 0,
        cmd: 0,
        sta: Self::STA_DD,
        _0: 0,
        css: 0,
        special: 0,
    };

    fn consume(&mut self) -> Option<Result<(), Error>> {
        unreachable!()
    }

    fn setup_tx(
        &mut self,
        buffer_address: BusAddress,
        size: usize,
        index: usize,
        capacity: usize,
    ) -> Result<(), Error> {
        if !(4..16288).contains(&size) {
            return Err(Error::InvalidArgument);
        }
        let quarter = capacity / 4;
        let ioc = index % quarter == quarter - 1;
        let mut cmd = Self::CMD_EOP | Self::CMD_IFCS;
        if ioc {
            cmd |= Self::CMD_RS;
        }
        self.address = buffer_address.into_u64();
        self.length = size as u16 - 4;
        self.css = 0;
        self.cso = 0;
        self.sta = 0;
        self.cmd = cmd;
        self.special = 0;
        Ok(())
    }
}

impl GenericRxDescriptor for RxDescriptor {
    fn consume(&mut self) -> Option<Result<usize, Error>> {
        if self.status & Self::STA_DD == 0 {
            None
        } else if self.errors & !1 != 0 {
            Some(Err(Error::InvalidArgument))
        } else {
            Some(Ok(self.length as _))
        }
    }

    fn setup_rx(
        buffer_address: BusAddress,
        buffer_size: usize,
        _last: bool,
    ) -> Result<Self, Error> {
        Ok(Self {
            address: buffer_address.into_u64(),
            length: buffer_size as _,
            checksum: 0,
            status: 0,
            errors: 0,
            special: 0,
        })
    }
}

impl TxDescriptor {
    // Descriptor done
    const STA_DD: u8 = 1 << 0;
    // End of packet
    const CMD_EOP: u8 = 1 << 0;
    // Insert MAC FCS
    const CMD_IFCS: u8 = 1 << 1;
    // // Insert checksum
    // const CMD_IC: u8 = 1 << 2;
    // Report status
    const CMD_RS: u8 = 1 << 3;
}

impl RxDescriptor {
    const STA_DD: u8 = 1 << 0;
}
