//!
//! Bindings to the DRM's modesetting capabilities.
//!

#![allow(clippy::too_many_arguments)]

use crate::ioctl;
use drm_sys::*;

use std::{io, os::unix::io::BorrowedFd};

/// Enumerate most card resources.
pub fn get_resources(
    fd: BorrowedFd<'_>,
    mut fbs: Option<&mut Vec<u32>>,
    mut crtcs: Option<&mut Vec<u32>>,
    mut connectors: Option<&mut Vec<u32>>,
    mut encoders: Option<&mut Vec<u32>>,
) -> io::Result<drm_mode_card_res> {
    let mut sizes = drm_mode_card_res::default();
    unsafe {
        ioctl::mode::get_resources(fd, &mut sizes)?;
    }

    map_reserve!(fbs, sizes.count_fbs as usize);
    map_reserve!(crtcs, sizes.count_crtcs as usize);
    map_reserve!(connectors, sizes.count_connectors as usize);
    map_reserve!(encoders, sizes.count_encoders as usize);

    let mut res = drm_mode_card_res {
        fb_id_ptr: map_ptr!(&fbs),
        crtc_id_ptr: map_ptr!(&crtcs),
        connector_id_ptr: map_ptr!(&connectors),
        encoder_id_ptr: map_ptr!(&encoders),
        count_fbs: map_len!(&fbs),
        count_crtcs: map_len!(&crtcs),
        count_connectors: map_len!(&connectors),
        count_encoders: map_len!(&encoders),
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_resources(fd, &mut res)?;
    }

    map_set!(fbs, res.count_fbs as usize);
    map_set!(crtcs, res.count_crtcs as usize);
    map_set!(connectors, res.count_connectors as usize);
    map_set!(encoders, res.count_encoders as usize);

    Ok(res)
}

/// Enumerate plane resources.
pub fn get_plane_resources(
    fd: BorrowedFd<'_>,
    mut planes: Option<&mut Vec<u32>>,
) -> io::Result<drm_mode_get_plane_res> {
    let mut sizes = drm_mode_get_plane_res::default();
    unsafe {
        ioctl::mode::get_plane_resources(fd, &mut sizes)?;
    }

    if planes.is_none() {
        return Ok(sizes);
    }

    map_reserve!(planes, sizes.count_planes as usize);

    let mut res = drm_mode_get_plane_res {
        plane_id_ptr: map_ptr!(&planes),
        count_planes: sizes.count_planes,
    };

    unsafe {
        ioctl::mode::get_plane_resources(fd, &mut res)?;
    }

    map_set!(planes, res.count_planes as usize);

    Ok(res)
}

/// Get info about a framebuffer.
pub fn get_framebuffer(fd: BorrowedFd<'_>, fb_id: u32) -> io::Result<drm_mode_fb_cmd> {
    let mut info = drm_mode_fb_cmd {
        fb_id,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_fb(fd, &mut info)?;
    }

    Ok(info)
}

/// Add a new framebuffer.
pub fn add_fb(
    fd: BorrowedFd<'_>,
    width: u32,
    height: u32,
    pitch: u32,
    bpp: u32,
    depth: u32,
    handle: u32,
) -> io::Result<drm_mode_fb_cmd> {
    let mut fb = drm_mode_fb_cmd {
        width,
        height,
        pitch,
        bpp,
        depth,
        handle,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::add_fb(fd, &mut fb)?;
    }

    Ok(fb)
}

/// Get info about a framebuffer (with modifiers).
pub fn get_framebuffer2(fd: BorrowedFd<'_>, fb_id: u32) -> io::Result<drm_mode_fb_cmd2> {
    let mut info = drm_mode_fb_cmd2 {
        fb_id,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_fb2(fd, &mut info)?;
    }

    Ok(info)
}

