#![allow(non_camel_case_types)]

use {
    crate::{
        format::{formats, Format},
        utils::oserror::OsError,
        video::{
            dmabuf::{DmaBuf, DmaBufIds, DmaBufPlane, PlaneVec},
            drm::{Drm, DrmError},
            Modifier, INVALID_MODIFIER,
        },
    },
    std::{
        fmt::{Debug, Formatter},
        ptr,
        rc::Rc,
        slice,
    },
    thiserror::Error,
    uapi::{c, OwnedFd},
};

#[derive(Debug, Error)]
pub enum GbmError {
    #[error("The drm subsystem returned an error")]
    Drm(#[from] DrmError),
    #[error("Cloud not create a gbm device")]
    CreateDevice,
    #[error("Cloud not create a gbm buffer")]
    CreateBo(#[source] OsError),
    #[error("gbm buffer has an unknown format")]
    UnknownFormat,
    #[error("Could not retrieve a drm-buf fd")]
    DrmFd,
    #[error("Could not map bo")]
    MapBo(#[source] OsError),
    #[error("Tried to allocate a buffer with no modifier")]
    NoModifier,
}

pub type Device = u8;
type Bo = u8;

pub const GBM_BO_USE_SCANOUT: u32 = 1 << 0;
#[allow(dead_code)]
pub const GBM_BO_USE_CURSOR: u32 = 1 << 1;
pub const GBM_BO_USE_RENDERING: u32 = 1 << 2;
#[allow(dead_code)]
pub const GBM_BO_USE_WRITE: u32 = 1 << 3;
pub const GBM_BO_USE_LINEAR: u32 = 1 << 4;
#[allow(dead_code)]
pub const GBM_BO_USE_PROTECTED: u32 = 1 << 5;

#[allow(dead_code)]
const GBM_BO_IMPORT_WL_BUFFER: u32 = 0x5501;
#[allow(dead_code)]
const GBM_BO_IMPORT_EGL_IMAGE: u32 = 0x5502;
#[allow(dead_code)]
const GBM_BO_IMPORT_FD: u32 = 0x5503;
const GBM_BO_IMPORT_FD_MODIFIER: u32 = 0x5504;

const GBM_BO_TRANSFER_READ: u32 = 1 << 0;
#[allow(dead_code)]
const GBM_BO_TRANSFER_WRITE: u32 = 1 << 1;
#[allow(dead_code)]
const GBM_BO_TRANSFER_READ_WRITE: u32 = GBM_BO_TRANSFER_READ | GBM_BO_TRANSFER_WRITE;

#[repr(C)]
struct gbm_import_fd_modifier_data {
    width: u32,
    height: u32,
    format: u32,
    num_fds: u32,
    fds: [c::c_int; 4],
    strides: [c::c_int; 4],
    offsets: [c::c_int; 4],
    modifier: u64,
}

#[link(name = "gbm")]
extern "C" {
    fn gbm_create_device(fd: c::c_int) -> *mut Device;
    fn gbm_device_destroy(dev: *mut Device);

    fn gbm_bo_import(dev: *mut Device, ty: u32, buffer: *mut u8, flags: u32) -> *mut Bo;
    fn gbm_bo_create_with_modifiers2(
        dev: *mut Device,
        width: u32,
        height: u32,
        format: u32,
        modifiers: *const u64,
        count: c::c_uint,
        flags: u32,
    ) -> *mut Bo;
    fn gbm_bo_destroy(bo: *mut Bo);
    fn gbm_bo_get_plane_count(bo: *mut Bo) -> c::c_int;
    fn gbm_bo_get_width(bo: *mut Bo) -> u32;
    fn gbm_bo_get_height(bo: *mut Bo) -> u32;
    #[allow(dead_code)]
    fn gbm_bo_get_stride(bo: *mut Bo) -> u32;
    fn gbm_bo_get_modifier(bo: *mut Bo) -> u64;
    fn gbm_bo_get_stride_for_plane(bo: *mut Bo, plane: c::c_int) -> u32;
    fn gbm_bo_get_fd_for_plane(bo: *mut Bo, plane: c::c_int) -> c::c_int;
    fn gbm_bo_get_offset(bo: *mut Bo, plane: c::c_int) -> u32;
    fn gbm_bo_get_format(bo: *mut Bo) -> u32;
    #[allow(dead_code)]
    fn gbm_bo_get_bpp(bo: *mut Bo) -> u32;
    fn gbm_bo_map(
        bo: *mut Bo,
        x: u32,
        y: u32,
        width: u32,
        height: u32,
        flags: u32,
        strid: *mut u32,
        map_data: *mut *mut u8,
    ) -> *mut u8;
    fn gbm_bo_unmap(bo: *mut Bo, map_data: *mut u8);
}

pub struct GbmDevice {
    pub drm: Drm,
    dev: *mut Device,
}

impl Debug for GbmDevice {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("GbmDevice").finish_non_exhaustive()
    }
}

struct BoHolder {
    bo: *mut Bo,
}

pub struct GbmBo {
    bo: BoHolder,
    dmabuf: DmaBuf,
}

pub struct GbmBoMap {
    bo: Rc<GbmBo>,
    data: *mut [u8],
    opaque: *mut u8,
}

impl GbmBoMap {
    pub unsafe fn data(&self) -> &[u8] {
        &*self.data
    }
}

unsafe fn export_bo(dmabuf_ids: &DmaBufIds, bo: *mut Bo) -> Result<DmaBuf, GbmError> {
    Ok(DmaBuf {
        id: dmabuf_ids.next(),
        width: gbm_bo_get_width(bo) as _,
        height: gbm_bo_get_height(bo) as _,
        modifier: gbm_bo_get_modifier(bo),
        format: {
            let format = gbm_bo_get_format(bo);
            match formats().get(&format).copied() {
                Some(f) => f,
                _ => return Err(GbmError::UnknownFormat),
            }
        },
        planes: {
            let mut planes = PlaneVec::new();
            for plane in 0..gbm_bo_get_plane_count(bo) {
                let offset = gbm_bo_get_offset(bo, plane);
                let stride = gbm_bo_get_stride_for_plane(bo, plane);
                let fd = gbm_bo_get_fd_for_plane(bo, plane);
                if fd < 0 {
                    return Err(GbmError::DrmFd);
                }
                planes.push(DmaBufPlane {
                    offset,
                    stride,
                    fd: Rc::new(OwnedFd::new(fd)),
                })
            }
            planes
        },
    })
}

impl GbmDevice {
    pub fn new(drm: &Drm) -> Result<Self, GbmError> {
        let drm = drm.dup_render()?;
        let dev = unsafe { gbm_create_device(drm.raw()) };
        if dev.is_null() {
            Err(GbmError::CreateDevice)
        } else {
            Ok(Self { drm, dev })
        }
    }

    pub fn raw(&self) -> *mut Device {
        self.dev
    }

    pub fn create_bo<'a>(
        &self,
        dma_buf_ids: &DmaBufIds,
        width: i32,
        height: i32,
        format: &Format,
        modifiers: impl IntoIterator<Item = &'a Modifier>,
        mut usage: u32,
    ) -> Result<GbmBo, GbmError> {
        unsafe {
            let modifiers: Vec<Modifier> = modifiers.into_iter().copied().collect();
            if modifiers.is_empty() {
                return Err(GbmError::NoModifier);
            }
            let (modifiers, n_modifiers) = if modifiers == [INVALID_MODIFIER] {
                (ptr::null(), 0)
            } else {
                usage &= !GBM_BO_USE_LINEAR;
                (modifiers.as_ptr() as _, modifiers.len() as _)
            };
            let bo = gbm_bo_create_with_modifiers2(
                self.dev,
                width as _,
                height as _,
                format.drm,
                modifiers,
                n_modifiers,
                usage,
            );
            if bo.is_null() {
                return Err(GbmError::CreateBo(OsError::default()));
            }
            let bo = BoHolder { bo };
            let dma = export_bo(dma_buf_ids, bo.bo)?;
            Ok(GbmBo { bo, dmabuf: dma })
        }
    }

    pub fn import_dmabuf(&self, dmabuf: &DmaBuf, usage: u32) -> Result<GbmBo, GbmError> {
        let mut import = gbm_import_fd_modifier_data {
            width: dmabuf.width as _,
            height: dmabuf.height as _,
            format: dmabuf.format.drm as _,
            num_fds: dmabuf.planes.len() as _,
            fds: [0; 4],
            strides: [0; 4],
            offsets: [0; 4],
            modifier: dmabuf.modifier,
        };
        for (i, plane) in dmabuf.planes.iter().enumerate() {
            import.fds[i] = plane.fd.raw();
            import.strides[i] = plane.stride as _;
            import.offsets[i] = plane.offset as _;
        }
        unsafe {
            let bo = gbm_bo_import(
                self.dev,
                GBM_BO_IMPORT_FD_MODIFIER,
                &mut import as *const _ as _,
                usage,
            );
            if bo.is_null() {
                return Err(GbmError::CreateBo(OsError::default()));
            }
            let bo = BoHolder { bo };
            Ok(GbmBo {
                bo,
                dmabuf: dmabuf.clone(),
            })
        }
    }
}

impl Drop for GbmDevice {
    fn drop(&mut self) {
        unsafe {
            gbm_device_destroy(self.dev);
        }
    }
}

impl GbmBo {
    pub fn dmabuf(&self) -> &DmaBuf {
        &self.dmabuf
    }

    pub fn map(self: &Rc<Self>) -> Result<GbmBoMap, GbmError> {
        let mut stride = 0;
        let mut map_data = ptr::null_mut();
        unsafe {
            let map = gbm_bo_map(
                self.bo.bo,
                0,
                0,
                self.dmabuf.width as _,
                self.dmabuf.height as _,
                GBM_BO_TRANSFER_READ,
                &mut stride,
                &mut map_data,
            );
            if map.is_null() {
                return Err(GbmError::MapBo(OsError::default()));
            }
            let map = slice::from_raw_parts_mut(map, (stride * self.dmabuf.height as u32) as usize);
            Ok(GbmBoMap {
                bo: self.clone(),
                data: map,
                opaque: map_data,
            })
        }
    }
}

impl Drop for GbmBoMap {
    fn drop(&mut self) {
        unsafe {
            gbm_bo_unmap(self.bo.bo.bo, self.opaque);
        }
    }
}

impl Drop for BoHolder {
    fn drop(&mut self) {
        unsafe {
            gbm_bo_destroy(self.bo);
        }
    }
}
