#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vulkan/vulkan.h>
#include <vulkan/vk_enum_string_helper.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_vulkan.h>

#undef max
#undef min

#define max(a,b) ((a > b) ? (a) : (b))

#define min(a,b) ((a < b) ? (a) : (b))

#define clamp(maxi,mini,val) max(min(val,maxi),mini)

#define GET_TAB_LEN(tab) sizeof(tab)/sizeof(tab[0])

//only used with tables
#define ADD_TAB_STR(str,tab,i) strncat(str+i,tab,GET_TAB_LEN(tab)); i += GET_TAB_LEN(tab) - 1;

#define UNUSED_PARAMETER(x) (void)(x)

VkBool32 debugReportCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT           messageSeverity,
    VkDebugUtilsMessageTypeFlagsEXT                  messageTypes,
    const VkDebugUtilsMessengerCallbackDataEXT*      pCallbackData,
    void*                                            pUserData) {
    UNUSED_PARAMETER(pUserData);
    char msg[1024] = {0};
    uint32_t i = 0;

    if (messageSeverity == 0) {
        // this should not happen
        ADD_TAB_STR(msg,"UNKNOWN",i);
    }
    else {
        if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
            ADD_TAB_STR(msg, "REPORT", i);
        }
        if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
            ADD_TAB_STR(msg, "WARNING", i);
        }
        if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) {
            ADD_TAB_STR(msg, "INFORMATION", i);
        }
        if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
            ADD_TAB_STR(msg, "VERBOSE", i);
        }
    }

    ADD_TAB_STR(msg," TYPE:",i);

    if (messageTypes == 0) {
        // this should not happen
        ADD_TAB_STR(msg," UNKNOWN",i);
    }
    else {
        if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) {
            ADD_TAB_STR(msg, " GENERAL", i);
        }
        if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) {
            ADD_TAB_STR(msg, " VALIDATION", i);
        }
        if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) {
            ADD_TAB_STR(msg, " PERFORMANCE", i);
        }
        if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT) {
            ADD_TAB_STR(msg, " BINDING", i);
        }
    }

    if (pCallbackData->pObjects == NULL) {
        fprintf(stderr, "%s, OBJECT: %s, obj_addr: %p, code: %s, message: %s\n",
        msg,
        "UNKNOWN",
        (void*)0,
        pCallbackData->pMessageIdName,
        pCallbackData->pMessage);
        return 0;
    }
    fprintf(stderr, "%s, OBJECT: %s, obj_addr: %p, code: %s, message: %s\n",
        msg,
        string_VkObjectType(pCallbackData->pObjects->objectType),
        (void*)pCallbackData->pObjects->objectHandle,
        pCallbackData->pMessageIdName,
        pCallbackData->pMessage);

    return 0;
}

void MakeNumberApiVersion(const uint32_t version, uint32_t *major, uint32_t *minor, uint32_t *patch, uint32_t *variant) {
    *major = VK_API_VERSION_MAJOR(version);
    *minor = VK_API_VERSION_MINOR(version);
    *patch = VK_API_VERSION_PATCH(version);
    *variant = VK_API_VERSION_VARIANT(version);
}

#define APP_VERSION_MAX 25

static const char* const app_name = "testvk";
static const uint32_t app_version_major = 0;
static const uint32_t app_version_minor = 0;
static const uint32_t app_version_patch = 0;
static const uint32_t app_version_unused = 0;
uint32_t app_version = 0;
char app_version_str[APP_VERSION_MAX] = {0};
static const char* const app_id = "org.idezu.testvk";
static const uint32_t default_window_width = 1000;
static const uint32_t default_window_height = 700;


