#ifndef PROTOTYPE1_SRC_L2_MARGARET_MARGARET_H
#define PROTOTYPE1_SRC_L2_MARGARET_MARGARET_H

#include "../../../gen/l1/OptionU32.h"
#include "../../../gen/l1/VecAndSpan_VecU8.h"
#include "../../l1_5/core/stringop.h"
#include "../../l1/system/fileio.h"
#include "time_utils.h"
#include <wayland-client.h>

#include "../../../gen/l1/vulkan/VecVkFramebuffer.h"
#include "../../../gen/l1/vulkan/VecVkImage.h"
#include "../../../gen/l1/vulkan/VecVkSemaphore.h"
#include "../../../gen/l1/vulkan/OptionVkExtent2D.h"
#include "../../../gen/l1/vulkan/VecVkBufferCopy.h"
#include "../../../gen/l1/vulkan/VecVkExtensionProperties.h"
#include "../../../gen/l1/vulkan/VecVkImageView.h"
#include "../../../gen/l1/vulkan/VecVkPhysicalDevice.h"
#include "../../../gen/l1/vulkan/SpanVkFormat.h"
#include "../../../gen/l1/vulkan/OptionVkFormat.h"
#include "../../../gen/l1/vulkan/VecVkQueueFamilyProperties.h"
#include "../../../gen/l1/vulkan/OptionVkCompositeAlphaFlagBitsKHR.h"
#include "../../../gen/l1/vulkan/VecVkPresentModeKHR.h"
#include "../../../gen/l1/vulkan/OptionVkPresentModeKHR.h"
#include "../../../gen/l1/vulkan/OptionVkPresentModeKHR.h"
#include "../../../gen/l1/vulkan/VecVkPresentModeKHR.h"
#include "../../../gen/l1/vulkan/VecVkSurfaceFormatKHR.h"
#include "../../../gen/l1/vulkan/OptionVkSurfaceFormatKHR.h"
#include <vulkan/vulkan_wayland.h>
#include "../../../gen/l1/vulkan/VecVkImageMemoryBarrier.h"

void margaret_create_debug_utils_messenger_EXT(
    VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
    const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger
    ) {
    PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
    if (func != NULL) {
        if (func(instance, pCreateInfo, pAllocator, pDebugMessenger) != VK_SUCCESS) {
            abortf("vkCreateDebugUtilsMessengerEXT");
        }
    } else {
        abortf("VK_ERROR_EXTENSION_NOT_PRESENT");
    }
}

void margaret_destroy_debug_utils_messenger_EXT(
    VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator
    ) {
    PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != NULL)
        func(instance, debugMessenger, pAllocator);
    else
        abortf("VK_ERROR_EXTENSION_NOT_PRESENT");
}

// This is the function margaret will provide to vulkan for debugging
static VKAPI_ATTR VkBool32 VKAPI_CALL margaret_static_debug_callback(
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData
    ) {
    fprintf(stderr, "Vk Validation layer: %s\n", pCallbackData->pMessage);
    return VK_FALSE;
}

#include "../../../gen/l1/VecCSTR.h"

typedef struct {
    VkInstance instance;
    VkDebugUtilsMessengerEXT debug_messenger;
} MargaretInstanceAndItsDebug;

NODISCARD MargaretInstanceAndItsDebug MargaretInstanceAndItsDebug_new(bool enable_validation_layers) {
    VkApplicationInfo app_info = {
        .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
        .pApplicationName = "Kto prochital tot zdohnet",
        .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
        .pEngineName = "Margaret",
        .engineVersion = VK_MAKE_VERSION(1, 0, 0),
        .apiVersion = VK_API_VERSION_1_2,
    };

    VecCSTR needed_extensions = VecCSTR_new();
    VecCSTR_append(&needed_extensions, "VK_KHR_wayland_surface");
    VecCSTR_append(&needed_extensions, "VK_KHR_surface");
    VecCSTR needed_layers = VecCSTR_new();
    if (enable_validation_layers) {
        VecCSTR_append(&needed_extensions, "VK_EXT_debug_utils");
        VecCSTR_append(&needed_layers, "VK_LAYER_KHRONOS_validation");
    }

    VkInstanceCreateInfo instance_crinfo = {
        .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        // .pNext may be set to `for-instance-creation-only` Debug Messanger crinfo later
        .pApplicationInfo = &app_info,
        .enabledLayerCount = needed_layers.len,
        .ppEnabledLayerNames = needed_layers.buf,
        .enabledExtensionCount = needed_extensions.len,
        .ppEnabledExtensionNames = needed_extensions.buf,
    };

    if (enable_validation_layers) {
        VkDebugUtilsMessengerCreateInfoEXT debug_messenger_2_crinfo = {
            .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
            .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
                VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
                    VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
            .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                    VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
            .pfnUserCallback = margaret_static_debug_callback,
            .pUserData = NULL,
        };
        instance_crinfo.pNext = &debug_messenger_2_crinfo;
    }
    VkInstance instance;
    if (vkCreateInstance(&instance_crinfo, NULL, &instance) != VK_SUCCESS)
        abortf("Failed to create Vulkan instance");
    VkDebugUtilsMessengerEXT debug_messenger = NULL;
    if (enable_validation_layers) {
        VkDebugUtilsMessengerCreateInfoEXT debug_messenger_crinfo = {
            .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
            .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
                VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
                    VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
            .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                    VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
            .pfnUserCallback = margaret_static_debug_callback,
            .pUserData = NULL,
        };
        margaret_create_debug_utils_messenger_EXT(instance, &debug_messenger_crinfo, NULL, &debug_messenger);
    }
    VecCSTR_drop(needed_layers); /* needed_layers invalidated */
    VecCSTR_drop(needed_extensions); /* needed_extensions invalidated */
    return (MargaretInstanceAndItsDebug){.instance = instance, .debug_messenger = debug_messenger};
}


void MargaretInstanceAndItsDebug_drop(MargaretInstanceAndItsDebug instance) {
    if (instance.debug_messenger) {
        margaret_destroy_debug_utils_messenger_EXT(instance.instance, instance.debug_messenger, NULL);
    }
    vkDestroyInstance(instance.instance, NULL);
}

typedef struct {
    U32 for_graphics;
    U32 for_presentation;
} MargaretChosenQueueFamilies;

/* MargaretChosenQueueFamilies or a static string, describing which part could not be found
 * (may replace with VecU8 later) */
typedef struct {
    Result_variant variant;
    union {
        MargaretChosenQueueFamilies ok;
        SpanU8 err;
    };
} ResultMargaretChosenQueueFamiliesOrSpanU8;

