use std::sync::{Arc, OnceLock};
use vulkano::{
	device::{
		physical::{PhysicalDevice, PhysicalDeviceType},
		Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
	},
	instance::{Instance, InstanceCreateInfo},
};
use wgui::gfx::WGfx;

static VULKAN_LIBRARY: OnceLock<Arc<vulkano::VulkanLibrary>> = OnceLock::new();
fn get_vulkan_library() -> &'static Arc<vulkano::VulkanLibrary> {
	VULKAN_LIBRARY.get_or_init(|| vulkano::VulkanLibrary::new().unwrap()) // want panic
}

#[allow(clippy::type_complexity)]
pub fn init_window(
	title: &str,
) -> anyhow::Result<(
	Arc<WGfx>,
	winit::event_loop::EventLoop<()>,
	Arc<winit::window::Window>,
	Arc<vulkano::swapchain::Surface>,
)> {
	use vulkano::{instance::InstanceCreateFlags, swapchain::Surface};
	use winit::{event_loop::EventLoop, window::Window};

	let event_loop = EventLoop::new().unwrap(); // want panic
	let mut vk_instance_extensions = Surface::required_extensions(&event_loop).unwrap();
	vk_instance_extensions.khr_get_physical_device_properties2 = true;
	log::debug!("Instance exts for runtime: {:?}", &vk_instance_extensions);

	let instance = Instance::new(
		get_vulkan_library().clone(),
		InstanceCreateInfo {
			flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
			enabled_extensions: vk_instance_extensions,
			..Default::default()
		},
	)?;

	#[allow(deprecated)]
	let window = Arc::new(
		event_loop
			.create_window(
				Window::default_attributes()
					.with_transparent(true)
					.with_title(title),
			)
			.unwrap(), // want panic
	);
	let surface = Surface::from_window(instance.clone(), window.clone())?;

	let mut device_extensions = DeviceExtensions::empty();
	device_extensions.khr_swapchain = true;

	log::debug!("Device exts for app: {:?}", &device_extensions);

	let (physical_device, mut my_extensions, queue_families) = instance
		.enumerate_physical_devices()?
		.filter_map(|p| {
			if p.supported_extensions().contains(&device_extensions) {
				Some((p, device_extensions))
			} else {
				log::debug!(
					"Not using {} because it does not implement the following device extensions:",
					p.properties().device_name,
				);
				for (ext, missing) in p.supported_extensions().difference(&device_extensions) {
					if missing {
						log::debug!("  {ext}");
					}
				}
				None
			}
		})
		.filter_map(|(p, my_extensions)| {
			try_all_queue_families(p.as_ref()).map(|families| (p, my_extensions, families))
		})
		.min_by_key(|(p, _, _)| prio_from_device_type(p))
		.expect("no suitable physical device found");

	log::info!(
		"Using vkPhysicalDevice: {}",
		physical_device.properties().device_name,
	);

	if physical_device.supported_extensions().img_filter_cubic {
		my_extensions.img_filter_cubic = true;
		log::info!("img_filter_cubic!");
	}

	let (device, queues) = Device::new(
		physical_device,
		DeviceCreateInfo {
			enabled_extensions: my_extensions,
			enabled_features: DeviceFeatures {
				dynamic_rendering: true,
				..DeviceFeatures::empty()
			},
			queue_create_infos: queue_families
				.iter()
				.map(|fam| QueueCreateInfo {
					queue_family_index: fam.queue_family_index,
					queues: fam.priorities.clone(),
					..Default::default()
				})
				.collect::<Vec<_>>(),
			..Default::default()
		},
	)?;

	let (queue_gfx, queue_xfer, _) = unwrap_queues(queues.collect());

	let me = WGfx::new_from_raw(
		instance,
		device,
		queue_gfx,
		queue_xfer,
		vulkano::format::Format::B8G8R8A8_UNORM,
	);
	Ok((me, event_loop, window, surface))
}

#[derive(Debug)]
struct QueueFamilyLayout {
	queue_family_index: u32,
	priorities: Vec<f32>,
}

fn prio_from_device_type(physical_device: &PhysicalDevice) -> u32 {
	match physical_device.properties().device_type {
		PhysicalDeviceType::DiscreteGpu => 0,
		PhysicalDeviceType::IntegratedGpu => 1,
		PhysicalDeviceType::VirtualGpu => 2,
		PhysicalDeviceType::Cpu => 3,
		_ => 4,
	}
}

fn unwrap_queues(queues: Vec<Arc<Queue>>) -> (Arc<Queue>, Arc<Queue>, Option<Arc<Queue>>) {
	match queues[..] {
		[ref g, ref t, ref c] => (g.clone(), t.clone(), Some(c.clone())),
		[ref gt, ref c] => (gt.clone(), gt.clone(), Some(c.clone())),
		[ref gt] => (gt.clone(), gt.clone(), None),
		_ => unreachable!(),
	}
}

fn try_all_queue_families(physical_device: &PhysicalDevice) -> Option<Vec<QueueFamilyLayout>> {
	queue_families_priorities(
		physical_device,
		vec![
			// main-thread graphics + uploads
			QueueFlags::GRAPHICS | QueueFlags::TRANSFER,
			// capture-thread uploads
			QueueFlags::TRANSFER,
		],
	)
	.or_else(|| {
		queue_families_priorities(
			physical_device,
			vec![
				// main thread graphics
				QueueFlags::GRAPHICS,
				// main thread uploads
				QueueFlags::TRANSFER,
				// capture thread uploads
				QueueFlags::TRANSFER,
			],
		)
	})
	.or_else(|| {
		queue_families_priorities(
			physical_device,
			// main thread-only. software capture not supported.
			vec![QueueFlags::GRAPHICS | QueueFlags::TRANSFER],
		)
	})
}

fn queue_families_priorities(
	physical_device: &PhysicalDevice,
	mut requested_queues: Vec<QueueFlags>,
) -> Option<Vec<QueueFamilyLayout>> {
	let mut result = Vec::with_capacity(3);

	for (idx, props) in physical_device.queue_family_properties().iter().enumerate() {
		let mut remaining = props.queue_count;
		let mut want = 0usize;

		requested_queues.retain(|requested| {
			if props.queue_flags.intersects(*requested) && remaining > 0 {
				remaining -= 1;
				want += 1;
				false
			} else {
				true
			}
		});

		if want > 0 {
			result.push(QueueFamilyLayout {
				queue_family_index: idx as u32,
				priorities: std::iter::repeat_n(1.0, want).collect(),
			});
		}
	}

	if requested_queues.is_empty() {
		log::debug!("Selected GPU queue families: {result:?}");
		Some(result)
	} else {
		None
	}
}
