use std::{
    cmp::Ordering,
    ffi::{CStr, CString},
    ops::Deref,
    sync::Arc,
};

use ash::{Entry, ext, khr, vk};
use raw_window_handle::RawDisplayHandle;

use crate::{
    Error, PhysicalDeviceFeatures, PhysicalDeviceInfo, SurfaceCapabilities,
    device::{Extension, get_extensions, get_layers},
    get_physical_device_features, get_physical_device_properties, make_extension,
    swapchain::Surface,
};

pub struct DebugUtilsCreateInfo {
    pub severity: vk::DebugUtilsMessageSeverityFlagsEXT,
    pub message_type: vk::DebugUtilsMessageTypeFlagsEXT,
}

pub struct InstanceInner {
    pub raw: ash::Instance,
    pub entry: Entry,
    pub(crate) _debug_utils: DebugUtils,
}

#[derive(Clone)]
pub struct Instance {
    pub(crate) inner: Arc<InstanceInner>,
}

impl Deref for Instance {
    type Target = InstanceInner;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl core::fmt::Debug for Instance {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Instance")
            .field("raw", &self.inner.raw.handle())
            .finish_non_exhaustive()
    }
}

#[derive(Debug)]
pub struct InstanceDesc<'a> {
    pub app_name: Option<&'a str>,
    pub app_version: u32,
    pub instance_extensions: &'a [Extension<'a>],
    pub layer_settings: &'a [vk::LayerSettingEXT<'a>],
    pub layers: &'a [&'a CStr],
    pub display_handle: Option<RawDisplayHandle>,
}

pub const VALIDATION_LAYER: &CStr = c"VK_LAYER_KHRONOS_validation";

#[cfg(not(debug_assertions))]
const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[];
#[cfg(debug_assertions)]
const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[
    vk::LayerSettingEXT {
        p_layer_name: VALIDATION_LAYER.as_ptr(),
        p_setting_name: c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES".as_ptr(),
        value_count: 1,
        p_values: &[1u8; 1] as *const u8 as _,
        ty: vk::LayerSettingTypeEXT::BOOL32,
        _marker: core::marker::PhantomData,
    },
    vk::LayerSettingEXT {
        p_layer_name: VALIDATION_LAYER.as_ptr(),
        p_setting_name: c"VK_KHRONOS_VALIDATION_VALIDATE_BEST_PRACTICES_AMD".as_ptr(),
        value_count: 1,
        p_values: &[1u8; 1] as *const u8 as _,
        ty: vk::LayerSettingTypeEXT::BOOL32,
        _marker: core::marker::PhantomData,
    },
    vk::LayerSettingEXT {
        p_layer_name: VALIDATION_LAYER.as_ptr(),
        p_setting_name: c"VK_KHRONOS_VALIDATION_VALIDATE_SYNC".as_ptr(),
        value_count: 1,
        p_values: &[1u8; 1] as *const u8 as _,
        ty: vk::LayerSettingTypeEXT::BOOL32,
        _marker: core::marker::PhantomData,
    },
];

impl Default for InstanceDesc<'_> {
    fn default() -> Self {
        Self {
            app_name: None,
            app_version: vk::make_api_version(0, 1, 0, 0),
            instance_extensions: &[make_extension!(khr::surface)],
            layer_settings: DEBUG_LAYER_SETTINGS,
            layers: &[VALIDATION_LAYER],
            display_handle: None,
        }
    }
}