/// Add a new framebuffer (with modifiers)
pub fn add_fb2(
    fd: BorrowedFd<'_>,
    width: u32,
    height: u32,
    fmt: u32,
    handles: &[u32; 4],
    pitches: &[u32; 4],
    offsets: &[u32; 4],
    modifier: &[u64; 4],
    flags: u32,
) -> io::Result<drm_mode_fb_cmd2> {
    let mut fb = drm_mode_fb_cmd2 {
        width,
        height,
        pixel_format: fmt,
        flags,
        handles: *handles,
        pitches: *pitches,
        offsets: *offsets,
        modifier: *modifier,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::add_fb2(fd, &mut fb)?;
    }

    Ok(fb)
}

/// Remove a framebuffer.
pub fn rm_fb(fd: BorrowedFd<'_>, mut id: u32) -> io::Result<()> {
    unsafe {
        ioctl::mode::rm_fb(fd, &mut id)?;
    }

    Ok(())
}

/// Mark a framebuffer as dirty.
pub fn dirty_fb(
    fd: BorrowedFd<'_>,
    fb_id: u32,
    clips: &[drm_clip_rect],
) -> io::Result<drm_mode_fb_dirty_cmd> {
    let mut dirty = drm_mode_fb_dirty_cmd {
        fb_id,
        num_clips: clips.len() as _,
        clips_ptr: clips.as_ptr() as _,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::dirty_fb(fd, &mut dirty)?;
    }

    Ok(dirty)
}

/// Get info about a CRTC
pub fn get_crtc(fd: BorrowedFd<'_>, crtc_id: u32) -> io::Result<drm_mode_crtc> {
    let mut info = drm_mode_crtc {
        crtc_id,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_crtc(fd, &mut info)?;
    }

    Ok(info)
}

/// Set CRTC state
pub fn set_crtc(
    fd: BorrowedFd<'_>,
    crtc_id: u32,
    fb_id: u32,
    x: u32,
    y: u32,
    conns: &[u32],
    mode: Option<drm_mode_modeinfo>,
) -> io::Result<drm_mode_crtc> {
    let mut crtc = drm_mode_crtc {
        set_connectors_ptr: conns.as_ptr() as _,
        count_connectors: conns.len() as _,
        crtc_id,
        fb_id,
        x,
        y,
        mode_valid: match mode {
            Some(_) => 1,
            None => 0,
        },
        mode: mode.unwrap_or_default(),
        ..Default::default()
    };

    unsafe {
        ioctl::mode::set_crtc(fd, &mut crtc)?;
    }

    Ok(crtc)
}

/// Get CRTC gamma ramp
pub fn get_gamma(
    fd: BorrowedFd<'_>,
    crtc_id: u32,
    size: usize,
    red: &mut [u16],
    green: &mut [u16],
    blue: &mut [u16],
) -> io::Result<drm_mode_crtc_lut> {
    let mut lut = drm_mode_crtc_lut {
        crtc_id,
        gamma_size: size as _,
        red: red.as_mut_ptr() as _,
        green: green.as_mut_ptr() as _,
        blue: blue.as_mut_ptr() as _,
    };

    unsafe {
        ioctl::mode::get_gamma(fd, &mut lut)?;
    }

    Ok(lut)
}

/// Set CRTC gamma ramp
pub fn set_gamma(
    fd: BorrowedFd<'_>,
    crtc_id: u32,
    size: usize,
    red: &[u16],
    green: &[u16],
    blue: &[u16],
) -> io::Result<drm_mode_crtc_lut> {
    let mut lut = drm_mode_crtc_lut {
        crtc_id,
        gamma_size: size as _,
        red: red.as_ptr() as _,
        green: green.as_ptr() as _,
        blue: blue.as_ptr() as _,
    };

    unsafe {
        ioctl::mode::set_gamma(fd, &mut lut)?;
    }

    Ok(lut)
}

