use crate::prelude::*;

use anyhow::Result;

use std::collections::HashSet;
use std::fmt;
use std::mem::MaybeUninit;
use std::ptr;
use std::sync::Arc;

use std::os::raw::c_char;
use std::os::raw::c_void;

use std::ffi::CStr;

Extensions!(InstanceExtensions, {
    (xlib_surface, "VK_KHR_xlib_surface"),
    (wayland_surface, "VK_KHR_wayland_surface"),
    (android_surface, "VK_KHR_android_surface"),
    (macos_surface, "VK_KHR_macos_surface"),
    (win32_surface, "VK_KHR_win32_surface"),
    (surface, "VK_KHR_surface"),
    (physical_device_properties2, "VK_KHR_get_physical_device_properties2"),
});

#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub struct VulkanDebugInfo {
    pub debugging: bool,
    pub steam_layer: bool,
    pub verbose: bool,
    pub renderdoc: bool,
}

pub struct Instance {
    _static_functions: StaticFunctions,
    _entry_functions: Option<EntryFunctions>,
    pub(crate) instance_functions: InstanceFunctions,
    instance_wsi_functions: InstanceWSIFunctions,
    physical_device_properties2_functions: PhysicalDeviceProperties2Functions,

    debug_report_callback_functions: DebugReportCallbackFunctions,
    instance: VkInstance,

    instance_extensions: InstanceExtensions,

    debug_report: Option<VkDebugReportCallbackEXT>,

    api_version: u32,
}

struct Layer {
    props: Vec<VkLayerProperties>,
}

impl Layer {
    fn create(entry_functions: &EntryFunctions) -> Result<Layer> {
        Ok(Layer {
            props: Instance::enumerate_layer_properties(
                entry_functions.vkEnumerateInstanceLayerProperties,
            )?,
        })
    }

    fn names(
        &self,
        debugging: bool,
        steam_layer: bool,
        verbose: bool,
        renderdoc: bool,
    ) -> Result<Vec<VkString>> {
        let mut names = Vec::new();

        for i in 0..self.props.len() {
            let name_string = self.props[i].layer_name()?;
            let name = name_string.as_str();

            if debugging && name == "VK_LAYER_KHRONOS_validation" {
                names.push(name_string.clone());
            }

            if verbose && name == "VK_LAYER_LUNARG_api_dump" {
                names.push(name_string.clone());
            }

            if renderdoc && name == "VK_LAYER_RENDERDOC_Capture" {
                names.push(name_string.clone());
            }

            if steam_layer
                && (name == "VK_LAYER_VALVE_steam_overlay_64"
                    || name == "VK_LAYER_VALVE_steam_overlay_32")
            {
                names.push(name_string.clone());
            }
        }

        Ok(names)
    }
}

impl Instance {
    pub fn preinitialized(
        instance: VkInstance,
        proc_addr: PFN_vkGetInstanceProcAddr,
        extensions: &[VkString],
        api_version: Option<u32>,
    ) -> Result<Arc<Instance>> {
        let static_functions = StaticFunctions {
            _lib: None,
            vkGetInstanceProcAddr: proc_addr,
        };

        let instance_functions = InstanceFunctions::new(&static_functions, instance);

        let instance_wsi_functions = InstanceWSIFunctions::new(&static_functions, instance);

        let physical_device_properties2_functions =
            PhysicalDeviceProperties2Functions::new(&static_functions, instance);

        let debug_report_callback_functions =
            DebugReportCallbackFunctions::new(&static_functions, instance);

        let instance_extensions = InstanceExtensions::from_list(extensions);

        let instance = Arc::new(Instance {
            _static_functions: static_functions,
            _entry_functions: None,
            instance_functions,
            instance_wsi_functions,
            physical_device_properties2_functions,

            debug_report_callback_functions,

            instance,

            instance_extensions,

            debug_report: None,

            api_version: api_version.unwrap_or(VK_MAKE_VERSION(1, 0, 0)),
        });

        Ok(instance)
    }

