//! FFmpeg's Vulkan hwcontext surface (`AVVulkanDeviceContext`, `AVVulkanFramesContext`,
//! `AVVkFrame`), bindgen-generated from the system headers at build time — see build.rs
//! for why this must not be hand-transcribed.
//!
//! The raw bindings use vulkan.h's own handle types (pointers on 64-bit). The [`ash`]
//! conversion helpers below cross between them and ash's u64-newtype handles; both sides
//! are the same underlying Vulkan object handles, so the casts are value-preserving.

#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
// bindgen's layout tests deref-null-pointer by design; silence the lints they trip.
#![allow(deref_nullptr)]
#![allow(unnecessary_transmutes)]

#[cfg(any(target_os = "linux", windows))]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

/// Conversions between the generated vulkan.h handle types and ash's.
#[cfg(any(target_os = "linux", windows))]
pub mod ashx {
    use super::*;
    use ash::vk::Handle as _;

    /// vulkan.h non-dispatchable handles are `*mut T` on 64-bit; ash's are `u64`
    /// newtypes. Same bits either way.
    pub fn image(h: VkImage) -> ash::vk::Image {
        ash::vk::Image::from_raw(h as u64)
    }

    pub fn semaphore(h: VkSemaphore) -> ash::vk::Semaphore {
        ash::vk::Semaphore::from_raw(h as u64)
    }

    // bindgen's enum repr is target-dependent: u32 on Linux (clang default), i32 on
    // MSVC — so the cast is required on one target and a same-type no-op on the other.
    #[allow(clippy::unnecessary_cast)]
    pub fn image_layout(l: VkImageLayout) -> ash::vk::ImageLayout {
        ash::vk::ImageLayout::from_raw(l as i32)
    }

    // --- ash → vulkan.h (filling AVVulkanDeviceContext) ---------------------------------

    pub fn to_instance(h: ash::vk::Instance) -> VkInstance {
        h.as_raw() as VkInstance
    }

    pub fn to_physical_device(h: ash::vk::PhysicalDevice) -> VkPhysicalDevice {
        h.as_raw() as VkPhysicalDevice
    }

    pub fn to_device(h: ash::vk::Device) -> VkDevice {
        h.as_raw() as VkDevice
    }

    /// ash's loader-level `vkGetInstanceProcAddr` as the header's PFN type. Both are the
    /// same C ABI function pointer (`extern "system"` == `extern "C"` on the platforms
    /// this crate builds for).
    pub fn to_get_proc_addr(
        f: unsafe extern "system" fn(
            ash::vk::Instance,
            *const std::ffi::c_char,
        ) -> ash::vk::PFN_vkVoidFunction,
    ) -> PFN_vkGetInstanceProcAddr {
        unsafe { std::mem::transmute(f) }
    }
}

#[cfg(all(test, any(target_os = "linux", windows)))]
mod tests {
    use super::*;

    /// The allocator runs (links against the system libavutil) and the struct is
    /// readable at the offsets bindgen computed — sem_value zero-initialized.
    #[test]
    fn vk_frame_alloc_links_and_zeroes() {
        unsafe {
            let f = av_vk_frame_alloc();
            assert!(!f.is_null(), "av_vk_frame_alloc returned NULL");
            assert_eq!((*f).sem_value[0], 0);
            assert_eq!((*f).queue_family[0], 0);
            // Leak the one test frame rather than binding av_free here.
        }
    }

    /// AV_NUM_DATA_POINTERS-sized arrays came through with the right length.
    #[test]
    fn frame_arrays_are_av_num_data_pointers() {
        let f: AVVkFrame = unsafe { std::mem::zeroed() };
        assert_eq!(f.img.len(), 8);
        assert_eq!(f.sem_value.len(), 8);
    }
}