impl Instance {
    pub fn new<'a>(desc: &InstanceDesc<'a>) -> crate::Result<Self> {
        let entry = unsafe { ash::Entry::load()? };

        let app_name = desc
            .app_name
            .and_then(|name| CString::new(name).ok())
            .unwrap_or(c"ShooterGame".to_owned());

        let version =
            unsafe { entry.try_enumerate_instance_version()? }.unwrap_or(vk::API_VERSION_1_0);

        let app_info = vk::ApplicationInfo::default()
            .api_version(if version < vk::API_VERSION_1_1 {
                vk::API_VERSION_1_0
            } else {
                // ash doesn't support 1.4 yet
                vk::API_VERSION_1_3
            })
            .application_name(&app_name)
            .application_version(desc.app_version)
            .engine_name(c"Bevy Engine")
            .engine_version(vk::make_api_version(0, 0, 1, 0));

        let mut validation_info =
            vk::LayerSettingsCreateInfoEXT::default().settings(desc.layer_settings);

        let layers = match get_layers(&entry, desc.layers.to_vec()) {
            Ok(layers) => layers,
            Err((supported, unsupported)) => {
                tracing::error!(
                    "Some requested layers were not supported by instance:\nSupported: {:?}\nUnsupported: {:?}",
                    supported,
                    unsupported
                );
                supported
            }
        };

        let mut requested_extensions = desc.instance_extensions.to_vec();

        requested_extensions.push(make_extension!(ext::debug_utils as 1));
        #[cfg(debug_assertions)]
        requested_extensions.push(make_extension!(ext::layer_settings));

        let (extensions, unsupported_extensions) =
            get_extensions(&entry, &layers, requested_extensions, desc.display_handle)?;

        if !unsupported_extensions.is_empty() {
            tracing::error!(
                "extensions were requested but not supported by instance: {:?}",
                unsupported_extensions
            );
        }

        let layers = layers
            .iter()
            .map(|layer| layer.as_ptr())
            .collect::<Vec<_>>();
        let extensions = extensions
            .iter()
            .map(|ext| ext.name.as_ptr())
            .collect::<Vec<_>>();

        let create_info = vk::InstanceCreateInfo::default()
            .application_info(&app_info)
            .enabled_extension_names(&extensions)
            .enabled_layer_names(&layers)
            .push_next(&mut validation_info);

        tracing::debug!(
            "Creating instance:\napp_info: {app_info:#?}\ncreate_info: {create_info:#?}"
        );

        let instance = unsafe { entry.create_instance(&create_info, None)? };

        let debug_utils = {
            let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
                .message_severity(
                    vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
                        | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
                        | vk::DebugUtilsMessageSeverityFlagsEXT::INFO,
                )
                .message_type(
                    vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
                        | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
                        | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
                )
                .pfn_user_callback(Some(crate::debug::debug_callback));

            let instance = ext::debug_utils::Instance::new(&entry, &instance);
            let messenger = unsafe { instance.create_debug_utils_messenger(&debug_info, None)? };

            crate::DebugUtils {
                instance,
                messenger,
            }
        };

        let instance = Arc::new(InstanceInner {
            raw: instance,
            _debug_utils: debug_utils,
            entry,
        });

        Ok(Self { inner: instance })
    }

    fn choose_adapter(
        &self,
        mut discriminator: impl FnMut(&PhysicalDeviceInfo, &PhysicalDeviceInfo) -> Ordering,
    ) -> crate::Result<PhysicalDeviceInfo> {
        let pdevs = unsafe { self.inner.raw.enumerate_physical_devices()? };
        let mut pdevs = pdevs
            .into_iter()
            .map(|pdev| -> crate::Result<PhysicalDeviceInfo> { self.expose_adapter(pdev) })
            .filter_map(Result::ok)
            .collect::<Vec<_>>();

        pdevs.sort_unstable_by(&mut discriminator);
        pdevs.pop().ok_or(Error::NoAdapter)
    }

    pub(crate) fn get_adapter_surface_capabilities(
        &self,
        pdev: vk::PhysicalDevice,
        surface: &Surface,
    ) -> crate::Result<SurfaceCapabilities> {
        let capabilities = unsafe {
            surface
                .functor
                .get_physical_device_surface_capabilities(pdev, surface.raw)?
        };

        let formats = unsafe {
            surface
                .functor
                .get_physical_device_surface_formats(pdev, surface.raw)?
        };

        let present_modes = unsafe {
            surface
                .functor
                .get_physical_device_surface_present_modes(pdev, surface.raw)?
        };

        Ok(SurfaceCapabilities {
            capabilities,
            formats,
            present_modes,
        })
    }

    pub(crate) fn expose_adapter(
        &self,
        pdev: vk::PhysicalDevice,
    ) -> crate::Result<PhysicalDeviceInfo> {
        let properties = get_physical_device_properties(&self.inner, pdev)?;
        let features = get_physical_device_features(&self.inner, pdev, &properties)?;

        Ok(PhysicalDeviceInfo {
            pdev,
            properties,
            features,
        })
    }

    pub(crate) fn choose_adapter_default(
        &self,
        surface: Option<&Surface>,
        required_extensions: &[Extension],
        required_features: Option<&PhysicalDeviceFeatures>,
    ) -> crate::Result<PhysicalDeviceInfo> {
        self.choose_adapter(|a, b| {
            // Extensions: we definitely need swapchain.
            match a
                .properties
                .supports_extension(make_extension!(khr::swapchain))
                .cmp(
                    &b.properties
                        .supports_extension(make_extension!(khr::swapchain)),
                ) {
                Ordering::Equal => {}
                other => return other,
            }

            for ext in required_extensions {
                match a.properties.supports_extension(*ext) {
                    true => {}
                    false => return Ordering::Less,
                }
            }

            // Check surface compatibility
            // TODO
            _ = surface;

            if let Some(ref required_features) = required_features {
                if b.features.superset_of(&required_features)
                    && !a.features.superset_of(&required_features)
                {
                    return Ordering::Less;
                }
            }

            // check specific features that we need
            match a
                .features
                .core13
                .synchronization2
                .cmp(&b.features.core13.synchronization2)
            {
                Ordering::Equal => {}
                other => return other,
            }
            match a
                .features
                .core13
                .dynamic_rendering
                .cmp(&b.features.core13.dynamic_rendering)
            {
                Ordering::Equal => {}
                other => return other,
            }
            match a
                .features
                .core13
                .maintenance4
                .cmp(&b.features.core13.maintenance4)
            {
                Ordering::Equal => {}
                other => return other,
            }

            // Prefer discrete GPUs
            let a_discrete = a.properties.core.device_type == vk::PhysicalDeviceType::DISCRETE_GPU;
            let b_discrete = b.properties.core.device_type == vk::PhysicalDeviceType::DISCRETE_GPU;

            match (a_discrete, b_discrete) {
                (true, false) => return Ordering::Greater,
                (false, true) => return Ordering::Less,
                _ => {}
            }

            // Then prefer newer APIs
            match a
                .properties
                .core
                .api_version
                .cmp(&b.properties.core.api_version)
            {
                Ordering::Equal => {}
                other => return other,
            }

            // Prefer larger memory
            match a
                .properties
                .get_device_local_memory_count()
                .cmp(&b.properties.get_device_local_memory_count())
            {
                Ordering::Equal => {}
                other => return other,
            }

            // Prefer larger texture size
            match a
                .properties
                .core
                .limits
                .max_image_dimension2_d
                .cmp(&b.properties.core.limits.max_image_dimension2_d)
            {
                Ordering::Equal => {}
                other => return other,
            }

            Ordering::Equal
        })
    }

    /// So... basically this probably doesn't matter because the graphics queue will always be a present queue as well...
    pub(crate) fn query_presentation_support(
        &self,
        pdev: vk::PhysicalDevice,
        queue_family: u32,
        display_handle: RawDisplayHandle,
    ) -> bool {
        unsafe {
            match display_handle {
                RawDisplayHandle::Xlib(display) => {
                    let surface =
                        ash::khr::xlib_surface::Instance::new(&self.inner.entry, &self.inner.raw);
                    surface.get_physical_device_xlib_presentation_support(
                        pdev,
                        queue_family,
                        display.display.unwrap().as_ptr() as _,
                        display.screen as _,
                    )
                }
                RawDisplayHandle::Xcb(_xcb_display_handle) => todo!("xcb"),
                RawDisplayHandle::Wayland(wayland_display_handle) => {
                    let surface = ash::khr::wayland_surface::Instance::new(
                        &self.inner.entry,
                        &self.inner.raw,
                    );
                    surface.get_physical_device_wayland_presentation_support(
                        pdev,
                        queue_family,
                        wayland_display_handle.display.cast().as_mut(),
                    )
                }
                RawDisplayHandle::Drm(_) => {
                    // idk ?
                    true
                }
                RawDisplayHandle::Windows(_) => {
                    ash::khr::win32_surface::Instance::new(&self.inner.entry, &self.inner.raw)
                        .get_physical_device_win32_presentation_support(pdev, queue_family)
                }
                _ => panic!("unsupported platform"),
            }
        }
    }
}

pub(crate) struct DebugUtils {
    pub instance: ext::debug_utils::Instance,
    pub messenger: vk::DebugUtilsMessengerEXT,
}

impl Drop for DebugUtils {
    fn drop(&mut self) {
        unsafe {
            self.instance
                .destroy_debug_utils_messenger(self.messenger, None);
        }
    }
}