/// Set cursor state
///
/// The buffer must be allocated using the buffer manager of the driver (GEM or TTM). It is not
/// allowed to be a dumb buffer.
#[deprecated = "use a cursor plane instead"]
pub fn set_cursor(
    fd: BorrowedFd<'_>,
    crtc_id: u32,
    buf_id: u32,
    width: u32,
    height: u32,
) -> io::Result<drm_mode_cursor> {
    let mut cursor = drm_mode_cursor {
        flags: DRM_MODE_CURSOR_BO,
        crtc_id,
        width,
        height,
        handle: buf_id,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::cursor(fd, &mut cursor)?;
    }

    Ok(cursor)
}

/// Set cursor state (with hotspot position)
///
/// The buffer must be allocated using the buffer manager of the driver (GEM or TTM). It is not
/// allowed to be a dumb buffer.
///
/// The hotspot position is used to coordinate the guest and host cursor location in case of
/// virtualization.
#[deprecated = "use a cursor plane instead"]
pub fn set_cursor2(
    fd: BorrowedFd<'_>,
    crtc_id: u32,
    buf_id: u32,
    width: u32,
    height: u32,
    hot_x: i32,
    hot_y: i32,
) -> io::Result<drm_mode_cursor2> {
    let mut cursor = drm_mode_cursor2 {
        flags: DRM_MODE_CURSOR_BO,
        crtc_id,
        width,
        height,
        handle: buf_id,
        hot_x,
        hot_y,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::cursor2(fd, &mut cursor)?;
    }

    Ok(cursor)
}

/// Move cursor
#[deprecated = "use a cursor plane instead"]
pub fn move_cursor(
    fd: BorrowedFd<'_>,
    crtc_id: u32,
    x: i32,
    y: i32,
) -> io::Result<drm_mode_cursor> {
    let mut cursor = drm_mode_cursor {
        flags: DRM_MODE_CURSOR_MOVE,
        crtc_id,
        x,
        y,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::cursor(fd, &mut cursor)?;
    }

    Ok(cursor)
}

/// Get info about a connector
pub fn get_connector(
    fd: BorrowedFd<'_>,
    connector_id: u32,
    mut props: Option<&mut Vec<u32>>,
    mut prop_values: Option<&mut Vec<u64>>,
    mut modes: Option<&mut Vec<drm_mode_modeinfo>>,
    mut encoders: Option<&mut Vec<u32>>,
    force_probe: bool,
) -> io::Result<drm_mode_get_connector> {
    assert_eq!(props.is_some(), prop_values.is_some());

    let tmp_mode = drm_mode_modeinfo::default();
    let mut sizes = drm_mode_get_connector {
        connector_id,
        modes_ptr: if force_probe {
            0
        } else {
            &tmp_mode as *const _ as _
        },
        count_modes: if force_probe { 0 } else { 1 },
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_connector(fd, &mut sizes)?;
    }

    let info = loop {
        map_reserve!(props, sizes.count_props as usize);
        map_reserve!(prop_values, sizes.count_props as usize);
        map_reserve!(modes, sizes.count_modes as usize);
        map_reserve!(encoders, sizes.count_encoders as usize);

        let mut info = drm_mode_get_connector {
            connector_id,
            encoders_ptr: map_ptr!(&encoders),
            modes_ptr: match &mut modes {
                Some(b) => b.as_mut_ptr() as _,
                None => {
                    if force_probe {
                        0 as _
                    } else {
                        &tmp_mode as *const _ as _
                    }
                }
            },
            props_ptr: map_ptr!(&props),
            prop_values_ptr: map_ptr!(&prop_values),
            count_modes: match &modes {
                Some(b) => b.capacity() as _,
                None => {
                    if force_probe {
                        0
                    } else {
                        1
                    }
                }
            },
            count_props: map_len!(&props),
            count_encoders: map_len!(&encoders),
            ..Default::default()
        };

        unsafe {
            ioctl::mode::get_connector(fd, &mut info)?;
        }

        if info.count_modes == sizes.count_modes
            && info.count_encoders == sizes.count_encoders
            && info.count_props == sizes.count_props
        {
            break info;
        } else {
            sizes = info;
        }
    };

    map_set!(modes, info.count_modes as usize);
    map_set!(props, info.count_props as usize);
    map_set!(prop_values, info.count_props as usize);
    map_set!(encoders, info.count_encoders as usize);

    Ok(info)
}