ResultMargaretChosenQueueFamiliesOrSpanU8 margaret_choose_good_queue_families(VkPhysicalDevice dev, VkSurfaceKHR surface) {
    uint32_t queue_family_count = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(dev, &queue_family_count, NULL);
    VecVkQueueFamilyProperties queue_families = VecVkQueueFamilyProperties_new_zeroinit(queue_family_count);
    vkGetPhysicalDeviceQueueFamilyProperties(dev, &queue_family_count, queue_families.buf);

    OptionU32 index_for_graphics = None_U32();
    OptionU32 index_for_presentation = None_U32();

    for (uint32_t i = 0; i < queue_family_count; i++) {
        const VkQueueFamilyProperties* props = VecVkQueueFamilyProperties_at(&queue_families, i);
        if (props->queueFlags & VK_QUEUE_GRAPHICS_BIT && props->queueCount >= 1) {
            index_for_graphics = Some_U32(i);
        }
        VkBool32 isPres = false;
        if (vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surface, &isPres) != VK_SUCCESS)
            abortf("vkGetPhysicalDeviceSurfaceSupportKHR");
        if (isPres)
            index_for_presentation = Some_U32(i);
    }
    VecVkQueueFamilyProperties_drop(queue_families);
    if (index_for_graphics.variant == Option_None)
        return (ResultMargaretChosenQueueFamiliesOrSpanU8){ .variant = Result_Err, .err = cstr("No graphics queue family") };
    if (index_for_presentation.variant == Option_None)
        return (ResultMargaretChosenQueueFamiliesOrSpanU8){ .variant = Result_Err, .err = cstr("No presentation queue family") };
    return (ResultMargaretChosenQueueFamiliesOrSpanU8){ .variant = Result_Ok, .ok = (MargaretChosenQueueFamilies){
        .for_graphics = index_for_graphics.some, .for_presentation = index_for_presentation.some
    } };
}

// These are not the same as instance extensions
VecVecU8 margaret_get_extensions_of_physical_device(VkPhysicalDevice physical_device) {
    uint32_t extensions_count = 0;
    if (vkEnumerateDeviceExtensionProperties(physical_device, NULL, &extensions_count, NULL) != VK_SUCCESS)
        abortf("vkEnumerateDeviceExtensionProperties");
    VecVkExtensionProperties extensions = VecVkExtensionProperties_new_zeroinit(extensions_count);
    if (vkEnumerateDeviceExtensionProperties(physical_device, NULL, &extensions_count, extensions.buf) != VK_SUCCESS)
        abortf("vkEnumerateDeviceExtensionProperties");
    VecVecU8 res = VecVecU8_new_of_size(extensions_count);
    for (size_t i = 0; i < extensions_count; i++) {
        // Previous value here was default (_new). It can be safely discarded
        // ->extensionName is some null-terminated string, we need to acquire a copy
        *VecVecU8_mat(&res, i) = VecU8_from_cstr(VecVkExtensionProperties_at(&extensions, i)->extensionName);
    }
    VecVkExtensionProperties_drop(extensions);
    return res;
}

NODISCARD VecU8 margaret_stringify_memory_heap_flags(VkMemoryHeapFlags flags) {
    VecU8 result = VecU8_new();
    /* enum VkMemoryHeapFlagBits */
    if (flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
        VecU8_append_span(&result, cstr("VK_MEMORY_HEAP_DEVICE_LOCAL_BIT"));
    }
    if (flags & VK_MEMORY_HEAP_MULTI_INSTANCE_BIT) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_HEAP_MULTI_INSTANCE_BIT"));
    }
    if (flags & VK_MEMORY_HEAP_TILE_MEMORY_BIT_QCOM) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_HEAP_TILE_MEMORY_BIT_QCOM"));
    }
    return result;
}

NODISCARD VecU8 margaret_stringify_memory_property_flags(VkMemoryPropertyFlags flags) {
    VecU8 result = VecU8_new();
    /* enum VkMemoryPropertyFlagBits */
    if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
        VecU8_append_span(&result, cstr("VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT"));
    }
    if (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT"));
    }
    if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_PROPERTY_HOST_COHERENT_BIT"));
    }
    if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_PROPERTY_HOST_CACHED_BIT"));
    }
    if (flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT"));
    }
    if (flags & VK_MEMORY_PROPERTY_PROTECTED_BIT) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_PROPERTY_PROTECTED_BIT"));
    }
    if (flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD"));
    }
    if (flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD"));
    }
    if (flags & VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV) {
        VecU8_add_vertical_separator(&result);
        VecU8_append_span(&result, cstr("VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV"));
    }
    return result;
}

NODISCARD VecU8 margaret_stringify_device_memory_properties(VkPhysicalDevice physical_device) {
    VkPhysicalDeviceMemoryProperties properties;
    vkGetPhysicalDeviceMemoryProperties(physical_device, &properties);
    VecU8 result = VecU8_new();
    for (size_t h = 0; h < properties.memoryHeapCount; h++) {
        VecU8_append_vec(&result, VecU8_fmt("-+ Heap %u of %u bytes [ ", (U64)h, (U64)properties.memoryHeaps[h].size));
        VecU8_append_vec(&result, margaret_stringify_memory_heap_flags(properties.memoryHeaps[h].flags));
        VecU8_append_span(&result, cstr(" ], mem types below\n"));
        for (size_t t = 0; t < properties.memoryTypeCount; t++) {
            if (properties.memoryTypes->heapIndex == h) {
                VecU8_append_vec(&result, VecU8_fmt("----> Mem type %u [ ", (U64)t));
                VecU8_append_vec(&result, margaret_stringify_memory_property_flags(properties.memoryTypes[t].propertyFlags));
                VecU8_append_span(&result, cstr(" ]\n"));
            }
        }
    }
    return result;
}

NODISCARD VecU8 margaret_stringify_device_memory_properties_2(VkPhysicalDevice physical_device){
    VkPhysicalDeviceMaintenance3Properties maintenance3_properties = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES,
    };
    VkPhysicalDeviceMaintenance4Properties maintenance4_properties = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_PROPERTIES,
        .pNext = &maintenance3_properties,
    };
    VkPhysicalDeviceProperties2 properties = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
        .pNext = &maintenance4_properties,
    };
    vkGetPhysicalDeviceProperties2(physical_device, &properties);
    U64 maxBufferSize = maintenance4_properties.maxBufferSize;
    U64 maxMemoryAllocationCount = properties.properties.limits.maxMemoryAllocationCount;
    U64 maxMemoryAllocationSize = maintenance3_properties.maxMemoryAllocationSize;
    return VecU8_fmt(
        "maxMemoryAllocationsCount: %u\n"
        "maxMemoryAllocationSize: %u\n"
        "maxBufferSize: %u\n",
        maxMemoryAllocationCount, maxMemoryAllocationSize, maxBufferSize);
}