    pub fn new(
        app_info: VkApplicationInfo<'_>,
        debug_info: VulkanDebugInfo,
        mut extensions: InstanceExtensions,
    ) -> Result<Arc<Instance>> {
        // required in physical device
        extensions.physical_device_properties2 = true;

        let static_functions = StaticFunctions::load()?;
        let entry_functions = EntryFunctions::new(&static_functions);

        let layers = if debug_info.debugging {
            let layer_object = Layer::create(&entry_functions)?;

            layer_object.names(
                true,
                debug_info.steam_layer,
                debug_info.verbose,
                debug_info.renderdoc,
            )?
        } else if debug_info.renderdoc {
            let layer_object = Layer::create(&entry_functions)?;

            // render doc only
            layer_object.names(false, false, false, true)?
        } else {
            Vec::new()
        };

        let mut checked_extensions = Vec::new();
        let mut extension_list = extensions.as_list();

        if debug_info.debugging || debug_info.renderdoc {
            extension_list.push(VkString::new("VK_EXT_debug_report"));
        }

        if !extension_list.is_empty() {
            let extension_properties = Self::get_extension_properties(&entry_functions, &layers)?;

            for extension in extension_list {
                for ext_prop in &extension_properties {
                    if extension == *ext_prop {
                        checked_extensions.push(extension);
                        break;
                    }
                }
            }
        }

        // instance create info
        let layer_names = VkNames::new(layers.as_slice());
        let extension_names = VkNames::new(checked_extensions.as_slice());
        let instance_ci = VkInstanceCreateInfo::new(
            VK_INSTANCE_CREATE_NULL_BIT,
            &app_info,
            &layer_names,
            &extension_names,
        );

        println!("enabled layers ({}):", layer_names.len());

        for layer_name in layer_names.iter() {
            println!("\t- {:?}", layer_name);
        }

        println!("\nenabled instance extensions ({}):", extension_names.len());

        for extension_name in extension_names.iter() {
            println!("\t- {:?}", extension_name);
        }

        println!();

        let enabled_extensions = InstanceExtensions::from_list(&checked_extensions);

        if let Err(missing_extensions) = extensions.check_availability(&enabled_extensions) {
            for m in missing_extensions {
                println!("{}", m);
            }
        }

        let instance = unsafe {
            let mut instance = MaybeUninit::uninit();
            let result =
                entry_functions.vkCreateInstance(&instance_ci, ptr::null(), instance.as_mut_ptr());

            if result == VK_SUCCESS {
                instance.assume_init()
            } else {
                return Err(anyhow::Error::new(result));
            }
        };

        let instance_functions = InstanceFunctions::new(&static_functions, instance);
        let instance_wsi_functions = InstanceWSIFunctions::new(&static_functions, instance);
        let physical_device_properties2_functions =
            PhysicalDeviceProperties2Functions::new(&static_functions, instance);
        let debug_report_callback_functions =
            DebugReportCallbackFunctions::new(&static_functions, instance);

        let mut instance = Instance {
            _static_functions: static_functions,
            _entry_functions: Some(entry_functions),
            instance_functions,
            instance_wsi_functions,
            physical_device_properties2_functions,

            debug_report_callback_functions,

            instance,

            instance_extensions: enabled_extensions,

            debug_report: None,

            api_version: app_info.apiVersion,
        };

        if !layers.is_empty() {
            if let Err(msg) = instance.create_debug_report() {
                println!("failed creating debug report: {}", msg);
            }
        }

        Ok(Arc::new(instance))
    }

    pub(crate) fn api_version(&self) -> u32 {
        self.api_version
    }

    pub fn enabled_extensions(&self) -> &InstanceExtensions {
        &self.instance_extensions
    }
}

impl_vk_handle!(Instance, VkInstance, instance);

