#include "LunarWM_xr.h"

#include "common.h"
#include "vec.h"

#include <assert.h>
#include <stdlib.h>
#include <string.h>

GLuint LunarWM_xr_get_swapchain_image(
    LunarWM *wm, int swapchain_images_i, uint32_t index)
{
	return wm->xr.swapchain_images[swapchain_images_i].a_imgs[index].image;
}

bool LunarWM_xr_init(LunarWM *this)
{
	XrResult res = XR_SUCCESS;

	XrApplicationInfo app_info = {
		.applicationVersion = 1,
		.engineVersion = 1,
		.apiVersion = XR_CURRENT_API_VERSION,
	};
	strncpy((char *)app_info.applicationName, "LunarWM",
	    XR_MAX_APPLICATION_NAME_SIZE);
	strncpy(
	    (char *)app_info.engineName, "LunarWM Engine", XR_MAX_ENGINE_NAME_SIZE);

	char const *required_instance_extensions[] = {
		XR_EXT_DEBUG_UTILS_EXTENSION_NAME,
		XR_MNDX_EGL_ENABLE_EXTENSION_NAME,
		XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME,
	};
	char const *optional_instance_extensions[] = {
		XR_EXT_HAND_TRACKING_EXTENSION_NAME,
	};
	bool hand_tracking_ext_available = false;
	char const **v_active_instance_extensions = vector_create();

	uint32_t extension_properties_count = 0;
	XrExtensionProperties *extension_properties;
	if (xrEnumerateInstanceExtensionProperties(
	        nullptr, 0, &extension_properties_count, nullptr)
	    != XR_SUCCESS) {
		wlr_log(WLR_ERROR,
		    "Failed to enumerate OpenXR instance extension properties (count)");
		return false;
	}
	extension_properties
	    = malloc(sizeof(*extension_properties) * extension_properties_count);
	for (uint32_t i = 0; i < extension_properties_count; ++i) {
		extension_properties[i].type = XR_TYPE_EXTENSION_PROPERTIES;
		extension_properties[i].next = NULL;
	}

	if (xrEnumerateInstanceExtensionProperties(nullptr,
	        extension_properties_count, &extension_properties_count,
	        extension_properties)
	    != XR_SUCCESS) {
		wlr_log(WLR_ERROR,
		    "Failed to enumerate OpenXR instance extension properties");
		return false;
	}

	for (size_t i = 0; i < ARRAY_SZ(required_instance_extensions); i++) {
		char const *requested_instance_extension
		    = required_instance_extensions[i];
		bool found = false;
		for (int j = 0; j < extension_properties_count; j++) {
			if (strcmp(requested_instance_extension,
			        extension_properties[j].extensionName)
			    != 0) {
				continue;
			}

			vector_add(
			    &v_active_instance_extensions, requested_instance_extension);
			found = true;
			break;
		}

		if (!found) {
			wlr_log(WLR_ERROR, "Failed to find OpenXR instance extension: %s",
			    requested_instance_extension);
			return false;
		}
	}

	for (size_t i = 0; i < ARRAY_SZ(optional_instance_extensions); i++) {
		char const *requested_instance_extension
		    = optional_instance_extensions[i];
		bool found = false;
		for (int j = 0; j < extension_properties_count; j++) {
			if (strcmp(requested_instance_extension,
			        extension_properties[j].extensionName)
			    != 0) {
				continue;
			}

			vector_add(
			    &v_active_instance_extensions, requested_instance_extension);
			found = true;
			break;
		}

		if (!found) {
			wlr_log(WLR_INFO, "Optional OpenXR instance extension missing: %s",
			    requested_instance_extension);
		} else if (strcmp(requested_instance_extension,
		               XR_EXT_HAND_TRACKING_EXTENSION_NAME)
		    == 0) {
			hand_tracking_ext_available = true;
		}
	}

	{
		XrInstanceCreateInfo const ci = {
			.type = XR_TYPE_INSTANCE_CREATE_INFO,
			.next = nullptr,
			.createFlags = 0,
			.applicationInfo = app_info,
			.enabledApiLayerCount = 0,
			.enabledApiLayerNames = nullptr,
			.enabledExtensionCount
			= (uint32_t)vector_size(v_active_instance_extensions),
			.enabledExtensionNames = v_active_instance_extensions,
		};

		XrInstance instance = nullptr;
		if (xrCreateInstance(&ci, &instance) != XR_SUCCESS) {
			wlr_log(WLR_ERROR, "Failed to create OpenXR instance");
			return false;
		}
		this->xr.instance = instance;

		this->xr.CreateHandTrackerEXT = NULL;
		this->xr.DestroyHandTrackerEXT = NULL;
		this->xr.LocateHandJointsEXT = NULL;

		if (hand_tracking_ext_available) {
			res = xrGetInstanceProcAddr(this->xr.instance,
			    "xrCreateHandTrackerEXT",
			    (PFN_xrVoidFunction *)&this->xr.CreateHandTrackerEXT);
			if (res != XR_SUCCESS) {
				wlr_log(WLR_ERROR,
				    "Failed to get proc addr xrCreateHandTrackerEXT "
				    "(optional): %d",
				    res);
				hand_tracking_ext_available = false;
			}
		}
		if (hand_tracking_ext_available) {
			res = xrGetInstanceProcAddr(this->xr.instance,
			    "xrDestroyHandTrackerEXT",
			    (PFN_xrVoidFunction *)&this->xr.DestroyHandTrackerEXT);
			if (res != XR_SUCCESS) {
				wlr_log(WLR_ERROR,
				    "Failed to get proc addr xrDestroyHandTrackerEXT "
				    "(optional): %d",
				    res);
				hand_tracking_ext_available = false;
			}
		}
		if (hand_tracking_ext_available) {
			res = xrGetInstanceProcAddr(this->xr.instance,
			    "xrLocateHandJointsEXT",
			    (PFN_xrVoidFunction *)&this->xr.LocateHandJointsEXT);
			if (res != XR_SUCCESS) {
				wlr_log(WLR_ERROR,
				    "Failed to get proc addr xrLocateHandJointsEXT (optional): "
				    "%d",
				    res);
				hand_tracking_ext_available = false;
			}
		}
	}

	{
		XrSystemGetInfo gi = {
			.type = XR_TYPE_SYSTEM_GET_INFO,
			.next = nullptr,
		};

		XrFormFactor const factors[2] = {
			XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY,
			XR_FORM_FACTOR_HANDHELD_DISPLAY,
		};
		for (size_t i = 0; i < ARRAY_SZ(factors); i++) {
			auto factor = factors[i];

			gi.formFactor = factor;
			XrSystemId system_id = 0;
			if (xrGetSystem(this->xr.instance, &gi, &system_id) == XR_SUCCESS) {
				this->xr.system_id = system_id;
				break;
			}
		}

		if (!this->xr.system_id) {
			wlr_log(WLR_ERROR, "Failed to find valid form factor");
			return false;
		}
	}

	{
		this->xr.hand_tracking_enabled = hand_tracking_ext_available;
		XrSystemProperties system_props = {
			.type = XR_TYPE_SYSTEM_PROPERTIES,
			.next = this->xr.hand_tracking_enabled
			    ? &this->xr.hand_tracking_system_properties
			    : NULL,
		};
		res = xrGetSystemProperties(
		    this->xr.instance, this->xr.system_id, &system_props);
		if (res != XR_SUCCESS) {
			wlr_log(WLR_ERROR, "xrGetSystemProperties failed: %d", res);
			return false;
		}
		if (this->xr.hand_tracking_enabled
		    && !this->xr.hand_tracking_system_properties.supportsHandTracking) {
			wlr_log(WLR_INFO,
			    "Hand tracking extension present but system does not support "
			    "it");
			this->xr.hand_tracking_enabled = false;
		}
	}

	XrGraphicsRequirementsOpenGLESKHR reqs = {
		.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR,
		.next = nullptr,
	};
	PFN_xrGetOpenGLESGraphicsRequirementsKHR
	    xrGetOpenGLESGraphicsRequirementsKHR
	    = nullptr;
	xrGetInstanceProcAddr(this->xr.instance,
	    "xrGetOpenGLESGraphicsRequirementsKHR",
	    (PFN_xrVoidFunction *)&xrGetOpenGLESGraphicsRequirementsKHR);
	if (xrGetOpenGLESGraphicsRequirementsKHR(
	        this->xr.instance, this->xr.system_id, &reqs)
	    != XR_SUCCESS) {
		wlr_log(WLR_ERROR, "Failed to get GLES graphics requirements");
		return false;
	}
	wlr_log(WLR_INFO, "OpenGL ES range: %d.%d.%d - %d.%d.%d\n",
	    XR_VERSION_MAJOR(reqs.minApiVersionSupported),
	    XR_VERSION_MINOR(reqs.minApiVersionSupported),
	    XR_VERSION_PATCH(reqs.minApiVersionSupported),
	    XR_VERSION_MAJOR(reqs.maxApiVersionSupported),
	    XR_VERSION_MINOR(reqs.maxApiVersionSupported),
	    XR_VERSION_PATCH(reqs.maxApiVersionSupported));

	wlr_log(WLR_DEBUG, "Creating XR stuff..");

	glEnable(GL_DEBUG_OUTPUT_KHR);
	glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);

	{
		XrGraphicsBindingEGLMNDX gbind = {
			.type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX,
			.next = nullptr,
			.getProcAddress = eglGetProcAddress,
			.display = this->wayland.egl_display,
			.config = this->wayland.egl_config,
			.context = this->wayland.egl_context,
		};

		XrSessionCreateInfo const ci = {
			.type = XR_TYPE_SESSION_CREATE_INFO,
			.next = &gbind,
			.createFlags = 0,
			.systemId = this->xr.system_id,
		};

		if (xrCreateSession(this->xr.instance, &ci, &this->xr.session)
		    != XR_SUCCESS) {
			wlr_log(WLR_ERROR, "Failed to create OpenXR session");
			return false;
		}
	}

	// Swapchain time!
	wlr_log(WLR_DEBUG, "Creating XR swapchains...");
	XrViewConfigurationType *a_view_config_types;
	uint32_t view_config_types_count = 0;
	{
		if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id,
		        0, &view_config_types_count, nullptr)
		    != XR_SUCCESS) {
			wlr_log(WLR_ERROR,
			    "Failed to get amount of OpenXR view configurations");
			return false;
		}
		a_view_config_types
		    = malloc(sizeof(*a_view_config_types) * view_config_types_count);
		for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) {
			this->xr.a_view_configuration_views[i].type
			    = XR_TYPE_VIEW_CONFIGURATION_VIEW;
			this->xr.a_view_configuration_views[i].next = NULL;
		}
		if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id,
		        view_config_types_count, &view_config_types_count,
		        a_view_config_types)
		    != XR_SUCCESS) {
			wlr_log(
			    WLR_ERROR, "Failed to enumerate OpenXR view configurations");
			return false;
		}
	}

	{
		bool found = false;
		for (size_t i = 0; i < view_config_types_count; i++) {
			if (a_view_config_types[i]
			    == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) {
				found = true;
				break;
			}
		}
		if (!found) {
			wlr_log(WLR_ERROR,
			    "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO not present");
			return false;
		}
	}

	{
		if (xrEnumerateViewConfigurationViews(this->xr.instance,
		        this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
		        0, &this->xr.view_configuration_views_count, nullptr)
		    != XR_SUCCESS) {
			wlr_log(WLR_ERROR,
			    "Failed to get amount of OpenXR view configuration views");
			return false;
		}
		this->xr.a_view_configuration_views
		    = malloc(sizeof(*this->xr.a_view_configuration_views)
		        * this->xr.view_configuration_views_count);
		for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) {
			this->xr.a_view_configuration_views[i].type
			    = XR_TYPE_VIEW_CONFIGURATION_VIEW;
			this->xr.a_view_configuration_views[i].next = NULL;
		}
		if (xrEnumerateViewConfigurationViews(this->xr.instance,
		        this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
		        this->xr.view_configuration_views_count,
		        &this->xr.view_configuration_views_count,
		        this->xr.a_view_configuration_views)
		    != XR_SUCCESS) {
			wlr_log(WLR_ERROR,
			    "Failed to enumerate OpenXR view configuration views");
			return false;
		}
	}

	int64_t *a_swapchain_formats;
	uint32_t a_swapchain_formats_count = 0;
	{
		if (xrEnumerateSwapchainFormats(
		        this->xr.session, 0, &a_swapchain_formats_count, nullptr)
		    != XR_SUCCESS) {
			wlr_log(
			    WLR_ERROR, "Failed to get amount of OpenXR swapchain formats");
			return false;
		}
		a_swapchain_formats
		    = malloc(sizeof(*a_swapchain_formats) * a_swapchain_formats_count);
		if (xrEnumerateSwapchainFormats(this->xr.session,
		        a_swapchain_formats_count, &a_swapchain_formats_count,
		        a_swapchain_formats)
		    != XR_SUCCESS) {
			wlr_log(WLR_ERROR, "Failed to enumerate OpenXR swapchain formats");
			return false;
		}
	}

	{
		bool found = false;
		for (size_t i = 0; i < a_swapchain_formats_count; i++) {
			if (a_swapchain_formats[i] == GL_DEPTH_COMPONENT16) {
				found = true;
				break;
			}
		}
		if (!found) {
			wlr_log(
			    WLR_ERROR, "Failed to find satisfying depth swapchain format");
			return false;
		}
	}

	auto const swapchain_format_depth = GL_DEPTH_COMPONENT24;
	auto const swapchain_format_color = GL_RGBA8;

	{
		bool found = false;
		for (size_t i = 0; i < a_swapchain_formats_count; i++) {
			if (a_swapchain_formats[i] == swapchain_format_color) {
				found = true;
				break;
			}
		}
		if (!found) {
			wlr_log(
			    WLR_ERROR, "Failed to find satisfying color swapchain format");
			return false;
		}
	}

	uint32_t const view_count = this->xr.view_configuration_views_count;
	uint32_t const eye_w
	    = this->xr.a_view_configuration_views[0].recommendedImageRectWidth;
	uint32_t const eye_h
	    = this->xr.a_view_configuration_views[0].recommendedImageRectHeight;
	uint32_t const buf_w = eye_w * view_count;

	if (!this->xr.swapchains.v_color) {
		this->xr.swapchains.v_color = vector_create();
	}
	if (!this->xr.swapchains.v_depth) {
		this->xr.swapchains.v_depth = vector_create();
	}

	vector_add(&this->xr.swapchains.v_color, (LunarWM_SwapchainInfo) {});
	vector_add(&this->xr.swapchains.v_depth, (LunarWM_SwapchainInfo) {});

	{
		auto *color_sc = &this->xr.swapchains.v_color[0];
		auto *depth_sc = &this->xr.swapchains.v_depth[0];
		auto *vcv = &this->xr.a_view_configuration_views[0];

		{ // Create color swapchain
			XrSwapchainCreateInfo const ci = {
				.type = XR_TYPE_SWAPCHAIN_CREATE_INFO,
				.next = nullptr,
				.createFlags = 0,
				.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT,
				.format = swapchain_format_color,
				.sampleCount = vcv->recommendedSwapchainSampleCount,
				.width = buf_w,
				.height = eye_h,
				.faceCount = 1,
				.arraySize = 1,
				.mipCount = 1,
			};
			color_sc->swapchain_format = ci.format;

			if (xrCreateSwapchain(this->xr.session, &ci, &color_sc->swapchain)
			    != XR_SUCCESS) {
				wlr_log(WLR_ERROR, "Failed to create OpenXR color swapchain");
				return false;
			}
		}

		{ // Create depth swapchain
			XrSwapchainCreateInfo const ci = {
				.type = XR_TYPE_SWAPCHAIN_CREATE_INFO,
				.next = nullptr,
				.createFlags = 0,
				.usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
				.format = swapchain_format_depth,
				.sampleCount = vcv->recommendedSwapchainSampleCount,
				.width = buf_w,
				.height = eye_h,
				.faceCount = 1,
				.arraySize = 1,
				.mipCount = 1,
			};
			depth_sc->swapchain_format = ci.format;

			if (xrCreateSwapchain(this->xr.session, &ci, &depth_sc->swapchain)
			    != XR_SUCCESS) {
				wlr_log(WLR_ERROR, "Failed to create OpenXR depth swapchain");
				return false;
			}
		}

		// Enumerate swapchain images
		{ // Color
			this->xr.swapchain_images[0].handle = color_sc->swapchain;
			if (xrEnumerateSwapchainImages(color_sc->swapchain, 0,
			        &this->xr.swapchain_images[0].a_imgs_count, nullptr)
			    != XR_SUCCESS) {
				wlr_log(
				    WLR_ERROR, "Failed to get Color Swapchain Images count.");
				return false;
			}
			this->xr.swapchain_images[0].a_imgs
			    = malloc(sizeof(*this->xr.swapchain_images[0].a_imgs)
			        * this->xr.swapchain_images[0].a_imgs_count);
			for (uint32_t i = 0; i < this->xr.swapchain_images[0].a_imgs_count;
			    ++i) {
				this->xr.swapchain_images[0].a_imgs[i].type
				    = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
				this->xr.swapchain_images[0].a_imgs[i].next = NULL;
			}
			if (xrEnumerateSwapchainImages(color_sc->swapchain,
			        this->xr.swapchain_images[0].a_imgs_count,
			        &this->xr.swapchain_images[0].a_imgs_count,
			        (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[0]
			            .a_imgs)
			    != XR_SUCCESS) {
				wlr_log(
				    WLR_ERROR, "Failed to enumerate color swapchain images.");
				return false;
			}
		}
		{ // Depth
			this->xr.swapchain_images[1].handle = depth_sc->swapchain;
			if (xrEnumerateSwapchainImages(depth_sc->swapchain, 0,
			        &this->xr.swapchain_images[1].a_imgs_count, nullptr)
			    != XR_SUCCESS) {
				wlr_log(
				    WLR_ERROR, "Failed to get depth Swapchain Images count.");
				return false;
			}
			this->xr.swapchain_images[1].a_imgs
			    = malloc(sizeof(*this->xr.swapchain_images[1].a_imgs)
			        * this->xr.swapchain_images[1].a_imgs_count);
			for (uint32_t i = 0; i < this->xr.swapchain_images[1].a_imgs_count;
			    ++i) {
				this->xr.swapchain_images[1].a_imgs[i].type
				    = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
				this->xr.swapchain_images[1].a_imgs[i].next = NULL;
			}
			if (xrEnumerateSwapchainImages(depth_sc->swapchain,
			        this->xr.swapchain_images[1].a_imgs_count,
			        &this->xr.swapchain_images[1].a_imgs_count,
			        (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[1]
			            .a_imgs)
			    != XR_SUCCESS) {
				wlr_log(
				    WLR_ERROR, "Failed to enumerate depth swapchain images.");
				return false;
			}
		}

		// Get image views
		for (uint32_t j = 0; j < this->xr.swapchain_images[0].a_imgs_count;
		    j++) {
			GLuint framebuffer = 0;
			glGenFramebuffers(1, &framebuffer);

			GLenum const attachment = GL_COLOR_ATTACHMENT0;

			glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
			glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment,
			    GL_TEXTURE_2D, LunarWM_xr_get_swapchain_image(this, 0, j), 0);

			GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
			if (result != GL_FRAMEBUFFER_COMPLETE) {
				wlr_log(WLR_ERROR, "Failed to create color framebuffer");
				return false;
			}
			glBindFramebuffer(GL_FRAMEBUFFER, 0);

			if (!color_sc->v_image_views) {
				color_sc->v_image_views = vector_create();
			}
			vector_add(&color_sc->v_image_views, framebuffer);
		}

		for (uint32_t j = 0; j < this->xr.swapchain_images[1].a_imgs_count;
		    j++) {
			GLuint framebuffer = 0;
			glGenFramebuffers(1, &framebuffer);

			GLenum const attachment = GL_DEPTH_ATTACHMENT;

			glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
			glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment,
			    GL_TEXTURE_2D, LunarWM_xr_get_swapchain_image(this, 1, j), 0);

			GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
			if (result != GL_FRAMEBUFFER_COMPLETE) {
				wlr_log(WLR_ERROR, "Failed to create depth framebuffer");
				return false;
			}
			glBindFramebuffer(GL_FRAMEBUFFER, 0);

			if (!depth_sc->v_image_views) {
				depth_sc->v_image_views = vector_create();
			}
			vector_add(&depth_sc->v_image_views, framebuffer);
		}
	}

	assert(this->xr.instance);
	assert(this->xr.system_id);

	wlr_log(WLR_DEBUG, "Fetching blend modes...");
	XrEnvironmentBlendMode *a_environment_blend_modes;
	uint32_t a_environment_blend_modes_count = 0;
	{ // Get available blend modes
		if (xrEnumerateEnvironmentBlendModes(this->xr.instance,
		        this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
		        0, &a_environment_blend_modes_count, nullptr)
		    != XR_SUCCESS) {
			wlr_log(
			    WLR_ERROR, "Failed to get OpenXR environment blend mode count");
			return false;
		}
		a_environment_blend_modes = malloc(sizeof(*a_environment_blend_modes)
		    * a_environment_blend_modes_count);
		if (xrEnumerateEnvironmentBlendModes(this->xr.instance,
		        this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
		        a_environment_blend_modes_count,
		        &a_environment_blend_modes_count, a_environment_blend_modes)
		    != XR_SUCCESS) {
			wlr_log(WLR_ERROR, "Failed to get XR environment blend modes");
			return false;
		}
	}
	XrEnvironmentBlendMode const requested_environment_blend_modes[] = {
		XR_ENVIRONMENT_BLEND_MODE_OPAQUE,
		XR_ENVIRONMENT_BLEND_MODE_ADDITIVE,
		XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM,
	};

	for (size_t i = 0; i < ARRAY_SZ(requested_environment_blend_modes); i++) {
		this->xr.environment_blend_mode = requested_environment_blend_modes[i];
		bool found = false;
		for (size_t j = 0; j < a_environment_blend_modes_count; j++) {
			if (requested_environment_blend_modes[i]
			    == a_environment_blend_modes[j]) {
				found = true;
				break;
			}
		}
		if (found) {
			break;
		}
	}
	if (this->xr.environment_blend_mode == XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM) {
		wlr_log(WLR_INFO,
		    "Failed to find a compatible blend mode. Defaulting to "
		    "XR_ENVIRONMENT_BLEND_MODE_OPAQUE.");
		this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
	}

	wlr_log(WLR_DEBUG, "Getting reference space...");
	{ // Reference space
		XrReferenceSpaceCreateInfo const ci = {
			.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO,
			.next = nullptr,
			.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL,
			.poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f },
			    .position = { 0.0f, 0.0f, 0.0f } },
		};

		if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.local_space)
		    != XR_SUCCESS) {
			wlr_log(WLR_ERROR, "Failed to create OpenXR reference space");
			return false;
		}
	}

	{ // View reference space
		XrReferenceSpaceCreateInfo const ci = {
			.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO,
			.next = nullptr,
			.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW,
			.poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f },
			    .position = { 0.0f, 0.0f, 0.0f } },
		};

		if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.view_space)
		    != XR_SUCCESS) {
			wlr_log(WLR_ERROR, "Failed to create OpenXR reference space");
			return false;
		}
	}

	if (this->xr.hand_tracking_enabled && this->xr.CreateHandTrackerEXT) {
		bool hand_trackers_created = true;
		for (size_t i = 0; i < 2; i++) {
			auto *hand = &this->xr.hands[i];

			XrHandTrackerCreateInfoEXT const ci = {
				.type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT,
				.hand = i == 0 ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT,
				.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT,
			};

			res = this->xr.CreateHandTrackerEXT(
			    this->xr.session, &ci, &hand->hand_tracker);
			if (res != XR_SUCCESS) {
				wlr_log(WLR_ERROR, "Failed to create hand tracker: %d", res);
				hand_trackers_created = false;
				break;
			}
		}
		if (!hand_trackers_created) {
			if (this->xr.DestroyHandTrackerEXT) {
				for (size_t i = 0; i < 2; i++) {
					auto *hand = &this->xr.hands[i];
					if (hand->hand_tracker != XR_NULL_HANDLE) {
						this->xr.DestroyHandTrackerEXT(hand->hand_tracker);
						hand->hand_tracker = XR_NULL_HANDLE;
					}
				}
			}
			this->xr.hand_tracking_enabled = false;
		}
	}
	if (!this->xr.hand_tracking_enabled) {
		for (size_t i = 0; i < 2; i++) {
			this->xr.hands[i].hand_tracker = XR_NULL_HANDLE;
		}
		this->xr.hand_tracking_system_properties.supportsHandTracking
		    = XR_FALSE;
	}

	free(extension_properties);
	free(a_view_config_types);
	free(a_swapchain_formats);
	free(a_environment_blend_modes);
	vector_free(v_active_instance_extensions);

	wlr_log(WLR_INFO, "OpenXR initialized.");

	return true;
}