/// Get info about an encoder
pub fn get_encoder(fd: BorrowedFd<'_>, encoder_id: u32) -> io::Result<drm_mode_get_encoder> {
    let mut info = drm_mode_get_encoder {
        encoder_id,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_encoder(fd, &mut info)?;
    }

    Ok(info)
}

/// Get info about a plane.
pub fn get_plane(
    fd: BorrowedFd<'_>,
    plane_id: u32,
    mut formats: Option<&mut Vec<u32>>,
) -> io::Result<drm_mode_get_plane> {
    let mut sizes = drm_mode_get_plane {
        plane_id,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_plane(fd, &mut sizes)?;
    }

    if formats.is_none() {
        return Ok(sizes);
    }

    map_reserve!(formats, sizes.count_format_types as usize);

    let mut info = drm_mode_get_plane {
        plane_id,
        count_format_types: sizes.count_format_types,
        format_type_ptr: map_ptr!(&formats),
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_plane(fd, &mut info)?;
    }

    map_set!(formats, info.count_format_types as usize);

    Ok(info)
}

/// Set plane state.
pub fn set_plane(
    fd: BorrowedFd<'_>,
    plane_id: u32,
    crtc_id: u32,
    fb_id: u32,
    flags: u32,
    crtc_x: i32,
    crtc_y: i32,
    crtc_w: u32,
    crtc_h: u32,
    src_x: u32,
    src_y: u32,
    src_w: u32,
    src_h: u32,
) -> io::Result<drm_mode_set_plane> {
    let mut plane = drm_mode_set_plane {
        plane_id,
        crtc_id,
        fb_id,
        flags,
        crtc_x,
        crtc_y,
        crtc_w,
        crtc_h,
        src_x,
        src_y,
        src_h,
        src_w,
    };

    unsafe {
        ioctl::mode::set_plane(fd, &mut plane)?;
    }

    Ok(plane)
}

/// Get property
pub fn get_property(
    fd: BorrowedFd<'_>,
    prop_id: u32,
    mut values: Option<&mut Vec<u64>>,
    mut enums: Option<&mut Vec<drm_mode_property_enum>>,
) -> io::Result<drm_mode_get_property> {
    let mut prop = drm_mode_get_property {
        prop_id,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_property(fd, &mut prop)?;
    }

    // There is no need to call get_property() twice if there is nothing else to retrieve.
    if prop.count_values == 0 && prop.count_enum_blobs == 0 {
        return Ok(prop);
    }

    map_reserve!(values, prop.count_values as usize);
    map_reserve!(enums, prop.count_enum_blobs as usize);

    prop.values_ptr = map_ptr!(&values);
    prop.enum_blob_ptr = map_ptr!(&enums);

    unsafe {
        ioctl::mode::get_property(fd, &mut prop)?;
    }

    map_set!(values, prop.count_values as usize);
    map_set!(enums, prop.count_enum_blobs as usize);

    Ok(prop)
}

/// Set property
pub fn set_connector_property(
    fd: BorrowedFd<'_>,
    connector_id: u32,
    prop_id: u32,
    value: u64,
) -> io::Result<drm_mode_connector_set_property> {
    let mut prop = drm_mode_connector_set_property {
        value,
        prop_id,
        connector_id,
    };

    unsafe {
        ioctl::mode::connector_set_property(fd, &mut prop)?;
    }

    Ok(prop)
}