VkDevice margaret_create_logical_device(VkPhysicalDevice physical_device, MargaretChosenQueueFamilies queue_fam) {
    VkPhysicalDeviceFeatures physical_features;
    vkGetPhysicalDeviceFeatures(physical_device, &physical_features);
    float qfam_queue_priorities[1] = {1.f};
    VkDeviceQueueCreateInfo queue_crinfo[2] = { 0 };
    int queue_c = 0;
    if (queue_fam.for_graphics == queue_fam.for_presentation) {
        queue_c = 1;
        queue_crinfo[0].queueFamilyIndex = queue_fam.for_graphics;
    } else {
        queue_c = 2;
        queue_crinfo[0].queueFamilyIndex = queue_fam.for_graphics;
        queue_crinfo[1].queueFamilyIndex = queue_fam.for_presentation;
    }
    for (int i = 0; i < queue_c; i++) {
        queue_crinfo[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queue_crinfo[i].queueCount = 1;
        queue_crinfo[i].pQueuePriorities = qfam_queue_priorities;
    }

    VkPhysicalDeviceVulkan12Features used_vk12_features = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
        .runtimeDescriptorArray = true,
        .descriptorBindingPartiallyBound = true,
        .shaderSampledImageArrayNonUniformIndexing = true,
    };
    // We DEMAND synchronization2
    VkPhysicalDeviceSynchronization2Features used_synchronization2_features = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES,
        .pNext = &used_vk12_features,
        .synchronization2 = VK_TRUE,
    };
    VkPhysicalDeviceFeatures2 used_features2 = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
        .pNext = &used_synchronization2_features,
        .features = (VkPhysicalDeviceFeatures) {
            .geometryShader = true,
            .samplerAnisotropy = physical_features.samplerAnisotropy,
        },
    };

    const char* needed_extensions[] = {"VK_KHR_swapchain", "VK_KHR_synchronization2", "VK_KHR_maintenance4"};

    VkDeviceCreateInfo device_crinfo = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .pNext = (const void*)&used_features2,
        .queueCreateInfoCount = queue_c,
        .pQueueCreateInfos = queue_crinfo,
        .enabledExtensionCount = ARRAY_SIZE(needed_extensions),
        .ppEnabledExtensionNames = needed_extensions,
        // We leave that filed because we have specified features2 in `.pNext`
        .pEnabledFeatures = NULL,
    };

    VkDevice device;
    if (vkCreateDevice(physical_device, &device_crinfo, NULL, &device) != VK_SUCCESS)
        abortf("Could not create logical vulkan device");
    return device;
}

/* These variables are in some way enforced by VkSurfaceCapabilitiesKHR (but not completely determined) */
typedef struct {
    VkSurfaceFormatKHR surface_format;
    VkPresentModeKHR presentation_mode;
    VkExtent2D image_extent;
    uint32_t image_count;
    VkSurfaceTransformFlagBitsKHR surface_pre_transform;
    VkCompositeAlphaFlagBitsKHR composite_alpha;
} MargaretChosenSwapchainDetails;

OptionVkSurfaceFormatKHR margaret_choose_surface_format(const VecVkSurfaceFormatKHR* surface_formats) {
    for (size_t i = 0; i < surface_formats->len; i++) {
        VkSurfaceFormatKHR f = *VecVkSurfaceFormatKHR_at(surface_formats, i);
        if ((f.format == VK_FORMAT_R8G8B8A8_SRGB || f.format == VK_FORMAT_B8G8R8A8_SRGB) &&
            f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
            ) {
            return Some_VkSurfaceFormatKHR(f);
        }
    }
    return None_VkSurfaceFormatKHR();
}

OptionVkPresentModeKHR margaret_choose_presentation_mode(const VecVkPresentModeKHR* pres_modes) {
    OptionVkPresentModeKHR res = None_VkPresentModeKHR();
    for (size_t i = 0; i < pres_modes->len; i++) {
        VkPresentModeKHR mode = *VecVkPresentModeKHR_at(pres_modes, i);
        if (mode == VK_PRESENT_MODE_MAILBOX_KHR)
            return Some_VkPresentModeKHR(VK_PRESENT_MODE_MAILBOX_KHR);
        else if (mode == VK_PRESENT_MODE_FIFO_KHR)
            res = Some_VkPresentModeKHR(mode);
    }
    return res;
}

OptionVkCompositeAlphaFlagBitsKHR margaret_choose_composite_alpha(VkCompositeAlphaFlagBitsKHR bits) {
    if (bits & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
        return Some_VkCompositeAlphaFlagBitsKHR(VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR);
    if (bits & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)
        return Some_VkCompositeAlphaFlagBitsKHR(VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR);
    return None_VkCompositeAlphaFlagBitsKHR();
}

OptionVkExtent2D margaret_choose_image_extent(const VkSurfaceCapabilitiesKHR* capabilities, VkExtent2D sane_limits) {
    if (capabilities->currentExtent.width == UINT32_MAX) {
        if (capabilities->minImageExtent.width > sane_limits.width ||
            capabilities->minImageExtent.height > sane_limits.height)
            return None_VkExtent2D();
        return Some_VkExtent2D((VkExtent2D) { MIN_U32(sane_limits.width, capabilities->maxImageExtent.width),
            MIN_U32(sane_limits.height, capabilities->maxImageExtent.height) });
    }
    /* May be bigger, than a sane limit */
    return Some_VkExtent2D(capabilities->currentExtent);
}

uint32_t margaret_choose_swapchain_image_count(const VkSurfaceCapabilitiesKHR* capabilities) {
    return MIN_U32(capabilities->minImageCount + 1,
        capabilities->maxImageCount == 0 ? UINT32_MAX : capabilities->maxImageCount);
}

/* MargaretChosenSwapchainDetails or static string span, describing what is missing */
typedef struct {
    Result_variant variant;
    union {
        MargaretChosenSwapchainDetails ok;
        SpanU8 err;
    };
} ResultMargaretChosenSwapchainDetailsOrSpanU8;

// Both queries swapchain support details and selects needed formats and presentation modes
ResultMargaretChosenSwapchainDetailsOrSpanU8 margaret_choose_swapchain_details(
    VkPhysicalDevice physical_device, VkSurfaceKHR surface, VkExtent2D sane_image_extent_limit
    ) {
    /* 1. Getting surface capabilities + formats + presentation modes */
    VkSurfaceCapabilitiesKHR surface_capabilities;
    if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surface_capabilities) != VK_SUCCESS)
        abortf("vkGetPhysicalDeviceSurfaceCapabilitiesKHR");

    uint32_t format_count = 0;
    if (vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, NULL) != VK_SUCCESS)
        abortf("vkGetPhysicalDeviceSurfaceFormatsKHR");
    VecVkSurfaceFormatKHR surface_formats = VecVkSurfaceFormatKHR_new_zeroinit(format_count);
    if (vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, surface_formats.buf) != VK_SUCCESS)
        abortf("vkGetPhysicalDeviceSurfaceFormatsKHR");

    uint32_t pres_mode_count = 0;
    if (vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &pres_mode_count, NULL) != VK_SUCCESS)
        abortf("vkGetPhysicalDeviceSurfacePresentModesKHR");
    VecVkPresentModeKHR pres_modes = VecVkPresentModeKHR_new_zeroinit(pres_mode_count);
    if (vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &pres_mode_count, pres_modes.buf) != VK_SUCCESS)
        abortf("vkGetPhysicalDeviceSurfacePresentModesKHR");

    /* 2. Choosing surface format and presentation mode */
    OptionVkSurfaceFormatKHR chosen_surface_format = margaret_choose_surface_format(&surface_formats);
    if (chosen_surface_format.variant == Option_None)
        return (ResultMargaretChosenSwapchainDetailsOrSpanU8){ .variant = Result_Err,
            .err = cstr("No suitable surface format") };
    OptionVkPresentModeKHR chosen_present_mode = margaret_choose_presentation_mode(&pres_modes);
    if (chosen_present_mode.variant == Option_None)
        return (ResultMargaretChosenSwapchainDetailsOrSpanU8){ .variant = Result_Err,
            .err = cstr("No suitable swapchain presentation mode") };

    OptionVkExtent2D image_extent = margaret_choose_image_extent(&surface_capabilities, sane_image_extent_limit);
    if (image_extent.variant == Option_None)
        return (ResultMargaretChosenSwapchainDetailsOrSpanU8){ .variant = Result_Err,
            .err = cstr("Can't choose sane image extent")};

    uint32_t image_count = margaret_choose_swapchain_image_count(&surface_capabilities);

    OptionVkCompositeAlphaFlagBitsKHR chosen_composite_alpha = margaret_choose_composite_alpha(
        surface_capabilities.supportedCompositeAlpha);
    if (chosen_composite_alpha.variant == Option_None)
        return (ResultMargaretChosenSwapchainDetailsOrSpanU8){.variant = Result_Err, .err = cstr("No composite alpha")};

    VecVkSurfaceFormatKHR_drop(surface_formats);
    VecVkPresentModeKHR_drop(pres_modes);

    return (ResultMargaretChosenSwapchainDetailsOrSpanU8){ .variant = Result_Ok,\
        .ok = (MargaretChosenSwapchainDetails){
            .surface_format = chosen_surface_format.some, .presentation_mode = chosen_present_mode.some,
            .image_extent = image_extent.some, .image_count = image_count,
            .surface_pre_transform = surface_capabilities.currentTransform,
            .composite_alpha = chosen_composite_alpha.some
        }
    };
}