// private
impl Instance {
    fn create_debug_report(&mut self) -> Result<()> {
        let debug_report_info = VkDebugReportCallbackCreateInfoEXT::new(
            VK_DEBUG_REPORT_WARNING_BIT_EXT
                | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT
                | VK_DEBUG_REPORT_ERROR_BIT_EXT,
            Instance::debug_report_callback,
        );

        let debug_report = self.create_debug_report_callbacks(&debug_report_info)?;

        self.debug_report = Some(debug_report);

        Ok(())
    }

    fn get_extension_properties(
        entry_functions: &EntryFunctions,
        layers: &[VkString],
    ) -> Result<Vec<VkString>> {
        let mut properties = HashSet::new();

        let default_properties = Self::enumerate_extension_properties(
            entry_functions.vkEnumerateInstanceExtensionProperties,
            None,
        )?;

        for property in default_properties {
            let prop_string = VkString::new(&property.extension_name()?);

            properties.insert(prop_string);
        }

        for layer in layers {
            let tmp_properties = Self::enumerate_extension_properties(
                entry_functions.vkEnumerateInstanceExtensionProperties,
                Some(layer),
            )?;

            for property in tmp_properties {
                let prop_string = VkString::new(&property.extension_name()?);

                properties.insert(prop_string);
            }
        }

        Ok(properties.iter().cloned().collect())
    }
}

// debug
impl Instance {
    extern "system" fn debug_report_callback(
        flags: VkDebugReportFlagsEXT,
        object_type: VkDebugReportObjectTypeEXT,
        _src_object: u64,
        _location: usize,
        _msg_code: i32,
        _layer_prefix: *const c_char,
        msg: *const c_char,
        _user_data: *mut c_void,
    ) -> VkBool32 {
        let mut output: String = String::new();

        output += match flags {
            VK_DEBUG_REPORT_INFORMATION_BIT_EXT => "INFO",
            VK_DEBUG_REPORT_WARNING_BIT_EXT => "WARNING",
            VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT => "PERFORMANCE",
            VK_DEBUG_REPORT_ERROR_BIT_EXT => "ERROR",
            VK_DEBUG_REPORT_DEBUG_BIT_EXT => "DEBUG",
        };

        output += ": OBJ( ";

        output += match object_type {
            VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT => "UNKNOWN",
            VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT => "INSTANCE",
            VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT => "PHYSICAL DEVICE",
            VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT => "DEVICE",
            VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT => "QUEUE",
            VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT => "SEMAPHORE",
            VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT => "COMMAND BUFFER",
            VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT => "FENCE",
            VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT => "DEVICE MEMORY",
            VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT => "BUFFER",
            VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT => "IMAGE",
            VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT => "EVENT",
            VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT => "QUERY POOL",
            VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT => "BUFFER VIEW",
            VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT => "IMAGE VIEW",
            VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT => "SHADER MODULE",
            VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT => "PIPELINE CACHE",
            VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT => "PIPELINE LAYOUT",
            VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT => "RENDER PASS",
            VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT => "PIPELINE",
            VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT => "DESCRIPTOR SET LAYOUT",
            VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT => "SAMPLER",
            VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT => "DESCRIPTOR POOL",
            VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT => "DESCRIPTOR SET",
            VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT => "FRAME BUFFER",
            VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT => "COMMAND POOL",
            VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT => "SURFACE KHR",
            VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT => "SWAPCHAIN KHR",
            VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT => "DEBUG REPORT",
            VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT => "DISPLAY KHR",
            VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT => "DISPLAY MODE KHR",
            VK_DEBUG_REPORT_OBJECT_TYPE_OBJECT_TABLE_NVX_EXT => "OBJECT TABLE NVX",
            VK_DEBUG_REPORT_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NVX_EXT => {
                "INDIRECT COMMANDS LAYOUT NVX"
            }
            VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT => "VALIDATION CACHE ",
            VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT => "SAMPLER YCBCR CONVERSION ",
            VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT => {
                "DESCRIPTOR UPDATE TEMPLATE "
            }
            VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR_EXT => {
                "ACCELERATION STRUCTURE NV"
            }
        };

        let s = match VkString::try_from(msg) {
            Ok(s) => s,
            Err(err) => {
                println!("{}", err);

                return VK_FALSE;
            }
        };

        output += " ):\n\t";
        output += s.as_str();

        println!("{}", output);

        VK_TRUE
    }
}