/// Get the value of a property blob
pub fn get_property_blob(
    fd: BorrowedFd<'_>,
    blob_id: u32,
    mut data: Option<&mut Vec<u8>>,
) -> io::Result<drm_mode_get_blob> {
    let mut sizes = drm_mode_get_blob {
        blob_id,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_blob(fd, &mut sizes)?;
    }

    if data.is_none() {
        return Ok(sizes);
    }

    map_reserve!(data, sizes.length as usize);

    let mut blob = drm_mode_get_blob {
        blob_id,
        length: sizes.length,
        data: map_ptr!(&data),
    };

    unsafe {
        ioctl::mode::get_blob(fd, &mut blob)?;
    }

    map_set!(data, blob.length as usize);

    Ok(blob)
}

/// Create a property blob
pub fn create_property_blob(
    fd: BorrowedFd<'_>,
    data: &mut [u8],
) -> io::Result<drm_mode_create_blob> {
    let mut blob = drm_mode_create_blob {
        data: data.as_mut_ptr() as _,
        length: data.len() as _,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::create_blob(fd, &mut blob)?;
    }

    Ok(blob)
}

/// Destroy a property blob
pub fn destroy_property_blob(fd: BorrowedFd<'_>, id: u32) -> io::Result<drm_mode_destroy_blob> {
    let mut blob = drm_mode_destroy_blob { blob_id: id };

    unsafe {
        ioctl::mode::destroy_blob(fd, &mut blob)?;
    }

    Ok(blob)
}

/// Get properties from an object
pub fn get_properties(
    fd: BorrowedFd<'_>,
    obj_id: u32,
    obj_type: u32,
    mut props: Option<&mut Vec<u32>>,
    mut values: Option<&mut Vec<u64>>,
) -> io::Result<drm_mode_obj_get_properties> {
    assert_eq!(props.is_some(), values.is_some());

    let mut sizes = drm_mode_obj_get_properties {
        obj_id,
        obj_type,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::obj_get_properties(fd, &mut sizes)?;
    }

    map_reserve!(props, sizes.count_props as usize);
    map_reserve!(values, sizes.count_props as usize);

    let mut info = drm_mode_obj_get_properties {
        props_ptr: map_ptr!(&props),
        prop_values_ptr: map_ptr!(&values),
        count_props: map_len!(&props),
        obj_id,
        obj_type,
    };

    unsafe {
        ioctl::mode::obj_get_properties(fd, &mut info)?;
    }

    map_set!(props, info.count_props as usize);
    map_set!(values, info.count_props as usize);

    Ok(info)
}

/// Set the properties of an object
pub fn set_property(
    fd: BorrowedFd<'_>,
    prop_id: u32,
    obj_id: u32,
    obj_type: u32,
    value: u64,
) -> io::Result<()> {
    let mut prop = drm_mode_obj_set_property {
        value,
        prop_id,
        obj_id,
        obj_type,
    };

    unsafe {
        ioctl::mode::obj_set_property(fd, &mut prop)?;
    }

    Ok(())
}

/// Schedule a page flip
pub fn page_flip(
    fd: BorrowedFd<'_>,
    crtc_id: u32,
    fb_id: u32,
    flags: u32,
    sequence: u32,
) -> io::Result<()> {
    let mut flip = drm_mode_crtc_page_flip {
        crtc_id,
        fb_id,
        flags,
        // Same struct as drm_mode_crtc_page_flip_target
        reserved: sequence,
        user_data: crtc_id as _,
    };

    unsafe {
        ioctl::mode::crtc_page_flip(fd, &mut flip)?;
    }

    Ok(())
}

/// Atomically set properties
pub fn atomic_commit(
    fd: BorrowedFd<'_>,
    flags: u32,
    objs: &mut [u32],
    prop_counts: &mut [u32],
    props: &mut [u32],
    values: &mut [u64],
) -> io::Result<()> {
    let mut atomic = drm_mode_atomic {
        flags,
        count_objs: objs.len() as _,
        objs_ptr: objs.as_mut_ptr() as _,
        count_props_ptr: prop_counts.as_mut_ptr() as _,
        props_ptr: props.as_mut_ptr() as _,
        prop_values_ptr: values.as_mut_ptr() as _,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::atomic(fd, &mut atomic)?;
    }

    Ok(())
}