OptionVkFormat margaret_find_supported_format_for_linear_tiling(
    VkPhysicalDevice physical_device, SpanVkFormat candidates, VkFormatFeatureFlags required_features
    ) {
    for (size_t i = 0; i < candidates.len; i++) {
        VkFormat format = *SpanVkFormat_at(candidates, i);
        VkFormatProperties properties;
        vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties);
        if ((properties.linearTilingFeatures & required_features) == required_features)
            return Some_VkFormat(format);
    }
    return None_VkFormat();
}

OptionVkFormat margaret_find_supported_format_for_optimal_tiling(
    VkPhysicalDevice physical_device, SpanVkFormat candidates, VkFormatFeatureFlags required_features
    ) {
    for (size_t i = 0; i < candidates.len; i++) {
        VkFormat format = *SpanVkFormat_at(candidates, i);
        VkFormatProperties properties;
        vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties);
        if ((properties.optimalTilingFeatures & required_features) == required_features)
            return Some_VkFormat(format);
    }
    return None_VkFormat();
}

OptionVkFormat margaret_find_supported_zbuffer_format(VkPhysicalDevice physical_device) {
    VkFormat candidates[3] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT };
    return margaret_find_supported_format_for_optimal_tiling(physical_device,
        (SpanVkFormat){.data = candidates, .len = ARRAY_SIZE(candidates)}, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
}

OptionVkFormat margaret_find_supported_hdr_buffer_format(VkPhysicalDevice physical_device) {
    // todo: use format without alpha component (even though they are probably not supported)
    VkFormat candidates[] = { /*VK_FORMAT_R16G16B16_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT,*/
         VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT  };
    return margaret_find_supported_format_for_optimal_tiling(physical_device,
        (SpanVkFormat){.data = candidates, .len = ARRAY_SIZE(candidates)},
        VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT );
}

bool margaret_is_format_supported_for_textures(VkPhysicalDevice physical_device, VkFormat format) {
    VkFormatProperties props;
    vkGetPhysicalDeviceFormatProperties(physical_device, format, &props);
    return props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
}

typedef struct {
    VkPhysicalDevice physical_device;
    S64 score;
    // static string
    SpanU8 disqualification_reason;
} MargaretScoredPhysicalDevice;

MargaretScoredPhysicalDevice margaret_score_physical_device(
    VkPhysicalDevice dev, VkSurfaceKHR surface, SpanU8 favourite_word, SpanU8 forbidden_word,
    VkExtent2D sane_image_extent_limit
    ) {
    VkPhysicalDeviceProperties properties;
    vkGetPhysicalDeviceProperties(dev, &properties);
    if (string_contains_string_ignorecase(SpanU8_from_cstr(properties.deviceName), forbidden_word))
        return (MargaretScoredPhysicalDevice){ dev, -1, cstr("Bugged gpu") };
    { /* Here I print a little bit of memory properties */
        printf("MEMORY OF %s\n", properties.deviceName);  // todo: score device based on memory
        VecU8 txt = margaret_stringify_device_memory_properties(dev);
        SpanU8_print(VecU8_to_span(&txt));
        VecU8_drop(txt);
    }
    VkPhysicalDeviceVulkan12Features vk12_features = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES
    };
    VkPhysicalDeviceSynchronization2Features synchronization2_features = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES,
        .pNext = &vk12_features
    };
    VkPhysicalDeviceFeatures2 features2 = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
        .pNext = &synchronization2_features,
    };
    vkGetPhysicalDeviceFeatures2(dev, &features2);
    // printf("Device %s\nmaxBoundDescriptorSets: %" PRIu32 " \nmaxPerStageDescriptorUniformBuffers: %" PRIu32 "\n"
        // , properties.deviceName, properties.limits.maxBoundDescriptorSets, properties.limits.maxPerStageDescriptorUniformBuffers);

    S64 score = 0;
    if (string_contains_string_ignorecase(SpanU8_from_cstr(properties.deviceName), favourite_word))
        score += 100000;
    if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
        score += 1000;
    else if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
        score += 100;
    if (!features2.features.geometryShader)
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("No geometry shaders")};
    if (!synchronization2_features.synchronization2)
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("No synchronization2")};
    if (!vk12_features.shaderSampledImageArrayNonUniformIndexing)
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("No shaderSampledImageArrayNonUniformIndexing")};
    if (!vk12_features.runtimeDescriptorArray)
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("No runtimeDescriptorArray")};
    if (!vk12_features.descriptorBindingPartiallyBound)
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("No descriptorBindingPartiallyBound")};
    ResultMargaretChosenQueueFamiliesOrSpanU8 queue_families = margaret_choose_good_queue_families(dev, surface);
    if (queue_families.variant == Result_Err)
        return (MargaretScoredPhysicalDevice){dev, -1, queue_families.err};
    // Checking device specific extensions (VK_KHR_swapchain required to check swap_chain support details
    VecVecU8 dev_extensions = margaret_get_extensions_of_physical_device(dev);
    SpanU8 required_dev_extensions[] = {
        cstr("VK_KHR_swapchain"), cstr("VK_KHR_synchronization2"),
        cstr("VK_KHR_maintenance4")};
    for (size_t ei = 0; ei < ARRAY_SIZE(required_dev_extensions); ei++) {
        if (!is_string_in_string_vec(required_dev_extensions[ei], &dev_extensions))
            return (MargaretScoredPhysicalDevice){dev, -1, cstr("Missing some device extensions")};
    }
    VecVecU8_drop(dev_extensions);
    // Extension VK_KHR_swapchain is present, now we can call query_swap_chain_support
    ResultMargaretChosenSwapchainDetailsOrSpanU8 swapchain_details = margaret_choose_swapchain_details(dev, surface, sane_image_extent_limit);
    if (swapchain_details.variant == Result_Err) {
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("Physical device lacks nice swapchain support")};
    }
    if (!margaret_is_format_supported_for_textures(dev, VK_FORMAT_R8_UNORM))
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("R8_UNORM format can't be used for optimal tiling of textures")};
    if (!margaret_is_format_supported_for_textures(dev, VK_FORMAT_R8G8B8A8_SRGB))
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("R8G8B8A8_SRGB format can't be used for optimal tiling of textures")};
    if (!margaret_is_format_supported_for_textures(dev, VK_FORMAT_R8G8B8A8_UNORM))
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("R8G8B8A8_UNORM format can't be used for optimal tiling of textures")};
    if (!margaret_is_format_supported_for_textures(dev, VK_FORMAT_R16G16B16A16_SFLOAT))
        return (MargaretScoredPhysicalDevice){dev, -1, cstr("VK_FORMAT_R16G16B16A16_SFLOAT format can't be used for optimal tiling of textures")};
    return (MargaretScoredPhysicalDevice){dev, score, cstr("")};
}

