//! Dmabuf slots for the cast stream: linear gbm bos exported to PipeWire, and
//! the unsafe access to the raw `spa_data` an `add_buffer`/`remove_buffer`
//! callback hands over.

use std::os::fd::{AsRawFd, OwnedFd};

use pipewire::spa;
use smithay::backend::allocator::dmabuf::{AsDmabuf, Dmabuf};
use smithay::backend::allocator::gbm::{GbmBuffer, GbmBufferFlags, GbmDevice};
use smithay::backend::drm::DrmDeviceFd;

/// One negotiated dmabuf slot: the gbm bo exported for PipeWire, keyed by the
/// raw fd placed in its `spa_data`.
pub(super) struct DmaSlot {
    pub(super) fd: i64,
    _fd_owner: OwnedFd,
    pub(super) stride: i32,
    pub(super) dmabuf: Dmabuf,
}

/// Allocate one linear ARGB gbm bo of `w`×`h` and export it as a dmabuf slot.
/// Linear so the fixed modifier needs no fixation dance and any consumer can
/// map it.
fn alloc_dma_slot(
    gbm: &GbmDevice<DrmDeviceFd>,
    w: u32,
    h: u32,
) -> Result<DmaSlot, Box<dyn std::error::Error>> {
    let bo = gbm.create_buffer_object::<()>(
        w,
        h,
        smithay::reexports::gbm::Format::Argb8888,
        GbmBufferFlags::RENDERING | GbmBufferFlags::LINEAR,
    )?;
    let stride = bo.stride() as i32;
    let fd_owner = bo.fd()?;
    let gbm_buf = GbmBuffer::from_bo(bo, false);
    let dmabuf = gbm_buf.export()?;
    Ok(DmaSlot { fd: fd_owner.as_raw_fd() as i64, _fd_owner: fd_owner, stride, dmabuf })
}

/// Fill `raw`'s first `spa_data` with a freshly allocated dmabuf slot, when
/// the negotiated data-type mask carries DmaBuf — otherwise PipeWire's own
/// MemPtr allocation stands and this returns `Ok(None)`.
///
/// # Safety
/// `raw` must be the valid `pw_buffer` handed to an `add_buffer` callback,
/// and must only be used for the duration of that callback.
pub(super) unsafe fn fill_dma_data(
    raw: *mut pipewire::sys::pw_buffer,
    gbm: &GbmDevice<DrmDeviceFd>,
    w: u32,
    h: u32,
) -> Result<Option<DmaSlot>, Box<dyn std::error::Error>> {
    let spa_buf = (*raw).buffer;
    if spa_buf.is_null() || (*spa_buf).n_datas < 1 {
        return Ok(None);
    }
    let data = &mut *(*spa_buf).datas;
    // At this point `type_` is the negotiated type MASK; the producer picks
    // one.
    if data.type_ & (1 << spa::sys::SPA_DATA_DmaBuf) == 0 {
        return Ok(None);
    }
    let slot = alloc_dma_slot(gbm, w, h)?;
    data.type_ = spa::sys::SPA_DATA_DmaBuf;
    data.flags = spa::buffer::DataFlags::READWRITE.bits();
    data.fd = slot.fd;
    data.mapoffset = 0;
    data.maxsize = slot.stride as u32 * h;
    Ok(Some(slot))
}

/// The fd stored in `raw`'s first `spa_data`, to find the matching slot on
/// `remove_buffer`.
///
/// # Safety
/// Same contract as [`fill_dma_data`], for a `remove_buffer` callback.
pub(super) unsafe fn buffer_fd(raw: *mut pipewire::sys::pw_buffer) -> Option<i64> {
    let spa_buf = (*raw).buffer;
    if spa_buf.is_null() || (*spa_buf).n_datas < 1 {
        return None;
    }
    Some((*(*spa_buf).datas).fd)
}