impl fmt::Debug for Instance {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Instance (VkInstance: {:#?})", self.instance)
    }
}

impl Drop for Instance {
    fn drop(&mut self) {
        if let Some(debug_report) = &self.debug_report {
            self.destroy_debug_report_callbacks(*debug_report);
        }

        self.destroy_instance();
    }
}

// private wrapper
impl Instance {
    #[inline]
    fn enumerate_layer_properties(
        enumerate_instance_layer_properties: PFN_vkEnumerateInstanceLayerProperties,
    ) -> Result<Vec<VkLayerProperties>> {
        let mut property_count: u32 = 0;

        // get the amount of properties
        let result = enumerate_instance_layer_properties(&mut property_count, ptr::null_mut());

        if result != VK_SUCCESS {
            return Err(anyhow::Error::new(result));
        }

        let mut properties = Vec::with_capacity(property_count as usize);
        unsafe { properties.set_len(property_count as usize) };

        // get the properties
        let result =
            enumerate_instance_layer_properties(&mut property_count, properties.as_mut_ptr());

        if result == VK_SUCCESS {
            Ok(properties)
        } else {
            Err(anyhow::Error::new(result))
        }
    }

    #[inline]
    fn enumerate_extension_properties(
        enumerate_instance_extension_properties: PFN_vkEnumerateInstanceExtensionProperties,
        layer_name: Option<&VkString>,
    ) -> Result<Vec<VkExtensionProperties>> {
        let mut count = 0;
        let name = match layer_name {
            Some(name) => name.as_ptr(),
            None => ptr::null(),
        };

        let mut result = enumerate_instance_extension_properties(name, &mut count, ptr::null_mut());

        if result != VK_SUCCESS {
            return Err(anyhow::Error::new(result));
        }

        let mut properties = Vec::with_capacity(count as usize);
        unsafe { properties.set_len(count as usize) };

        result = enumerate_instance_extension_properties(name, &mut count, properties.as_mut_ptr());

        if result == VK_SUCCESS {
            Ok(properties)
        } else {
            Err(anyhow::Error::new(result))
        }
    }

    #[inline]
    fn destroy_instance(&self) {
        unsafe {
            self.instance_functions
                .vkDestroyInstance(self.instance, ptr::null());
        }
    }
}

// public, wrapped vulkan calls
impl Instance {
    #[inline]
    pub fn create_debug_report_callbacks(
        &self,
        debug_report_callback_create_info: &VkDebugReportCallbackCreateInfoEXT,
    ) -> Result<VkDebugReportCallbackEXT> {
        unsafe {
            let mut debug_report_callback = MaybeUninit::uninit();

            let result = self
                .debug_report_callback_functions
                .vkCreateDebugReportCallbackEXT(
                    self.instance,
                    debug_report_callback_create_info,
                    ptr::null(),
                    debug_report_callback.as_mut_ptr(),
                );

            if result == VK_SUCCESS {
                Ok(debug_report_callback.assume_init())
            } else {
                Err(anyhow::Error::new(result))
            }
        }
    }

    #[inline]
    pub fn destroy_debug_report_callbacks(&self, debug_report_callback: VkDebugReportCallbackEXT) {
        unsafe {
            self.debug_report_callback_functions
                .vkDestroyDebugReportCallbackEXT(self.instance, debug_report_callback, ptr::null())
        }
    }