#define MargaretScoredPhysicalDevice_less_MargaretScoredPhysicalDevice(cap, cbp) ((cap)->score < (cbp)->score)

#include "../../../gen/l1/eve/margaret/VecMargaretScoredPhysicalDevice.h"

VecMargaretScoredPhysicalDevice margaret_get_physical_devices_scored(
    VkInstance instance, VkSurfaceKHR surface,
    SpanU8 favourite_word, SpanU8 forbidden_word, VkExtent2D sane_image_extent_limit
    ) {
    uint32_t physical_device_count = 0;
    if (vkEnumeratePhysicalDevices(instance, &physical_device_count, NULL) != VK_SUCCESS)
        abortf("vkEnumeratePhysicalDevices");
    VecVkPhysicalDevice physical_devices = VecVkPhysicalDevice_new_zeroinit(physical_device_count);
    if (vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices.buf) != VK_SUCCESS)
        abortf("vkEnumeratePhysicalDevices");
    VecMargaretScoredPhysicalDevice scored_devices = VecMargaretScoredPhysicalDevice_new_zeroinit(physical_device_count);
    for (uint32_t i = 0; i < physical_device_count; i++) {
        *VecMargaretScoredPhysicalDevice_mat(&scored_devices, i) = margaret_score_physical_device(
            *VecVkPhysicalDevice_at(&physical_devices, i), surface,
            favourite_word, forbidden_word, sane_image_extent_limit
        );
    }
    VecMargaretScoredPhysicalDevice_sort(&scored_devices);
    VecVkPhysicalDevice_drop(physical_devices);
    return scored_devices;
}

VkPhysicalDevice margaret_select_one_physical_device(
    VkInstance instance, VkSurfaceKHR surface,
    SpanU8 favourite_word, SpanU8 forbidden_word, VkExtent2D sane_image_extent_limit
    ) {
    VecMargaretScoredPhysicalDevice scored_devices = margaret_get_physical_devices_scored(instance, surface,
        favourite_word, forbidden_word, sane_image_extent_limit);
    printf("Physical devices (with scores):\n");
    for (size_t i = 0; i < scored_devices.len; i++) {
        const MargaretScoredPhysicalDevice* dev = VecMargaretScoredPhysicalDevice_at(&scored_devices, i);
        VkPhysicalDeviceProperties dev_props;
        vkGetPhysicalDeviceProperties(dev->physical_device, &dev_props);
        if (dev->score >= 0) {
            printf("%s: %ld\n", dev_props.deviceName, dev->score);
        } else {
            printf("%s: DISQUALIFIED: ", dev_props.deviceName);
            SpanU8_print(dev->disqualification_reason);
            putchar('\n');
        }
    }
    if (scored_devices.len == 0)
        abortf("No vulkan devices");
    const MargaretScoredPhysicalDevice* best_dev = VecMargaretScoredPhysicalDevice_at(&scored_devices, scored_devices.len - 1);
    if (best_dev->score < 0)
        abortf("No suitable vulkan devices");
    VkPhysicalDevice result = best_dev->physical_device;
    VecMargaretScoredPhysicalDevice_drop(scored_devices); /* best_dev, scored_devices invalidated */
    return result;
}

VkSwapchainKHR margaret_create_swapchain (
    VkDevice device,
    MargaretChosenQueueFamilies queue_families,
    MargaretChosenSwapchainDetails swapchain_details,
    VkSurfaceKHR surface, VkSwapchainKHR old_swapchain
    ) {
    VkSwapchainCreateInfoKHR swapchain_crinfo = {
        .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
        .surface = surface,
        .minImageCount = swapchain_details.image_count,
        .imageFormat = swapchain_details.surface_format.format,
        .imageColorSpace = swapchain_details.surface_format.colorSpace,
        .imageExtent = swapchain_details.image_extent,
        // when trying VR update this according to VkSurfaceCapabilitiesKHR::maxImageArrayLayers
        .imageArrayLayers = 1,
        .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
        // Filling imageSharingMode and queueFamilyIndexes later
        .preTransform = swapchain_details.surface_pre_transform,
        .compositeAlpha = swapchain_details.composite_alpha,
        .presentMode = swapchain_details.presentation_mode,
        .clipped = VK_TRUE,
        .oldSwapchain = old_swapchain,

    };
    uint32_t sharing_between_qfams[2] = {queue_families.for_graphics, queue_families.for_presentation};
    if (queue_families.for_graphics != queue_families.for_presentation) {
        swapchain_crinfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
        swapchain_crinfo.queueFamilyIndexCount = ARRAY_SIZE(sharing_between_qfams);
        swapchain_crinfo.pQueueFamilyIndices = sharing_between_qfams;
    } else {
        swapchain_crinfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    }

    VkSwapchainKHR swapchain;
    if (vkCreateSwapchainKHR(device, &swapchain_crinfo, NULL, &swapchain) != VK_SUCCESS)
        abortf("vkCreateSwapchainKHR");
    return swapchain;
}

VecVkImageView margaret_create_swapchain_image_views(
    VkDevice device, VkSwapchainKHR swapchain,
    VkFormat image_format
    ) {
    uint32_t swapchain_image_count = 0;
    if (vkGetSwapchainImagesKHR(device, swapchain, &swapchain_image_count, NULL) != VK_SUCCESS)
        abortf("vkGetSwapchainImagesKHR");
    VecVkImage swapchain_images = VecVkImage_new_zeroinit(swapchain_image_count);
    if (vkGetSwapchainImagesKHR(device, swapchain, &swapchain_image_count, swapchain_images.buf) != VK_SUCCESS)
        abortf("vkGetSwapchainImagesKHR");

    VecVkImageView swapchain_image_views = VecVkImageView_new_zeroinit(swapchain_image_count);
    for (uint32_t i = 0; i < swapchain_image_count; i++) {
        VkImageViewCreateInfo imageview_crinfo = {
            .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
            .image = *VecVkImage_at(&swapchain_images, i),
            .viewType = VK_IMAGE_VIEW_TYPE_2D,
            .format = image_format,
            .components.r = VK_COMPONENT_SWIZZLE_IDENTITY,
            .components.g = VK_COMPONENT_SWIZZLE_IDENTITY,
            .components.b = VK_COMPONENT_SWIZZLE_IDENTITY,
            .components.a = VK_COMPONENT_SWIZZLE_IDENTITY,
            .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .subresourceRange.baseMipLevel = 0,
            .subresourceRange.levelCount = 1,
            .subresourceRange.baseArrayLayer = 0,
            .subresourceRange.layerCount = 1,
        };
        if (vkCreateImageView(device, &imageview_crinfo, NULL, VecVkImageView_mat(&swapchain_image_views, i)) != VK_SUCCESS)
            abortf("vkCreateImageView");
    }
    VecVkImage_drop(swapchain_images);
    return swapchain_image_views;
}

