use std::collections::VecDeque;

use dambi::Bytes;
use dope_core::IoVec;

pub(crate) struct SendRing<const IOV: usize> {
    queue: VecDeque<Bytes>,
    partial_sent: u32,
    iov_buf: [IoVec; IOV],
    msghdr: libc::msghdr,
}

impl<const IOV: usize> SendRing<IOV> {
    #[inline(always)]
    pub(crate) fn new() -> Self {
        Self {
            queue: VecDeque::new(),
            partial_sent: 0,
            iov_buf: [IoVec::empty(); IOV],

            msghdr: unsafe { std::mem::zeroed() },
        }
    }

    #[inline(always)]
    pub(crate) fn push(&mut self, bytes: Bytes) {
        self.queue.push_back(bytes);
    }

    #[inline(always)]
    pub(crate) fn is_empty(&self) -> bool {
        self.queue.is_empty()
    }

    #[inline(always)]
    pub(crate) fn fill_msghdr(&mut self, bytes_cap: usize) -> Option<&libc::msghdr> {
        if self.queue.is_empty() {
            return None;
        }
        let cap = bytes_cap.min(u32::MAX as usize);
        let mut n = 0usize;
        let mut bytes = 0usize;
        for (i, b) in self.queue.iter().enumerate() {
            if n == IOV || bytes >= cap {
                break;
            }
            let off = if i == 0 {
                self.partial_sent as usize
            } else {
                0
            };
            let slice = b.as_slice();
            if off >= slice.len() {
                continue;
            }
            let avail = slice.len() - off;
            let take = avail.min(cap - bytes);
            self.iov_buf[n] = IoVec::from_slice(&slice[off..off + take], 0);
            bytes += take;
            n += 1;
            if take < avail {
                break;
            }
        }
        if n == 0 {
            return None;
        }
        self.msghdr.msg_iov = self.iov_buf.as_mut_ptr() as *mut libc::iovec;
        self.msghdr.msg_iovlen = n as _;
        Some(&self.msghdr)
    }

    #[inline(always)]
    pub(crate) fn ack(&mut self, n: u32) {
        let mut left = n as u64;
        while left > 0 {
            let Some(head) = self.queue.front() else {
                break;
            };
            let head_len = head.len() as u64;
            let already = self.partial_sent as u64;
            let remaining = head_len - already;
            if left >= remaining {
                left -= remaining;
                self.partial_sent = 0;
                self.queue.pop_front();
            } else {
                self.partial_sent += left as u32;
                left = 0;
            }
        }
    }
}

impl<const IOV: usize> Default for SendRing<IOV> {
    #[inline(always)]
    fn default() -> Self {
        Self::new()
    }
}