/// Create a drm lease
pub fn create_lease(
    fd: BorrowedFd<'_>,
    objects: &[u32],
    flags: u32,
) -> io::Result<drm_mode_create_lease> {
    let mut data = drm_mode_create_lease {
        object_ids: objects.as_ptr() as _,
        object_count: objects.len() as u32,
        flags,
        ..Default::default()
    };

    unsafe {
        ioctl::mode::create_lease(fd, &mut data)?;
    }

    Ok(data)
}

/// List all active drm leases
pub fn list_lessees(
    fd: BorrowedFd<'_>,
    mut lessees: Option<&mut Vec<u32>>,
) -> io::Result<drm_mode_list_lessees> {
    let mut sizes = drm_mode_list_lessees::default();

    unsafe {
        ioctl::mode::list_lessees(fd, &mut sizes)?;
    };

    map_reserve!(lessees, sizes.count_lessees as usize);

    let mut data = drm_mode_list_lessees {
        lessees_ptr: map_ptr!(&lessees),
        count_lessees: map_len!(&lessees),
        ..Default::default()
    };

    unsafe {
        ioctl::mode::list_lessees(fd, &mut data)?;
    };

    map_set!(lessees, data.count_lessees as usize);

    Ok(data)
}

/// Get leased objects for a lease file descriptor
pub fn get_lease(
    fd: BorrowedFd<'_>,
    mut objects: Option<&mut Vec<u32>>,
) -> io::Result<drm_mode_get_lease> {
    let mut sizes = drm_mode_get_lease::default();

    unsafe {
        ioctl::mode::get_lease(fd, &mut sizes)?;
    }

    map_reserve!(objects, sizes.count_objects as usize);

    let mut data = drm_mode_get_lease {
        count_objects: map_len!(&objects),
        objects_ptr: map_ptr!(&objects),
        ..Default::default()
    };

    unsafe {
        ioctl::mode::get_lease(fd, &mut data)?;
    }

    map_set!(objects, data.count_objects as usize);

    Ok(data)
}

/// Revoke previously issued lease
pub fn revoke_lease(fd: BorrowedFd<'_>, lessee_id: u32) -> io::Result<()> {
    let mut data = drm_mode_revoke_lease { lessee_id };

    unsafe {
        ioctl::mode::revoke_lease(fd, &mut data)?;
    }

    Ok(())
}

///
/// Dumbbuffers are basic buffers that can be used for scanout.
///
pub mod dumbbuffer {
    use crate::ioctl;
    use drm_sys::*;

    use std::{io, os::unix::io::BorrowedFd};

    /// Create a dumb buffer
    pub fn create(
        fd: BorrowedFd<'_>,
        width: u32,
        height: u32,
        bpp: u32,
        flags: u32,
    ) -> io::Result<drm_mode_create_dumb> {
        let mut db = drm_mode_create_dumb {
            height,
            width,
            bpp,
            flags,
            ..Default::default()
        };

        unsafe {
            ioctl::mode::create_dumb(fd, &mut db)?;
        }

        Ok(db)
    }

    /// Destroy a dumb buffer
    pub fn destroy(fd: BorrowedFd<'_>, handle: u32) -> io::Result<drm_mode_destroy_dumb> {
        let mut db = drm_mode_destroy_dumb { handle };

        unsafe {
            ioctl::mode::destroy_dumb(fd, &mut db)?;
        }

        Ok(db)
    }

    /// Map a dump buffer and prep it for an mmap
    pub fn map(
        fd: BorrowedFd<'_>,
        handle: u32,
        pad: u32,
        offset: u64,
    ) -> io::Result<drm_mode_map_dumb> {
        let mut map = drm_mode_map_dumb {
            handle,
            pad,
            offset,
        };

        unsafe {
            ioctl::mode::map_dumb(fd, &mut map)?;
        }

        Ok(map)
    }
}
