use std::{
    alloc::{self, Layout},
    io,
    sync::atomic::{AtomicU16, Ordering},
};

use dope_core::driver::ProvidedRingOps;
use io_uring::types::BufRingEntry;

use crate::driver::{ProvidedBuf, Ring};

pub struct ProvidedRing {
    tail_pos: u16,
    last_published: u16,
    mask: u16,
    buf_len: u32,
    buf_base: *mut u8,
    entries: *mut BufRingEntry,
    tail: *mut AtomicU16,
    _buf_mem: Box<[u8]>,
    entries_layout: Layout,
}

impl ProvidedRing {
    const BGID: u16 = 1;
    const RING_ALIGN: usize = 16384;

    pub(crate) fn new(ring: &Ring, entries: u16, buf_len: usize) -> io::Result<Self> {
        if !entries.is_power_of_two() {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "pbuf_ring entries must be power-of-two",
            ));
        }
        let entry_size = std::mem::size_of::<BufRingEntry>();
        let alloc_len = entry_size.checked_mul(entries as usize).ok_or_else(|| {
            io::Error::new(io::ErrorKind::InvalidInput, "pbuf_ring size overflow")
        })?;
        let entries_layout = Layout::from_size_align(alloc_len, Self::RING_ALIGN)
            .map_err(|_| io::Error::other("pbuf_ring layout"))?;
        let entries_ptr = unsafe { alloc::alloc_zeroed(entries_layout) } as *mut BufRingEntry;
        if entries_ptr.is_null() {
            return Err(io::Error::other("pbuf_ring alloc"));
        }

        let total_buf_len = (entries as usize).checked_mul(buf_len).ok_or_else(|| {
            io::Error::new(io::ErrorKind::InvalidInput, "pbuf backing size overflow")
        })?;
        let mut buf_mem = vec![0u8; total_buf_len].into_boxed_slice();
        let buf_base = buf_mem.as_mut_ptr();

        for bid in 0..entries {
            let e = unsafe { &mut *entries_ptr.add(bid as usize) };
            let addr = unsafe { buf_base.add(bid as usize * buf_len) };
            e.set_addr(addr as u64);
            e.set_len(buf_len as u32);
            e.set_bid(bid);
        }

        let tail =
            unsafe { BufRingEntry::tail(entries_ptr as *const _) as *mut u16 } as *mut AtomicU16;
        unsafe { (*tail).store(entries, Ordering::Release) };

        unsafe {
            ring.submitter().register_buf_ring_with_flags(
                entries_ptr as u64,
                entries,
                Self::BGID,
                0,
            )?;
        }

        Ok(Self {
            tail_pos: entries,
            last_published: entries,
            mask: entries.wrapping_sub(1),
            buf_len: buf_len as u32,
            buf_base,
            entries: entries_ptr,
            tail,
            _buf_mem: buf_mem,
            entries_layout,
        })
    }

    pub(crate) fn group(&self) -> (u16, u32) {
        (Self::BGID, self.buf_len)
    }

    pub(crate) fn make_buf(&mut self, bid: u16, len: usize) -> ProvidedBuf {
        let (ptr, cap) = self.ptr_len(bid);
        ProvidedBuf::new(bid, ptr, cap, len)
    }

    pub(crate) fn release(&mut self, bid: u16) {
        self.defer(bid);
        unsafe { (*self.tail).store(self.tail_pos, Ordering::Release) };
        self.last_published = self.tail_pos;
    }

    pub(crate) fn flush(&mut self) -> u64 {
        let n = self.tail_pos.wrapping_sub(self.last_published);
        if n == 0 {
            return 0;
        }
        unsafe { (*self.tail).store(self.tail_pos, Ordering::Release) };
        self.last_published = self.tail_pos;
        n as u64
    }
}

impl ProvidedRingOps for ProvidedRing {
    #[inline(always)]
    fn ptr_len(&self, bid: u16) -> (*mut u8, usize) {
        let off = bid as usize * self.buf_len as usize;
        let ptr = unsafe { self.buf_base.add(off) };
        (ptr, self.buf_len as usize)
    }

    #[inline(always)]
    fn defer(&mut self, bid: u16) {
        let slot = (self.tail_pos & self.mask) as usize;
        let e = unsafe { &mut *self.entries.add(slot) };
        let addr = unsafe { self.buf_base.add(bid as usize * self.buf_len as usize) };
        e.set_addr(addr as u64);
        e.set_len(self.buf_len);
        e.set_bid(bid);
        self.tail_pos = self.tail_pos.wrapping_add(1);
    }
}

impl Drop for ProvidedRing {
    fn drop(&mut self) {
        unsafe { alloc::dealloc(self.entries as *mut u8, self.entries_layout) };
    }
}