static void free_swapchain_info(LunarWM_SwapchainInfo *sc)
{
	if (!sc)
		return;
	if (sc->v_image_views) {
		for (size_t i = 0; i < vector_size(sc->v_image_views); ++i) {
			GLuint fb = sc->v_image_views[i];
			if (fb)
				glDeleteFramebuffers(1, &fb);
		}
		vector_free(sc->v_image_views);
		sc->v_image_views = NULL;
	}
	if (sc->swapchain) {
		xrDestroySwapchain(sc->swapchain);
		sc->swapchain = XR_NULL_HANDLE;
	}
}

void LunarWM_xr_cleanup(LunarWM *this)
{
	for (int i = 0; i < 2; ++i) {
		if (this->xr.swapchain_images[i].a_imgs) {
			free(this->xr.swapchain_images[i].a_imgs);
			this->xr.swapchain_images[i].a_imgs = NULL;
			this->xr.swapchain_images[i].a_imgs_count = 0;
		}
	}

	if (this->xr.swapchains.v_color) {
		for (size_t i = 0; i < vector_size(this->xr.swapchains.v_color); ++i)
			free_swapchain_info(&this->xr.swapchains.v_color[i]);
		vector_free(this->xr.swapchains.v_color);
		this->xr.swapchains.v_color = NULL;
	}
	if (this->xr.swapchains.v_depth) {
		for (size_t i = 0; i < vector_size(this->xr.swapchains.v_depth); ++i)
			free_swapchain_info(&this->xr.swapchains.v_depth[i]);
		vector_free(this->xr.swapchains.v_depth);
		this->xr.swapchains.v_depth = NULL;
	}

	if (this->renderer.fbo) {
		glDeleteFramebuffers(1, &this->renderer.fbo);
		this->renderer.fbo = 0;
	}
	this->renderer.tmp_rt = (RenderTexture2D) { 0 };

	if (this->xr.view_space)
		xrDestroySpace(this->xr.view_space),
		    this->xr.view_space = XR_NULL_HANDLE;
	if (this->xr.local_space)
		xrDestroySpace(this->xr.local_space),
		    this->xr.local_space = XR_NULL_HANDLE;

	for (size_t i = 0; i < 2; ++i) {
		if (this->xr.hands[i].hand_tracker && this->xr.DestroyHandTrackerEXT)
			this->xr.DestroyHandTrackerEXT(this->xr.hands[i].hand_tracker);
		this->xr.hands[i].hand_tracker = XR_NULL_HANDLE;
	}

	if (this->xr.a_view_configuration_views) {
		free(this->xr.a_view_configuration_views);
		this->xr.a_view_configuration_views = NULL;
		this->xr.view_configuration_views_count = 0;
	}

	if (this->xr.session != XR_NULL_HANDLE) {
		xrDestroySession(this->xr.session);
		this->xr.session = XR_NULL_HANDLE;
		this->xr.session_running = false;
	}

	if (this->xr.instance)
		xrDestroyInstance(this->xr.instance),
		    this->xr.instance = XR_NULL_HANDLE;
}
void LunarWM_xr_poll_events(LunarWM *this)
{
	XrEventDataBuffer event_data = {
		.type = XR_TYPE_EVENT_DATA_BUFFER,
	};

	while (xrPollEvent(this->xr.instance, &event_data) == XR_SUCCESS) {
		switch (event_data.type) {
		case XR_TYPE_EVENT_DATA_EVENTS_LOST: {
			auto *el = (XrEventDataEventsLost *)&event_data;
			wlr_log(WLR_INFO, "OPENXR: Events Lost: %d", el->lostEventCount);
			break;
		}
		case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: {
			auto *ilp = (XrEventDataInstanceLossPending *)&event_data;
			wlr_log(WLR_INFO, "OPENXR: Instance Loss Pending at: %ld",
			    ilp->lossTime);
			this->xr.session_running = false;
			this->running = false;
			break;
		}
		case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: {
			auto *ipc = (XrEventDataInteractionProfileChanged *)&event_data;
			wlr_log(WLR_INFO,
			    "OPENXR: Interaction Profile changed for Session: "
			    "%p",
			    ipc->session);
			if (ipc->session != this->xr.session) {
				wlr_log(WLR_ERROR,
				    "XrEventDataInteractionProfileChanged for "
				    "unknown Session");
				break;
			}
			break;
		}
		case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: {
			auto *scp = (XrEventDataReferenceSpaceChangePending *)&event_data;
			wlr_log(WLR_INFO,
			    "OPENXR: Reference Space Change pending for "
			    "Session: %p",
			    scp->session);
			if (scp->session != this->xr.session) {
				wlr_log(WLR_ERROR,
				    "XrEventDataReferenceSpaceChangePending for "
				    "unknown "
				    "Session");
				break;
			}
			break;
		}
		case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
			auto *sc = (XrEventDataSessionStateChanged *)&event_data;
			if (sc->session != this->xr.session) {
				wlr_log(WLR_ERROR,
				    "XrEventDataSessionStateChanged for unknown "
				    "Session");
				break;
			}

			if (sc->state == XR_SESSION_STATE_READY) {
				XrSessionBeginInfo bi = { .type = XR_TYPE_SESSION_BEGIN_INFO };
				bi.primaryViewConfigurationType
				    = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
				bi.primaryViewConfigurationType
				    = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
				if (xrBeginSession(this->xr.session, &bi) != XR_SUCCESS) {
					wlr_log(WLR_ERROR, "Failed to begin session");
				} else {
					this->xr.session_running = true;
				}
			}
			if (sc->state == XR_SESSION_STATE_STOPPING) {
				if (xrEndSession(this->xr.session) != XR_SUCCESS) {
					wlr_log(WLR_ERROR, "Failed to end session");
				}
				this->xr.session_running = false;
			}
			if (sc->state == XR_SESSION_STATE_EXITING) {
				this->xr.session_running = false;
				this->running = false;
			}
			if (sc->state == XR_SESSION_STATE_LOSS_PENDING) {
				this->xr.session_running = false;
				this->running = false;
			}
			this->xr.session_state = sc->state;
			break;
		}
		default: {
			break;
		}
		}
	}
}

bool LunarWM_xr_acquire_wait(XrSwapchain sc, uint32_t *idx)
{
	XrSwapchainImageAcquireInfo ai
	    = { .type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, .next = NULL };
	if (xrAcquireSwapchainImage(sc, &ai, idx) != XR_SUCCESS) {
		return false;
	}

	XrSwapchainImageWaitInfo wi = { .type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO,
		.next = NULL,
		.timeout = XR_INFINITE_DURATION };
	return xrWaitSwapchainImage(sc, &wi) == XR_SUCCESS;
}