    #[inline]
    pub fn get_device_proc_addr(&self, device: VkDevice, name: VkString) -> PFN_vkVoidFunction {
        unsafe {
            self.instance_functions
                .vkGetDeviceProcAddr(device, name.as_ptr())
        }
    }

    #[inline]
    pub fn get_device_proc_addr_raw(&self, device: VkDevice, name: &CStr) -> PFN_vkVoidFunction {
        unsafe {
            self.instance_functions
                .vkGetDeviceProcAddr(device, name.as_ptr())
        }
    }

    #[inline]
    pub fn enumerate_physical_devices(&self) -> Result<Vec<VkPhysicalDevice>> {
        let mut count = 0;

        let result = unsafe {
            self.instance_functions.vkEnumeratePhysicalDevices(
                self.instance,
                &mut count,
                ptr::null_mut(),
            )
        };

        if result != VK_SUCCESS {
            return Err(anyhow::Error::new(result));
        }

        let mut physical_devices = Vec::with_capacity(count as usize);
        unsafe { physical_devices.set_len(count as usize) };

        let result = unsafe {
            self.instance_functions.vkEnumeratePhysicalDevices(
                self.instance,
                &mut count,
                physical_devices.as_mut_ptr(),
            )
        };

        if result == VK_SUCCESS {
            Ok(physical_devices)
        } else {
            Err(anyhow::Error::new(result))
        }
    }

    #[inline]
    pub fn physical_device_properties(
        &self,
        physical_device: VkPhysicalDevice,
    ) -> VkPhysicalDeviceProperties {
        unsafe {
            let mut physical_device_properties = MaybeUninit::uninit();

            self.instance_functions.vkGetPhysicalDeviceProperties(
                physical_device,
                physical_device_properties.as_mut_ptr(),
            );

            physical_device_properties.assume_init()
        }
    }

    #[inline]
    pub fn physical_device_features(
        &self,
        physical_device: VkPhysicalDevice,
    ) -> VkPhysicalDeviceFeatures {
        unsafe {
            let mut physical_device_features = MaybeUninit::uninit();

            self.instance_functions.vkGetPhysicalDeviceFeatures(
                physical_device,
                physical_device_features.as_mut_ptr(),
            );

            physical_device_features.assume_init()
        }
    }

    #[inline]
    pub fn physical_device_format_properties(
        &self,
        physical_device: VkPhysicalDevice,
        format: VkFormat,
    ) -> VkFormatProperties {
        unsafe {
            let mut physical_device_format_properties = MaybeUninit::uninit();

            self.instance_functions.vkGetPhysicalDeviceFormatProperties(
                physical_device,
                format,
                physical_device_format_properties.as_mut_ptr(),
            );

            physical_device_format_properties.assume_init()
        }
    }

    #[inline]
    pub fn physical_device_queue_family_properties(
        &self,
        physical_device: VkPhysicalDevice,
    ) -> Vec<VkQueueFamilyProperties> {
        let mut count = 0;

        unsafe {
            self.instance_functions
                .vkGetPhysicalDeviceQueueFamilyProperties(
                    physical_device,
                    &mut count,
                    ptr::null_mut(),
                );
        }

        let mut queue_family_properties = Vec::with_capacity(count as usize);
        unsafe { queue_family_properties.set_len(count as usize) };

        unsafe {
            self.instance_functions
                .vkGetPhysicalDeviceQueueFamilyProperties(
                    physical_device,
                    &mut count,
                    queue_family_properties.as_mut_ptr(),
                );
        }

        queue_family_properties
    }

    #[inline]
    pub fn physical_device_memory_properties(
        &self,
        physical_device: VkPhysicalDevice,
    ) -> VkPhysicalDeviceMemoryProperties {
        unsafe {
            let mut physical_device_memory_properties = MaybeUninit::uninit();

            self.instance_functions.vkGetPhysicalDeviceMemoryProperties(
                physical_device,
                physical_device_memory_properties.as_mut_ptr(),
            );

            physical_device_memory_properties.assume_init()
        }
    }

