use crate::renderer::vulkan::{
    utils::layers::{use_layers, LayersSelector},
    VkPhysicalDevice,
};
use ash::khr::surface;
use ash::{vk, Entry, Instance};
use std::ffi::{c_char, CStr, CString};

pub struct VkInstance {
    pub(super) entry: Entry,
    pub(super) handle: Instance,
    pub(super) surface_loader: surface::Instance,
}

impl VkInstance {
    pub fn new(required_extensions: &Vec<*const c_char>) -> Self {
        let entry = Entry::linked();

        log::debug!("Initializing Vulkan instance");

        if log::log_enabled!(log::Level::Debug) {
            let layer_properties =
                unsafe { entry.enumerate_instance_layer_properties() }.unwrap_or_default();

            for layer_property in layer_properties {
                let layer_extensions = unsafe {
                    entry.enumerate_instance_extension_properties(
                        layer_property.layer_name_as_c_str().ok(),
                    )
                }
                    .unwrap_or_default();
                log::debug!("{layer_property:#?} {layer_extensions:#?}");
            }
        }

        {
            let required_extensions = required_extensions
                .iter()
                .map(|str| unsafe { CStr::from_ptr(*str) })
                .map(|cstr| cstr.to_string_lossy())
                .collect::<Vec<_>>();
            log::debug!(
                "Required instance extensions: {}",
                required_extensions.join(", ")
            );
        }

        // Layers
        #[allow(unused)]
        let mut layer_selector = LayersSelector::Nothing;
        #[cfg(debug_assertions)]
        {
            layer_selector = LayersSelector::SpecificLayers(vec![
                "VK_LAYER_KHRONOS_validation",
                "VK_LAYER_MANGOHUD_overlay_x86_64",
                "VK_LAYER_NV_optimus",
            ]);
        }
        let layers = use_layers(&entry, layer_selector);

        {
            let layers = layers
                .iter()
                .map(|layer| layer.to_string_lossy())
                .collect::<Vec<_>>();
            log::debug!("Selected debug layers : {}", layers.join(", "))
        }

        let layers_raw = layers.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();

        // App Info
        let app_name = CString::new("VulkanTriangle").unwrap();
        let appinfo = vk::ApplicationInfo::default()
            .application_name(app_name.as_c_str())
            .application_version(0)
            .engine_name(app_name.as_c_str())
            .engine_version(0)
            .api_version(vk::make_api_version(0, 1, 0, 0));

        let create_flags = if cfg!(any(target_os = "macos", target_os = "ios")) {
            vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
        } else {
            vk::InstanceCreateFlags::default()
        };

        // Instance Info
        let create_info = vk::InstanceCreateInfo::default()
            .application_info(&appinfo)
            .enabled_layer_names(&layers_raw)
            .enabled_extension_names(&required_extensions)
            .flags(create_flags);

        let instance: Instance = unsafe {
            entry
                .create_instance(&create_info, None)
                .expect("Instance creation error")
        };

        let surface_loader = surface::Instance::new(&entry, &instance);

        log::debug!("Vulkan instance created ({:?})", instance.handle());

        Self {
            entry,
            handle: instance,
            surface_loader,
        }
    }

    pub fn get_physical_devices(&self) -> Vec<VkPhysicalDevice> {
        let physical_devices = unsafe { self.handle.enumerate_physical_devices() };
        physical_devices
            .unwrap_or_default()
            .iter()
            .map(|physical_device| VkPhysicalDevice::new(&self.handle, *physical_device))
            .collect()
    }
}

impl Drop for VkInstance {
    fn drop(&mut self) {
        unsafe {
            self.handle.destroy_instance(None);
        }
        log::debug!("Vulkan instance destroyed ({:?})", self.handle.handle());
    }
}