// Okay, this part is already somewhat pipeline-specific
VecVkFramebuffer margaret_create_swapchain_framebuffers(
    VkDevice device, const VecVkImageView* swapchain_image_views, VkRenderPass render_pass, VkExtent2D image_extent
    ) {
    VecVkFramebuffer swapchain_framebuffers = VecVkFramebuffer_new_zeroinit(swapchain_image_views->len);
    for (uint32_t i = 0; i < swapchain_image_views->len; i++) {
        VkImageView attachments[1] = {*VecVkImageView_at(swapchain_image_views, i)};
        printf("CREATING FRAMEBUFFER: %u %u\n", image_extent.width, image_extent.height);
        VkFramebufferCreateInfo framebuffer_crinfo = {
            .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
            .renderPass = render_pass,
            .attachmentCount = ARRAY_SIZE(attachments),
            .pAttachments = attachments,
            .width = image_extent.width,
            .height = image_extent.height,
            .layers = 1,
        };

        if (vkCreateFramebuffer(device, &framebuffer_crinfo, NULL, VecVkFramebuffer_mat(& swapchain_framebuffers, i)) != VK_SUCCESS)
            abortf("vkCreateFramebuffer");
    }
    return swapchain_framebuffers;
}

VkSemaphore margaret_create_semaphore(VkDevice device) {
    VkSemaphoreCreateInfo crinfo = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
    VkSemaphore res;
    if (vkCreateSemaphore(device, &crinfo, NULL, &res) != VK_SUCCESS)
        abortf("vkCreateSemaphore");
    return res;
}

VkFence margaret_create_fence(VkDevice device, bool create_signaled) {
    VkFenceCreateInfo crinfo = {
        .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
        .flags = create_signaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0
    };
    VkFence res;
    if (vkCreateFence(device, &crinfo, NULL, &res) != VK_SUCCESS)
        abortf("vkCreateFence");
    return res;
}

typedef struct {
    VkSwapchainKHR swapchain;
    VecVkImageView image_views;
    VecVkFramebuffer framebuffers;
    VkExtent2D extent;
    VecVkSemaphore rendering_finished_here_semaphores;
} MargaretSwapchainBundle;

MargaretSwapchainBundle MargaretSwapchainBundle_new(
    VkDevice device, MargaretChosenQueueFamilies queue_families, MargaretChosenSwapchainDetails swapchain_details,
    VkSurfaceKHR surface, VkRenderPass render_pass, VkSwapchainKHR old_swapchain
    ) {
    VkSwapchainKHR swapchain = margaret_create_swapchain(device, queue_families, swapchain_details, surface, old_swapchain);
    VecVkImageView image_views = margaret_create_swapchain_image_views(device, swapchain, swapchain_details.surface_format.format);
    VecVkFramebuffer framebuffers = margaret_create_swapchain_framebuffers(device, &image_views, render_pass, swapchain_details.image_extent);
    VecVkSemaphore rendering_finished_semaphores = VecVkSemaphore_new_zeroinit(framebuffers.len);
    for (size_t i = 0; i < framebuffers.len; i++)
        *VecVkSemaphore_mat(&rendering_finished_semaphores, i) = margaret_create_semaphore(device);
    return (MargaretSwapchainBundle){ .swapchain = swapchain, .image_views = image_views,
        .framebuffers = framebuffers, .extent = swapchain_details.image_extent,
        .rendering_finished_here_semaphores = rendering_finished_semaphores
    };
}

VkSwapchainKHR MargaretSwapchainBundle_pop_swapchain_drop_rest(VkDevice device, MargaretSwapchainBundle swfb) {
    for (size_t i = 0; i < swfb.rendering_finished_here_semaphores.len; i++)
        vkDestroySemaphore(device, *VecVkSemaphore_at(&swfb.rendering_finished_here_semaphores, i), NULL);
    VecVkSemaphore_drop(swfb.rendering_finished_here_semaphores);
    for (size_t i = 0; i < swfb.framebuffers.len; i++) {
        vkDestroyFramebuffer(device, *VecVkFramebuffer_at(&swfb.framebuffers, i), NULL);
    }
    VecVkFramebuffer_drop(swfb.framebuffers);
    for (size_t i = 0; i < swfb.image_views.len; i++) {
        vkDestroyImageView(device, *VecVkImageView_at(&swfb.image_views, i), NULL);
    }
    VecVkImageView_drop(swfb.image_views);
    // Old swapchain bundle is 83% dropped
    return swfb.swapchain;
}

// Not a regular _drop method, because it requires a bundled VkDevice
void MargaretSwapchainBundle_drop_with_device(VkDevice device, MargaretSwapchainBundle swfb) {
    VkSwapchainKHR swapchain = MargaretSwapchainBundle_pop_swapchain_drop_rest(device, swfb);
    vkDestroySwapchainKHR(device, swapchain, NULL);
    // Now swapchain bundle is 100% dropped
}

VkShaderModule margaret_VkShaderModule_new(VkDevice device, VecU8 code) {
    if (code.len < 4)
        abortf("Kill yourself, please\n");
    VkShaderModule shad_module;
    check(vkCreateShaderModule(device, &(VkShaderModuleCreateInfo){
        .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
        .codeSize = code.len,
        // Now this is funny, we can't put arbitrary byte-string here, it should be 4-byte aligned
        // Thanks goodness all the strings in VecU8 are allocated with calloc, which gives high alignment to
        // virtually everything
        .pCode = (const uint32_t*)code.buf
    }, NULL, &shad_module) == VK_SUCCESS);
    VecU8_drop(code);
    return shad_module;
}

VkCommandPool margaret_create_resettable_command_pool(VkDevice device, uint32_t wanted_queue_family) {
    VkCommandPoolCreateInfo crinfo = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
        .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
        .queueFamilyIndex = wanted_queue_family,
    };
    VkCommandPool res;
    if (vkCreateCommandPool(device, &crinfo, NULL, &res) != VK_SUCCESS)
        abortf("vkCreateCommandPool");
    return res;
}

VkCommandBuffer margaret_allocate_command_buffer(VkDevice device, VkCommandPool pool) {
    VkCommandBufferAllocateInfo alloc_info = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
        .commandPool = pool,
        .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
        .commandBufferCount = 1,
    };
    VkCommandBuffer res;
    if (vkAllocateCommandBuffers(device, &alloc_info, &res) != VK_SUCCESS)
        abortf("vkAllocateCommandBuffers");
    return res;
}


VkSurfaceKHR margaret_create_surface(VkInstance instance, struct wl_display* wl_display, struct wl_surface* wl_surface) {
    VkWaylandSurfaceCreateInfoKHR crinfo = {
        .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
        .display = wl_display, .surface = wl_surface
    };
    VkSurfaceKHR surface;
    if (vkCreateWaylandSurfaceKHR(instance, &crinfo, NULL, &surface) != VK_SUCCESS)
        abortf("vkCreateWaylandSurfaceKHR\n");
    return surface;
}