    #[inline]
    pub fn physical_device_sparse_image_format_properties<T>(
        &self,
        physical_device: VkPhysicalDevice,
        format: VkFormat,
        ty: VkImageType,
        samples: VkSampleCountFlags,
        usage: impl Into<VkImageUsageFlagBits>,
        tiling: VkImageTiling,
    ) -> Vec<VkSparseImageFormatProperties> {
        let mut count = 0;
        let usage = usage.into();

        unsafe {
            self.instance_functions
                .vkGetPhysicalDeviceSparseImageFormatProperties(
                    physical_device,
                    format,
                    ty,
                    samples,
                    usage,
                    tiling,
                    &mut count,
                    ptr::null_mut(),
                );
        }

        let mut sparse_image_formats = Vec::with_capacity(count as usize);
        unsafe { sparse_image_formats.set_len(count as usize) };

        unsafe {
            self.instance_functions
                .vkGetPhysicalDeviceSparseImageFormatProperties(
                    physical_device,
                    format,
                    ty,
                    samples,
                    usage,
                    tiling,
                    &mut count,
                    sparse_image_formats.as_mut_ptr(),
                );
        }

        sparse_image_formats
    }

    #[inline]
    pub fn physical_device_image_format_properties(
        &self,
        physical_device: VkPhysicalDevice,
        format: VkFormat,
        image_type: VkImageType,
        tiling: VkImageTiling,
        usage: impl Into<VkImageUsageFlagBits>,
        flags: impl Into<VkImageCreateFlagBits>,
    ) -> Result<VkImageFormatProperties> {
        unsafe {
            let mut image_format_properties = MaybeUninit::uninit();

            let result = self
                .instance_functions
                .vkGetPhysicalDeviceImageFormatProperties(
                    physical_device,
                    format,
                    image_type,
                    tiling,
                    usage.into(),
                    flags.into(),
                    image_format_properties.as_mut_ptr(),
                );

            if result == VK_SUCCESS {
                Ok(image_format_properties.assume_init())
            } else {
                Err(anyhow::Error::new(result))
            }
        }
    }

    #[inline]
    pub fn create_device<'a>(
        &self,
        physical_device: VkPhysicalDevice,
        device_create_info: &'a VkDeviceCreateInfo<'a>,
    ) -> Result<VkDevice> {
        unsafe {
            let mut device = MaybeUninit::uninit();

            let result = self.instance_functions.vkCreateDevice(
                physical_device,
                device_create_info,
                ptr::null(),
                device.as_mut_ptr(),
            );

            if result == VK_SUCCESS {
                Ok(device.assume_init())
            } else {
                Err(anyhow::Error::new(result))
            }
        }
    }

    #[inline]
    pub fn physical_device_surface_support(
        &self,
        physical_device: VkPhysicalDevice,
        queue_family_index: u32,
        surface: VkSurfaceKHR,
    ) -> Result<bool> {
        unsafe {
            let mut supported = MaybeUninit::uninit();

            let result = self
                .instance_wsi_functions
                .vkGetPhysicalDeviceSurfaceSupportKHR(
                    physical_device,
                    queue_family_index,
                    surface,
                    supported.as_mut_ptr(),
                );

            if result == VK_SUCCESS {
                Ok(supported.assume_init() == VK_TRUE)
            } else {
                Err(anyhow::Error::new(result))
            }
        }
    }