int main(void) {

    VkBool32 debug_report_callback_destroyed = VK_FALSE;

    VkResult result = VK_ERROR_UNKNOWN;

    /////////////////////////
    //APP METADATA CREATION//
    /////////////////////////

    app_version = VK_MAKE_API_VERSION(app_version_unused, app_version_major, app_version_minor, app_version_patch);
    snprintf(app_version_str,APP_VERSION_MAX,"%u.%u.%u variant %u",app_version_major,app_version_minor,app_version_patch,app_version_unused);

    //////////////////////
    //SDL INITIALISATION//
    //////////////////////

    if (!SDL_SetAppMetadata(app_name,app_version_str,app_id)) {
        printf("failed to set app metadata: %s\n", SDL_GetError());
        return -1;
    }

    if (!SDL_Init(SDL_INIT_VIDEO)) {
        printf("failed to initialize SDL:%s\n", SDL_GetError());
        return -1;
    }

    ///////////////////
    //WINDOW CREATION//
    ///////////////////

    SDL_Window* window = SDL_CreateWindow("testvk", (int)default_window_width,(int)default_window_height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN);

    if (window == NULL) {
        printf("failed to create window: %s", SDL_GetError());
        goto UNINIT_SDL;
    }

    /////////////////////
    //INSTANCE CREATION//
    /////////////////////


    uint32_t api_version = 0;
    result = vkEnumerateInstanceVersion(&api_version);
    if (result < VK_SUCCESS) {
        printf("vkEnumerateInstanceVersion error: %s\n", string_VkResult(result));
        goto DESTROY_WINDOW;
    }

    uint32_t major = 0;
    uint32_t minor = 0;
    uint32_t patch = 0;
    uint32_t variant = 0;

    MakeNumberApiVersion(api_version, &major, &minor, &patch, &variant);

    printf("loader API version: %u.%u.%u variant %u\n",major,minor,patch,variant);

    const VkApplicationInfo app_info = {
        .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
        .pApplicationName = app_name,
        .applicationVersion = app_version,
        .pEngineName = app_id,
        .engineVersion = app_version,
        .apiVersion = api_version
    };

    uint32_t sdl_extension_count = 0;
    const char* const* sdl_extensions = SDL_Vulkan_GetInstanceExtensions(&sdl_extension_count);

    const char* test_vk_extensions[]= {
        VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
    };

    uint32_t instance_extension_count = sdl_extension_count+GET_TAB_LEN(test_vk_extensions);
    const char* *instance_extensions = calloc(instance_extension_count,sizeof(char*));
    if (instance_extensions == NULL) {
        goto DESTROY_WINDOW;
    }

    memcpy(instance_extensions,test_vk_extensions,GET_TAB_LEN(test_vk_extensions)*sizeof(char*));
    memcpy(instance_extensions+GET_TAB_LEN(test_vk_extensions),sdl_extensions,sdl_extension_count*sizeof(char*));

    const char* instance_layer[]= {
        "VK_LAYER_KHRONOS_validation",
        "VK_LAYER_LUNARG_crash_diagnostic",
        //"VK_LAYER_LUNARG_api_dump",
    };

    VkDebugUtilsMessengerCreateInfoEXT debug_utils_messenger_create_info_ext = {
        .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
        .pfnUserCallback = debugReportCallback,
        .pUserData = NULL,
        .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
        | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
        // | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
        // | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
        ,
        .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
        //| VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT
        | VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
        | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT
        ,
    };

    const VkInstanceCreateInfo info = {
        .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        .pNext = &debug_utils_messenger_create_info_ext,
        .enabledExtensionCount = instance_extension_count,
        .ppEnabledExtensionNames = instance_extensions,
        .enabledLayerCount = GET_TAB_LEN(instance_layer),
        .ppEnabledLayerNames = instance_layer,
        .pApplicationInfo = &app_info,
    };

    VkInstance instance;

    result = vkCreateInstance(&info, NULL, &instance);
    if (result < VK_SUCCESS) {
        printf("vkCreateInstance error: %s\n", string_VkResult(result));
        goto DESTROY_WINDOW; // memory leak of instance_extensions ok because we have an error
    }

    free(instance_extensions);

    //SETTING UP FUNCTION POINTERS

    PFN_vkDestroyDebugUtilsMessengerEXT pfn_vk_destroy_debug_utils_messenger_ext = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    PFN_vkCreateDebugUtilsMessengerEXT pfn_vk_create_debug_report_callback_ext = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");

    /////////////////////////
    //DEBUG REPORT CREATION//
    /////////////////////////


    VkDebugUtilsMessengerEXT debug_report_callback;
    result = pfn_vk_create_debug_report_callback_ext(instance, &debug_utils_messenger_create_info_ext, NULL, &debug_report_callback);
    if (result < VK_SUCCESS) {
        printf("vkCreateDebugUtilsMessengerEXT error: %s\n", string_VkResult(result));
        goto DESTROY_INSTANCE;
    }

    //////////////////////////////
    //CHOOSING A PHYSICAL DEVICE//
    //////////////////////////////

    uint32_t pd_count = 0;
    result = vkEnumeratePhysicalDevices(instance, &pd_count, NULL);
    if (result < VK_SUCCESS) {
        printf("vkEnumeratePhysicalDevices error: %s\n", string_VkResult(result));
        goto DESTROY_DEBUG_CALLBACK;
    }
    VkPhysicalDevice physical_device = VK_NULL_HANDLE;
    VkPhysicalDevice* pds = calloc(pd_count,sizeof(VkPhysicalDevice));
    {
        result = vkEnumeratePhysicalDevices(instance, &pd_count, pds);
        if (result < VK_SUCCESS) {
            printf("vkEnumeratePhysicalDevices error: %s\n", string_VkResult(result));
            //memory leak but ok because error
            goto DESTROY_DEBUG_CALLBACK;
        }
        printf("here are the devices connected to this PC:\n");
        for (size_t i = 0; i < pd_count; i++) {
            VkPhysicalDeviceProperties2 pdp = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
            vkGetPhysicalDeviceProperties2(pds[i],&pdp);
            uint32_t api_major = 0;
            uint32_t api_minor = 0;
            uint32_t api_patch = 0;
            uint32_t api_variant = 0;
            VkPhysicalDeviceProperties p = pdp.properties;
            MakeNumberApiVersion(p.apiVersion,&api_major,&api_minor,&api_patch,&api_variant);
            printf("device: %s, id:%i, vendor version: %u, api version: %u.%u.%u variant %u\n",p.deviceName,p.deviceID,p.driverVersion,api_major,api_minor,api_patch,api_variant);
        }
        physical_device = *pds;
    }
    free(pds);
    pds = NULL;

    ////////////////////
    //SURFACE CREATION//
    ////////////////////

    VkSurfaceKHR surface = VK_NULL_HANDLE;

    if (!SDL_Vulkan_CreateSurface(window,instance,NULL,&surface)) {
        printf("SDL_Vulkan_CreateSurface error: %s\n", SDL_GetError());
        goto DESTROY_DEBUG_CALLBACK;
    }

    ////////////////////
    //CHOOSING A QUEUE//
    ////////////////////

    uint32_t queue_family_count = 0;

    vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_count, NULL);

    VkQueueFamilyProperties2* queue_family_properties2 = calloc(queue_family_count,sizeof(VkQueueFamilyProperties2));
    for (size_t i = 0; i < queue_family_count; i++) {
        queue_family_properties2[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
    }

    vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_count, queue_family_properties2);

    uint32_t graphic_queue_family_index = UINT32_MAX;
    uint32_t present_queue_family_index = UINT32_MAX;

    for (uint32_t i = 0; i < queue_family_count; i++) {
        VkQueueFamilyProperties queue_family_properties = queue_family_properties2[i].queueFamilyProperties;
        if (queue_family_properties.queueFlags & VK_QUEUE_GRAPHICS_BIT && graphic_queue_family_index == UINT32_MAX) {
            graphic_queue_family_index = i;
        }

        if (present_queue_family_index == UINT32_MAX) {
            VkBool32 present_support = VK_FALSE;
            vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, i, surface, &present_support);
            if (present_support) {
                present_queue_family_index = i;
            }
        }

        if (graphic_queue_family_index != UINT32_MAX &&  present_queue_family_index != UINT32_MAX) {
            break;
        }
    }

    if (graphic_queue_family_index==UINT32_MAX || present_queue_family_index==UINT32_MAX) {
        printf("failed to find required queues\n");
        goto DESTROY_DEBUG_CALLBACK;
    }

    VkDeviceQueueGlobalPriorityCreateInfo device_queue_global_priority_create_info = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO,
        .pNext = NULL,
        .globalPriority = VK_QUEUE_GLOBAL_PRIORITY_HIGH_EXT,
    };

    uint32_t queue_info_count = 0;
    VkDeviceQueueCreateInfo* queue_infos;
    float* queue_priorities;

    if (graphic_queue_family_index == present_queue_family_index) {
        queue_info_count = 1;
        queue_infos = calloc(queue_info_count,sizeof(VkDeviceQueueCreateInfo));
        if (queue_infos == NULL) {
            printf("failed to allocate memory for graphics queue infos\n");
            goto DESTROY_DEBUG_CALLBACK;
        }
        queue_priorities = calloc(2,sizeof(float));
        if (queue_priorities == NULL) {
            printf("failed to allocate memory for graphics queue priorities\n");
            goto DESTROY_DEBUG_CALLBACK;
        }
        queue_priorities[0] = 1.0f;
        queue_priorities[1] = 1.0f;
        queue_infos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queue_infos[0].pNext = &device_queue_global_priority_create_info;
        queue_infos[0].queueFamilyIndex = graphic_queue_family_index;
        queue_infos[0].queueCount = 2;
        queue_infos[0].pQueuePriorities = queue_priorities;
    }
    else {
        queue_info_count = 2;
        queue_infos = calloc(queue_info_count,sizeof(VkDeviceQueueCreateInfo));
        if (queue_infos == NULL) {
            printf("failed to allocate memory for graphics queue infos\n");
            goto DESTROY_DEBUG_CALLBACK;
        }
        queue_priorities = calloc(1,sizeof(float));
        if (queue_priorities == NULL) {
            printf("failed to allocate memory for graphics queue priorities\n");
            goto DESTROY_DEBUG_CALLBACK;
        }
        queue_priorities[0] = 1.0f;
        uint32_t queue_family_index[] = {graphic_queue_family_index,present_queue_family_index};
        for (size_t i = 0; i < queue_family_count; i++) {
            queue_infos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
            queue_infos[i].pNext = &device_queue_global_priority_create_info;
            queue_infos[i].queueFamilyIndex = queue_family_index[i];
            queue_infos[i].queueCount = 1;
            queue_infos[i].pQueuePriorities = queue_priorities;
        }
    }

    ///////////////////
    //DEVICE CREATION//
    ///////////////////

    const char* device_extensions[] = {
        VK_KHR_SWAPCHAIN_EXTENSION_NAME,
        VK_EXT_DEVICE_ADDRESS_BINDING_REPORT_EXTENSION_NAME,
        VK_EXT_PAGEABLE_DEVICE_LOCAL_MEMORY_EXTENSION_NAME,
        VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME
    };

    uint32_t available_device_extension_count = 0;
    result = vkEnumerateDeviceExtensionProperties(physical_device,NULL,&available_device_extension_count,NULL);
    if (result < VK_SUCCESS) {
        printf("vkEnumerateDeviceExtensionProperties error: %s\n", string_VkResult(result));
        goto DESTROY_SURFACE;
    }
    VkExtensionProperties* available_device_extensions = calloc(available_device_extension_count,sizeof(VkExtensionProperties));
    if (available_device_extensions == NULL) {
        printf("failed to allocate device extension properties\n");
        goto DESTROY_SURFACE;
    }
    result = vkEnumerateDeviceExtensionProperties(physical_device,NULL,&available_device_extension_count,available_device_extensions);
    if (result < VK_SUCCESS) {
        printf("vkEnumerateDeviceExtensionProperties error: %s\n", string_VkResult(result));
        goto DESTROY_SURFACE;
    }

    for (size_t i = 0; i < GET_TAB_LEN(device_extensions); i++) {
        VkBool32 found_extension = VK_FALSE;
        for (size_t k = 0; k < available_device_extension_count; k++) {
            if (strcmp(available_device_extensions[k].extensionName, device_extensions[i]) == 0) {
                found_extension = VK_TRUE;
            }
        }
        if (!found_extension) {
            printf("failed to find required extension: %s\n", device_extensions[i]);
            goto DESTROY_SURFACE;
        }
    }

    VkPhysicalDeviceFeatures physical_device_features = {0};

    vkGetPhysicalDeviceFeatures(physical_device,&physical_device_features);

    physical_device_features.robustBufferAccess = VK_FALSE;

    VkPhysicalDeviceVulkan13Features physical_device_vulkan13_features = {
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
        .dynamicRendering = VK_TRUE,
        .pipelineCreationCacheControl = VK_TRUE,
    };

    VkDevicePipelineBinaryInternalCacheControlKHR pipeline_binary_internal_cache_control = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_PIPELINE_BINARY_INTERNAL_CACHE_CONTROL_KHR,
        .pNext = &physical_device_vulkan13_features,
        .disableInternalCache = VK_FALSE,
    };

    VkDeviceCreateInfo device_info = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .pNext = &pipeline_binary_internal_cache_control,
        .enabledExtensionCount = GET_TAB_LEN(device_extensions),
        .ppEnabledExtensionNames = device_extensions,
        .pEnabledFeatures = &physical_device_features,
        .queueCreateInfoCount = queue_info_count,
        .pQueueCreateInfos = queue_infos,
    };

    VkDevice device;

    result = vkCreateDevice(physical_device, &device_info, NULL, &device);
    if (result < VK_SUCCESS) {
        printf("vkCreateDevice error: %s\n", string_VkResult(result));
        goto DESTROY_SURFACE;
    }

    free(queue_infos);
    queue_infos = NULL;
    free(queue_priorities);
    queue_priorities = NULL;

    pfn_vk_destroy_debug_utils_messenger_ext(instance, debug_report_callback, NULL);

    debug_report_callback_destroyed = VK_TRUE;

    debug_utils_messenger_create_info_ext.messageType |= VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT;
    result = pfn_vk_create_debug_report_callback_ext(instance, &debug_utils_messenger_create_info_ext, NULL, &debug_report_callback);
    if (result < VK_SUCCESS) {
        printf("vkCreateDebugUtilsMessengerEXT error: %s\n", string_VkResult(result));
        goto DESTROY_DEVICE;
    }

    debug_report_callback_destroyed = VK_FALSE;

    //////////////////////
    //SWAPCHAIN CREATION//
    //////////////////////

    VkSurfaceCapabilitiesKHR surface_capabilities = {0};
    result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device,surface,&surface_capabilities);
    if (result < VK_SUCCESS) {
        printf("vkGetPhysicalDeviceSurfaceCapabilitiesKHR error: %s\n", string_VkResult(result));
        goto DESTROY_DEVICE;
    }

    VkExtent2D swapchain_extent;
    if (surface_capabilities.currentExtent.width == UINT32_MAX || surface_capabilities.currentExtent.height == UINT32_MAX) {
        uint32_t window_width;
        uint32_t window_height;
        if (!SDL_GetWindowSize(window,(int*)&window_width,(int*)&window_height)) {
            printf("SDL_GetWindowSize error: %s\n", SDL_GetError());
            goto DESTROY_DEVICE;
        }
        swapchain_extent.width = clamp(surface_capabilities.maxImageExtent.width,surface_capabilities.minImageExtent.width,window_width);
        swapchain_extent.height = clamp(surface_capabilities.maxImageExtent.height,surface_capabilities.minImageExtent.height,window_height);
    }
    else {
        swapchain_extent = surface_capabilities.currentExtent;
    }

    uint32_t available_surface_format_count = 0;
    result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device,surface,&available_surface_format_count,NULL);
    if (result < VK_SUCCESS) {
        printf("vkGetPhysicalDeviceSurfaceFormatsKHR error: %s\n", string_VkResult(result));
        goto DESTROY_DEVICE;
    }
    VkSurfaceFormatKHR* available_surface_formats = calloc(available_surface_format_count,sizeof(VkSurfaceFormatKHR));
    if (available_surface_formats == NULL) {
        printf("failed to allocated space for swapchain's surface formats\n");
        goto DESTROY_DEVICE;
    }
    result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device,surface,&available_surface_format_count,available_surface_formats);
    if (result < VK_SUCCESS) {
        printf("vkGetPhysicalDeviceSurfaceFormatsKHR error: %s\n", string_VkResult(result));
        goto DESTROY_DEVICE; // memory leak on swapchain_surface_formats which is fine because it is an error
    }

    VkSurfaceFormatKHR swapchain_surface_format = available_surface_formats[0];
    for (uint32_t i = 0; i < available_surface_format_count; i++) {
        if (available_surface_formats[i].format == VK_FORMAT_B8G8R8A8_SRGB && available_surface_formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR ) {
            swapchain_surface_format = available_surface_formats[i];
            break;
        }
    }

    free(available_surface_formats);
    available_surface_formats = NULL;

    uint32_t available_present_mode_count = 0;
    result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device,surface,&available_present_mode_count,NULL);
    if (result < VK_SUCCESS) {
        printf("vkGetPhysicalDeviceSurfacePresentModesKHR error: %s\n", string_VkResult(result));
        goto DESTROY_DEVICE;
    }
    VkPresentModeKHR* available_present_modes = calloc(available_present_mode_count,sizeof(VkPresentModeKHR));
    if (available_present_modes == NULL) {
        printf("failed to allocated space for swapchain's presentation modes\n");
        goto DESTROY_DEVICE;
    }
    result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device,surface,&available_present_mode_count,available_present_modes);
    if (result < VK_SUCCESS) {
        printf("vkGetPhysicalDeviceSurfacePresentModesKHR error: %s\n", string_VkResult(result));
        goto DESTROY_DEVICE; // memory leak on swapchain_presentation_modes which is fine because it is an error
    }

    VkPresentModeKHR swapchain_presentation_mode = VK_PRESENT_MODE_FIFO_KHR; // default because available everywhere
    for (uint32_t i = 0; i < available_present_mode_count; i++) {
        if (available_present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
            swapchain_presentation_mode = available_present_modes[i];
            break;
        }
    }

    free(available_present_modes);
    available_present_modes = NULL;

    uint32_t queueFamilyIndices[] = {graphic_queue_family_index, present_queue_family_index};

    VkFormatProperties format_props;
    vkGetPhysicalDeviceFormatProperties(physical_device, swapchain_surface_format.format, &format_props);
    if (!(format_props.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) {
        printf("Format not suitable as color attachment\n");
    }

    uint32_t minImageCount = surface_capabilities.minImageCount+1;

    if (minImageCount > surface_capabilities.maxImageCount && surface_capabilities.maxImageCount != 0) {
        minImageCount = surface_capabilities.maxImageCount;
    }

    if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) {
        printf("VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT not supported by surface\n");
    }
    if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) {
        printf("VK_IMAGE_USAGE_TRANSFER_DST_BIT not supported by surface\n");
    }

    VkSwapchainCreateInfoKHR swapchain_info = {
        .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
        .surface = surface,
        .imageExtent = swapchain_extent,
        .imageFormat = swapchain_surface_format.format,
        .presentMode = swapchain_presentation_mode,
        .minImageCount = minImageCount,
        .imageColorSpace = swapchain_surface_format.colorSpace,
        .clipped = VK_TRUE,
        .preTransform = surface_capabilities.currentTransform,
        .imageArrayLayers = 1,
        .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
        .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
        .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
    };

    if (graphic_queue_family_index != present_queue_family_index) {
        swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
        swapchain_info.queueFamilyIndexCount = 2;
        swapchain_info.pQueueFamilyIndices = queueFamilyIndices ;
    }

    VkSwapchainKHR swapchain = VK_NULL_HANDLE;

    result = vkCreateSwapchainKHR(device, &swapchain_info, NULL, &swapchain);
    if (result < VK_SUCCESS) {
        printf("vkCreateSwapchainKHR error: %s\n", string_VkResult(result));
        goto DESTROY_DEVICE;
    }

    uint32_t swapchain_image_count = 0;
    result = vkGetSwapchainImagesKHR(device, swapchain, &swapchain_image_count, NULL);
    if (result < VK_SUCCESS) {
        printf("vkGetSwapchainImagesKHR error: %s\n", string_VkResult(result));
        goto DESTROY_SWAPCHAIN;
    }
    VkImage* swapchain_images = calloc(swapchain_image_count, sizeof(VkImage));
    if (swapchain_images == NULL) {
        printf("failed to allocate swapchain images\n");
        goto DESTROY_SWAPCHAIN;
    }
    result = vkGetSwapchainImagesKHR(device, swapchain, &swapchain_image_count, swapchain_images);
    if (result < VK_SUCCESS) {
        printf("vkGetSwapchainImagesKHR error: %s\n", string_VkResult(result));
        goto DESTROY_SWAPCHAIN;
    }

    ///////////////
    //IMAGE VIEWS//
    ///////////////

    VkImageView* swapchain_image_views = malloc(sizeof(VkImageView) * swapchain_image_count);

    VkImageViewCreateInfo swapchain_image_views_info = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
        .viewType = VK_IMAGE_VIEW_TYPE_2D,
        .format = swapchain_surface_format.format,
        .subresourceRange = {
            VK_IMAGE_ASPECT_COLOR_BIT,
            0,
            1,
            0,
            1
        },
        .components = {
            VK_COMPONENT_SWIZZLE_IDENTITY,
            VK_COMPONENT_SWIZZLE_IDENTITY,
            VK_COMPONENT_SWIZZLE_IDENTITY,
            VK_COMPONENT_SWIZZLE_IDENTITY
        },
    };

    for (uint32_t i = 0; i < swapchain_image_count; i++) {
        swapchain_image_views_info.image = swapchain_images[i];
        result = vkCreateImageView(device, &swapchain_image_views_info, NULL, &swapchain_image_views[i]);
        if (result < VK_SUCCESS) {
            printf("vkCreateImageView error: %s\n", string_VkResult(result));
            goto DESTROY_SWAPCHAIN;
        }
    }

    /////////////////////
    //GRAPHICS PIPELINE//
    /////////////////////

    //shader file

    FILE* shader_file = fopen("shaders/shader.spv", "rb");
    if (shader_file == NULL) {
        printf("failed to open shader.spv\n");
        goto DESTROY_IMAGE_VIEWS;
    }

    fseek(shader_file, 0, SEEK_END);
    int shader_file_size = ftell(shader_file);
    if (shader_file_size == 0) {
        printf("failed to seek to end of shader file\n");
        goto DESTROY_IMAGE_VIEWS;
    }
    fseek(shader_file, 0, SEEK_SET);

    const uint32_t* shader_buffer = calloc(shader_file_size, 1);
    if (shader_buffer == NULL) {
        printf("failed to allocate shader buffer\n");
        goto DESTROY_IMAGE_VIEWS;
    }

    fread((void*)shader_buffer, sizeof(char), shader_file_size, shader_file);

    fclose(shader_file);

    VkShaderModuleCreateInfo shader_module_info = {
        .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
        .codeSize = shader_file_size*sizeof(char),
        .pCode = shader_buffer,
    };

    VkShaderModule shader_module;

    result = vkCreateShaderModule(device, &shader_module_info, NULL, &shader_module);
    if (result < VK_SUCCESS) {
        printf("vkCreateShaderModule error: %s\n", string_VkResult(result));
        goto DESTROY_IMAGE_VIEWS;
    }

    free((void*)shader_buffer);

    VkPipelineShaderStageCreateInfo vertex_module_stage_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_VERTEX_BIT,
        .module = shader_module,
        .pName = "vertMain",
    };

    VkPipelineShaderStageCreateInfo fragment_module_stage_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
        .module = shader_module,
        .pName = "fragMain",
    };

    VkPipelineShaderStageCreateInfo shader_stages[] = {vertex_module_stage_info, fragment_module_stage_info};

    VkDynamicState dynamic_states[] = {
        VK_DYNAMIC_STATE_VIEWPORT,
        VK_DYNAMIC_STATE_SCISSOR
    };

    VkPipelineDynamicStateCreateInfo dynamic_state_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
        .dynamicStateCount = GET_TAB_LEN(dynamic_states),
        .pDynamicStates = dynamic_states,
    };

    VkPipelineVertexInputStateCreateInfo vertex_input_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,

    };

    VkPipelineInputAssemblyStateCreateInfo input_assembly_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
        .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
    };

    VkPipelineViewportStateCreateInfo viewport_state_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
        .viewportCount = 1,
        .scissorCount = 1,
    };

    VkPipelineRasterizationStateCreateInfo rasterization_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
        .depthClampEnable = VK_FALSE,
        .rasterizerDiscardEnable = VK_FALSE,
        .polygonMode = VK_POLYGON_MODE_FILL,
        .cullMode = VK_CULL_MODE_BACK_BIT,
        .frontFace = VK_FRONT_FACE_CLOCKWISE,
        .depthBiasEnable = VK_FALSE,
        .depthBiasSlopeFactor = 1.0f,
        .lineWidth = 1.0f,
    };

    VkPipelineMultisampleStateCreateInfo multisample_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
        .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
        .sampleShadingEnable = VK_FALSE,
    };

    VkPipelineColorBlendAttachmentState color_blend_attachment_state = {
        .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
        .blendEnable = VK_TRUE,
        .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,
    };

    VkPipelineColorBlendStateCreateInfo color_blend_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
        .logicOpEnable = VK_FALSE,
        .logicOp = VK_LOGIC_OP_COPY,
        .attachmentCount = 1,
        .pAttachments = &color_blend_attachment_state,
    };

    VkPipelineLayout pipeline_layout;

    VkPipelineLayoutCreateInfo pipeline_layout_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
        .setLayoutCount = 0,
        .pushConstantRangeCount = 0,
    };

    result = vkCreatePipelineLayout(device, &pipeline_layout_info, NULL, &pipeline_layout);
    if (result < VK_SUCCESS) {
        printf("failed to create pipeline layout\n");
        goto DESTROY_IMAGE_VIEWS;
    }

    VkPipelineRenderingCreateInfo rendering_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
        .colorAttachmentCount = 1,
        .pColorAttachmentFormats = &swapchain_surface_format.format,
    };

    VkGraphicsPipelineCreateInfo graphics_pipeline_info = {
        .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
        .pNext = &rendering_info,
        .stageCount = 2,
        .pStages = shader_stages,
        .pVertexInputState = &vertex_input_info,
        .pInputAssemblyState = &input_assembly_info,
        .pViewportState = &viewport_state_info,
        .pRasterizationState = &rasterization_info,
        .pMultisampleState = &multisample_info,
        .pColorBlendState = &color_blend_info,
        .pDynamicState = &dynamic_state_info,
        .layout = pipeline_layout,
        .renderPass = NULL,
    };

    VkPipelineCacheCreateInfo pipeline_cache_info = {
        .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
        .flags = VK_PIPELINE_CACHE_CREATE_EXTERNALLY_SYNCHRONIZED_BIT_EXT,
    };

    VkPipelineCache pipeline_cache;
    result = vkCreatePipelineCache(device, &pipeline_cache_info, NULL, &pipeline_cache);
    if (result < VK_SUCCESS) {
        printf("failed to create pipeline cache\n");
        goto DESTROY_IMAGE_VIEWS;
    }

    VkPipeline graphics_pipeline;
    result = vkCreateGraphicsPipelines(device, pipeline_cache, 1, &graphics_pipeline_info, NULL, &graphics_pipeline);
    if (result < VK_SUCCESS) {
        printf("failed to create graphics pipeline\n");
        goto DESTROY_PIPELINE_CACHE;
    }

    vkDestroyPipelineLayout(device, pipeline_layout, NULL);
    vkDestroyShaderModule(device, shader_module, NULL);

    /////////////
    //MAIN LOOP//
    /////////////

    while (1) {

        //get event
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
                    goto CLEANUP;
                default:
                    continue;
            }
        }
    }

CLEANUP:

    ///////////
    //CLEANUP//
    ///////////

DESTROY_PIPELINE_CACHE:
    vkDestroyPipelineCache(device, pipeline_cache, NULL);

DESTROY_PIPELINE:
    vkDestroyPipeline(device, graphics_pipeline, NULL);

DESTROY_IMAGE_VIEWS:
    for (uint32_t i = 0; i < swapchain_image_count; i++) {
        vkDestroyImageView(device, swapchain_image_views[i], NULL);
    }
    free(swapchain_image_views);
    swapchain_image_views = NULL;

DESTROY_SWAPCHAIN:
    vkDestroySwapchainKHR(device,swapchain,NULL);

DESTROY_DEVICE:
    vkDestroyDevice(device, NULL);

DESTROY_SURFACE:
    vkDestroySurfaceKHR(instance, surface, NULL);

DESTROY_DEBUG_CALLBACK:
    if (!debug_report_callback_destroyed)
        pfn_vk_destroy_debug_utils_messenger_ext(instance, debug_report_callback, NULL);

DESTROY_INSTANCE:
    vkDestroyInstance(instance, NULL);

DESTROY_WINDOW:
    SDL_DestroyWindow(window);

UNINIT_SDL:
    SDL_Quit();

    return result;
}