// type_filter is a set of memory types (bit set) and we return one of its elements.
// Result must satisfy `properties`
// Bit index in `type_filter` is an index in VkPhysicalDeviceMemoryProperties::memoryTypes for that physical_device
uint32_t margaret_find_memory_type(
    VkPhysicalDevice physical_device, uint32_t type_filter,
    VkMemoryPropertyFlags properties
    ) {
    VkPhysicalDeviceMemoryProperties mem_properties;
    vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties);
    assert(mem_properties.memoryTypeCount < 32);
    for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) {
        if ((type_filter & (1 << i)) && ((mem_properties.memoryTypes[i].propertyFlags & properties) == properties)) {
            return i;
        }
    }
    abortf("Could not find a good memory type");
}

// Suppose the previous buffer ended at pos `was`. It may just so happen that `was` is aligned with the next buffer,
// but if it is not, for some reason, we skip a couple of bytes after `was` and return the smallest next aligned pos
VkDeviceSize margaret_align_start_of_buffer(VkDeviceSize was, VkDeviceSize alignment) {
    return was % alignment ? (was + alignment - was % alignment) : was;
}

// For texture
VkImageView margaret_create_view_for_image (
    VkDevice device, VkImage image, VkFormat format, VkImageAspectFlags aspect_flags
    ){
    VkImageView view;
    check(vkCreateImageView(device, &(VkImageViewCreateInfo){
        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
        .image = image,
        .viewType = VK_IMAGE_VIEW_TYPE_2D,
        .format = format,
        .subresourceRange = (VkImageSubresourceRange){
            .aspectMask = aspect_flags, .baseMipLevel = 0, .levelCount = 1,
            .baseArrayLayer = 0, .layerCount = 1,
        },
    }, NULL, &view) == VK_SUCCESS)
    return view;
}

// For texture
VkSampler margaret_create_sampler(VkPhysicalDevice physical_device, VkDevice device, bool make_linear) {
    VkPhysicalDeviceProperties physical_device_properties;
    vkGetPhysicalDeviceProperties(physical_device, &physical_device_properties);
    VkPhysicalDeviceFeatures physical_device_features;
    vkGetPhysicalDeviceFeatures(physical_device, &physical_device_features);
    VkSampler sampler;
    check(vkCreateSampler(device, &(VkSamplerCreateInfo){
        .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
        .magFilter = make_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST,
        .minFilter = make_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST,
        .mipmapMode = make_linear ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST,
        .addressModeU = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT,
        .addressModeV = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT,
        .addressModeW = VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT,
        .mipLodBias = 0.f,  // will understand that when I generate mipmaps
        .anisotropyEnable = physical_device_features.samplerAnisotropy,
        // only if feature device_used_features.samplerAnisotropy was enabled
        .maxAnisotropy = physical_device_properties.limits.maxSamplerAnisotropy,
        .compareEnable = VK_FALSE,
        .compareOp = VK_COMPARE_OP_ALWAYS,
        .minLod = 0.f,
        .maxLod = 0.f,
        .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
        .unnormalizedCoordinates = VK_FALSE,
    }, NULL, &sampler) == VK_SUCCESS);

    return sampler;
}

VkDescriptorPool margaret_create_descriptor_set_pool(
    VkDevice device, uint32_t ubo_descriptor_count, uint32_t image_sampler_descriptor_count, uint32_t max_sets
    ) {
    VkDescriptorPoolSize sizes[2];
    int sizes_c = 0;
    if (ubo_descriptor_count > 0) {
        sizes[sizes_c] = (VkDescriptorPoolSize){
            .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
            .descriptorCount = ubo_descriptor_count
        };
        sizes_c++;
    }
    if (image_sampler_descriptor_count > 0) {
        sizes[sizes_c] = (VkDescriptorPoolSize){
            .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
            .descriptorCount = image_sampler_descriptor_count
        };
        sizes_c++;
    }
    VkDescriptorPool descriptor_pool;
    check(vkCreateDescriptorPool(device, &(VkDescriptorPoolCreateInfo){
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
        .maxSets = max_sets,
        .poolSizeCount = sizes_c,
        .pPoolSizes = sizes,
    }, NULL, &descriptor_pool) == VK_SUCCESS)
    return descriptor_pool;
}

VkDescriptorSet margaret_allocate_descriptor_set(VkDevice device, VkDescriptorPool descriptor_pool, VkDescriptorSetLayout layout) {
    VkDescriptorSetAllocateInfo alloc_info = {
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
        .descriptorPool = descriptor_pool,
        .descriptorSetCount = 1,
        .pSetLayouts = &layout,
    };
    VkDescriptorSet descriptor_set;
    if (vkAllocateDescriptorSets(device, &alloc_info, &descriptor_set) != VK_SUCCESS)
        abortf("vkAllocateDescriptorSets");
    return descriptor_set;
}

/* Aborts on error */
void margaret_reset_and_begin_command_buffer(VkCommandBuffer command_buffer){
    check(vkResetCommandBuffer(command_buffer, 0) == VK_SUCCESS);
    check(vkBeginCommandBuffer(command_buffer,
        &(VkCommandBufferBeginInfo){ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }) == VK_SUCCESS);
}

/* Aborts on error */
void margaret_end_command_buffer(VkCommandBuffer command_buffer){
    check(vkEndCommandBuffer(command_buffer) == VK_SUCCESS);
}

typedef struct {
    VkPipelineLayout pipeline_layout;
    VecU8 vertex_shader_code;
    VecU8 geometry_shader_code;
    VecU8 fragment_shader_code;
    U32 vertexBindingDescriptionCount;
    VkVertexInputBindingDescription* pVertexBindingDescriptions;
    U32 vertexAttributeDescriptionCount;
    VkVertexInputAttributeDescription* pVertexAttributeDescriptions;
    bool depthTestEnable;
    bool depthWriteEnable;
    bool blendEnable;
} MargaretMostImportantPipelineOptions;