    #[inline]
    pub fn physical_device_surface_capabilities(
        &self,
        physical_device: VkPhysicalDevice,
        surface: VkSurfaceKHR,
    ) -> Result<VkSurfaceCapabilitiesKHR> {
        unsafe {
            let mut surface_capabilities = MaybeUninit::uninit();

            let result = self
                .instance_wsi_functions
                .vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
                    physical_device,
                    surface,
                    surface_capabilities.as_mut_ptr(),
                );

            if result == VK_SUCCESS {
                Ok(surface_capabilities.assume_init())
            } else {
                Err(anyhow::Error::new(result))
            }
        }
    }

    #[inline]
    pub fn physical_device_surface_formats(
        &self,
        physical_device: VkPhysicalDevice,
        surface: VkSurfaceKHR,
    ) -> Result<Vec<VkSurfaceFormatKHR>> {
        let mut count = 0;

        let result = unsafe {
            self.instance_wsi_functions
                .vkGetPhysicalDeviceSurfaceFormatsKHR(
                    physical_device,
                    surface,
                    &mut count,
                    ptr::null_mut(),
                )
        };

        if result != VK_SUCCESS {
            return Err(anyhow::Error::new(result));
        }

        let mut surface_formats = Vec::with_capacity(count as usize);
        unsafe { surface_formats.set_len(count as usize) };

        let result = unsafe {
            self.instance_wsi_functions
                .vkGetPhysicalDeviceSurfaceFormatsKHR(
                    physical_device,
                    surface,
                    &mut count,
                    surface_formats.as_mut_ptr(),
                )
        };

        if result == VK_SUCCESS {
            Ok(surface_formats)
        } else {
            Err(anyhow::Error::new(result))
        }
    }

    #[inline]
    pub fn physical_device_present_modes(
        &self,
        physical_device: VkPhysicalDevice,
        surface: VkSurfaceKHR,
    ) -> Result<Vec<VkPresentModeKHR>> {
        let mut count = 0;

        let result = unsafe {
            self.instance_wsi_functions
                .vkGetPhysicalDeviceSurfacePresentModesKHR(
                    physical_device,
                    surface,
                    &mut count,
                    ptr::null_mut(),
                )
        };

        if result != VK_SUCCESS {
            return Err(anyhow::Error::new(result));
        }

        let mut surface_present_modes = Vec::with_capacity(count as usize);
        unsafe { surface_present_modes.set_len(count as usize) };

        let result = unsafe {
            self.instance_wsi_functions
                .vkGetPhysicalDeviceSurfacePresentModesKHR(
                    physical_device,
                    surface,
                    &mut count,
                    surface_present_modes.as_mut_ptr(),
                )
        };

        if result == VK_SUCCESS {
            Ok(surface_present_modes)
        } else {
            Err(anyhow::Error::new(result))
        }
    }

    #[inline]
    pub fn enumerate_device_extensions(
        &self,
        physical_device: VkPhysicalDevice,
    ) -> Result<Vec<VkExtensionProperties>> {
        let mut count = 0;

        let result = unsafe {
            self.instance_functions
                .vkEnumerateDeviceExtensionProperties(
                    physical_device,
                    ptr::null(),
                    &mut count,
                    ptr::null_mut(),
                )
        };

        if result != VK_SUCCESS {
            return Err(anyhow::Error::new(result));
        }

        let mut extension_properties = Vec::with_capacity(count as usize);
        unsafe { extension_properties.set_len(count as usize) };

        let result = unsafe {
            self.instance_functions
                .vkEnumerateDeviceExtensionProperties(
                    physical_device,
                    ptr::null(),
                    &mut count,
                    extension_properties.as_mut_ptr(),
                )
        };

        if result == VK_SUCCESS {
            Ok(extension_properties)
        } else {
            Err(anyhow::Error::new(result))
        }
    }

    #[inline]
    pub fn physical_device_properties2(
        &self,
        physical_device: VkPhysicalDevice,
        device_properties: &mut VkPhysicalDeviceProperties2KHR,
    ) {
        unsafe {
            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceProperties2KHR(physical_device, device_properties);
        }
    }

    #[inline]
    pub fn physical_device_features2(
        &self,
        physical_device: VkPhysicalDevice,
        device_features: &mut VkPhysicalDeviceFeatures2KHR,
    ) {
        unsafe {
            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceFeatures2KHR(physical_device, device_features);
        }
    }

    #[inline]
    pub fn physical_device_format_properties2(
        &self,
        physical_device: VkPhysicalDevice,
    ) -> VkFormatProperties2KHR<'_> {
        unsafe {
            let mut handle = MaybeUninit::uninit();

            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceFormatProperties2KHR(physical_device, handle.as_mut_ptr());

            handle.assume_init()
        }
    }

    #[inline]
    pub fn physical_device_image_format_properties2(
        &self,
        physical_device: VkPhysicalDevice,
        image_format_info: &VkPhysicalDeviceImageFormatInfo2KHR,
    ) -> VkImageFormatProperties2KHR<'_> {
        unsafe {
            let mut handle = MaybeUninit::uninit();

            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceImageFormatProperties2KHR(
                    physical_device,
                    image_format_info,
                    handle.as_mut_ptr(),
                );

            handle.assume_init()
        }
    }

    #[inline]
    pub fn physical_device_queue_family_properties2(
        &self,
        physical_device: VkPhysicalDevice,
    ) -> Vec<VkQueueFamilyProperties2KHR> {
        let mut count = 0;

        unsafe {
            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceQueueFamilyProperties2KHR(
                    physical_device,
                    &mut count,
                    ptr::null_mut(),
                )
        };

        let mut family_queue_properties = Vec::with_capacity(count as usize);
        unsafe { family_queue_properties.set_len(count as usize) };

        unsafe {
            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceQueueFamilyProperties2KHR(
                    physical_device,
                    &mut count,
                    family_queue_properties.as_mut_ptr(),
                )
        };

        family_queue_properties
    }

    #[inline]
    pub fn physical_device_memory_properties2(
        &self,
        physical_device: VkPhysicalDevice,
    ) -> VkPhysicalDeviceMemoryProperties2KHR {
        unsafe {
            let mut handle = MaybeUninit::uninit();

            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceMemoryProperties2KHR(physical_device, handle.as_mut_ptr());

            handle.assume_init()
        }
    }

    #[inline]
    pub fn physical_device_memory_budget(
        &self,
        physical_device: VkPhysicalDevice,
    ) -> (VkPhysicalDeviceMemoryBudgetPropertiesEXT, u32) {
        unsafe {
            let mut properties = VkPhysicalDeviceMemoryProperties2KHR::default();
            let mut memory_budget = VkPhysicalDeviceMemoryBudgetPropertiesEXT::default();
            properties.chain(&mut memory_budget);

            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceMemoryProperties2KHR(physical_device, &mut properties);

            (memory_budget, properties.memoryProperties.memoryHeapCount)
        }
    }

    #[inline]
    pub fn physical_device_sparse_image_format_properties2(
        &self,
        physical_device: VkPhysicalDevice,
        format_info: &VkPhysicalDeviceSparseImageFormatInfo2KHR,
    ) -> Vec<VkSparseImageFormatProperties2KHR> {
        let mut count = 0;

        unsafe {
            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceSparseImageFormatProperties2KHR(
                    physical_device,
                    format_info,
                    &mut count,
                    ptr::null_mut(),
                )
        };

        let mut sparse_image_formats = Vec::with_capacity(count as usize);
        unsafe { sparse_image_formats.set_len(count as usize) };

        unsafe {
            self.physical_device_properties2_functions
                .vkGetPhysicalDeviceSparseImageFormatProperties2KHR(
                    physical_device,
                    format_info,
                    &mut count,
                    sparse_image_formats.as_mut_ptr(),
                )
        };

        sparse_image_formats
    }

    #[inline]
    pub fn destroy_surface(&self, surface: VkSurfaceKHR) {
        unsafe {
            self.instance_wsi_functions
                .vkDestroySurfaceKHR(self.instance, surface, ptr::null())
        };
    }
}