VkPipeline margaret_create_triangle_pipeline_one_attachment(
    VkDevice device, VkRenderPass render_pass, U32 renderpass_subpass,
    MargaretMostImportantPipelineOptions op
    ){
    VkPipelineShaderStageCreateInfo shader_modules[3] = {
        (VkPipelineShaderStageCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
            .module = margaret_VkShaderModule_new(device, op.vertex_shader_code),
            .stage = VK_SHADER_STAGE_VERTEX_BIT, .pName = "main",
        },
        (VkPipelineShaderStageCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
            .module = margaret_VkShaderModule_new(device, op.fragment_shader_code),
            .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .pName = "main",
        },
    };
    U32 shader_modules_c = 2;
    if (op.geometry_shader_code.len > 0) {
        shader_modules[shader_modules_c] = (VkPipelineShaderStageCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
            .module = margaret_VkShaderModule_new(device, op.geometry_shader_code),
            .stage = VK_SHADER_STAGE_GEOMETRY_BIT, .pName = "main",
        };
        shader_modules_c++;
    }

    VkGraphicsPipelineCreateInfo pipeline_crinfo = {
        .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
        .stageCount = shader_modules_c,
        .pStages = shader_modules,
        .pVertexInputState = &(VkPipelineVertexInputStateCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
            .vertexBindingDescriptionCount = op.vertexBindingDescriptionCount,
            .pVertexBindingDescriptions = op.pVertexBindingDescriptions,
            .vertexAttributeDescriptionCount = op.vertexAttributeDescriptionCount,
            .pVertexAttributeDescriptions = op.pVertexAttributeDescriptions,
        },
        .pInputAssemblyState = &(VkPipelineInputAssemblyStateCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
            .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
            .primitiveRestartEnable = VK_FALSE,
        },
        .pViewportState = &(VkPipelineViewportStateCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
            // We are using dynamic viewport and scissors, that is why we do not attach viewport/scissor values
            // when creating a rendering pipeline. We will do that later
            .viewportCount = 1,
            .scissorCount = 1,
        },
        .pRasterizationState = &(VkPipelineRasterizationStateCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
            .depthClampEnable = VK_FALSE,
            .polygonMode = VK_POLYGON_MODE_FILL,
            // .cullMode = VK_CULL_MODE_BACK_BIT,
            .cullMode = VK_CULL_MODE_NONE,
            .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
            .depthBiasEnable = VK_FALSE,
            .depthBiasConstantFactor = 0.0f,
            .depthBiasClamp = 0.0f,
            .depthBiasSlopeFactor = 0.0f,
            .lineWidth = 1.0f,
        },
        .pMultisampleState = &(VkPipelineMultisampleStateCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
            .sampleShadingEnable = VK_FALSE,
            .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
            .minSampleShading = 1.0f,
            .pSampleMask = NULL,
            .alphaToCoverageEnable = VK_FALSE,
            .alphaToOneEnable = VK_FALSE,
        },
        .pDepthStencilState = &(VkPipelineDepthStencilStateCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
            .depthTestEnable = op.depthTestEnable,
            .depthWriteEnable = op.depthWriteEnable,
            .depthCompareOp = VK_COMPARE_OP_LESS
        },
        .pColorBlendState = &(VkPipelineColorBlendStateCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
            .logicOpEnable = VK_FALSE,
            .logicOp = VK_LOGIC_OP_COPY,
            .attachmentCount = 1,
            .pAttachments = &(VkPipelineColorBlendAttachmentState){
                .blendEnable = op.blendEnable,
                .srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA,
                .dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
                .colorBlendOp = VK_BLEND_OP_ADD,
                .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
                .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
                .alphaBlendOp = VK_BLEND_OP_ADD,
                .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
                    VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT
            },
        },
        .pDynamicState = &(VkPipelineDynamicStateCreateInfo){
            .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
            .dynamicStateCount = 2,
            .pDynamicStates = (VkDynamicState[]){VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR},
        },
        .layout = op.pipeline_layout,
        .renderPass = render_pass,
        .subpass = renderpass_subpass,
        .basePipelineHandle = VK_NULL_HANDLE,
    };

    VkPipeline pipeline;
    check(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_crinfo, NULL, &pipeline) == VK_SUCCESS);

    for (U32 i = 0; i < shader_modules_c; i++) {
        vkDestroyShaderModule(device, shader_modules[i].module, NULL);
    }
    return pipeline;
}

#include "vulkan_memory.h"

// todo: move image copying function here

typedef struct {
    VkDevice device;
    VkPhysicalDevice physical_device;
    VkCommandBuffer transfer_cmd_buffer;
    MargaretImgAllocator* dev_local_images;
    MargaretBufAllocator* dev_local_buffers;
    MargaretBufAllocator* staging_buffers;
    VkDescriptorPool descriptor_pool;
    VkSampler linear_sampler;
    VkSampler nearest_sampler;
} MargaretEngineReference;

void margaret_rec_cmd_copy_buffer(
    VkCommandBuffer cmd_buf,
    const MargaretSubbuf* src_allocation, U64 src_offset,
    const MargaretSubbuf* dst_allocation, U64 dst_offset, U64 length){
    vkCmdCopyBuffer(cmd_buf,
        MargaretSubbuf_get_buffer(src_allocation), MargaretSubbuf_get_buffer(dst_allocation),
        1, &(VkBufferCopy){
            .srcOffset = src_allocation->start + src_offset, .dstOffset = dst_allocation->start + dst_offset,
            .size = length});
}

void margaret_rec_cmd_copy_buffer_one_to_one_part(
    VkCommandBuffer cmd_buf,
    const MargaretSubbuf* src_allocation,
    const MargaretSubbuf* dst_allocation, U64 offset, U64 length){
    assert(offset + length <= src_allocation->len);
    assert(offset + length <= dst_allocation->len);
    vkCmdCopyBuffer(cmd_buf,
        MargaretSubbuf_get_buffer(src_allocation), MargaretSubbuf_get_buffer(dst_allocation),
        1, &(VkBufferCopy){
            .srcOffset = src_allocation->start + offset, .dstOffset = dst_allocation->start + offset, .size = length});
}

void margaret_rec_cmd_copy_buffer_one_to_one(
    VkCommandBuffer cmd_buf, const MargaretSubbuf* src_allocation, const MargaretSubbuf* dst_allocation){
    U64 copying_len = MIN_U64(src_allocation->len, dst_allocation->len);
    vkCmdCopyBuffer(cmd_buf,
        MargaretSubbuf_get_buffer(src_allocation), MargaretSubbuf_get_buffer(dst_allocation),
        1, &(VkBufferCopy){
            .srcOffset = src_allocation->start, .dstOffset = dst_allocation->start, .size = copying_len});
}

/* (destination_stage_mask, destination_access_mask) are probably
 * (VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT) */
void margaret_rec_cmd_copy_buffer_to_image_one_to_one_color_aspect(
    VkCommandBuffer cmd_buf, const MargaretSubbuf* src, MargaretImg* dst,
    VkImageLayout dst_new_layout,
    VkPipelineStageFlags destination_stage_mask, VkAccessFlags destination_access_mask){

    vkCmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
    0 /* Flags */, 0, NULL, 0, NULL, 1, &(VkImageMemoryBarrier){
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .srcAccessMask = 0,
        .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
        .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
        .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .image = dst->a.image,
        .subresourceRange = (VkImageSubresourceRange){
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0,
            .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1,
        },
    });

    vkCmdCopyBufferToImage(cmd_buf, MargaretSubbuf_get_buffer(src), dst->a.image,
        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &(VkBufferImageCopy){
        .bufferOffset = src->start,
        .bufferRowLength = 0,
        .bufferImageHeight = 0,
        .imageSubresource = (VkImageSubresourceLayers){
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1,
        },
        .imageOffset = {0, 0, 0},
        .imageExtent = { .width = dst->width, .height = dst->height, .depth = 1 },
    });

    vkCmdPipelineBarrier(cmd_buf, VK_PIPELINE_STAGE_TRANSFER_BIT, destination_stage_mask,
        0 /* Flags */,  0, NULL, 0, NULL, 1, &(VkImageMemoryBarrier){
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
        .dstAccessMask = destination_access_mask,
        .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        .newLayout = dst_new_layout,
        .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .image = dst->a.image,
        .subresourceRange = (VkImageSubresourceRange){
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0,
            .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1,
        },
    });
    dst->current_layout = dst_new_layout;
}

#endif
