#include "PathUtils.h"
#include "Utils.h"
#include <vulkan/vulkan.h>
#include "descriptorSethandling.h"
#include<iostream>
#include<tuple> 


struct Spotlight
{
    public:
    float fov = 60;
    glm::vec4 position;
    glm::vec4 direction;
    glm::vec4 parameters = glm::vec4(0.09, 0.032, glm::cos(glm::radians(10.0f)), glm::cos(glm::radians(25.0f))); // x = light.linAttenFact y= light.quadrAttenFact, z = light.cutOff, w = light.outerCutOff
    glm::vec4 up;
    void update(glm::vec4 inputPos, glm::vec4 inputDirection, glm::vec4 inputUp)
    {
        position = inputPos;
        direction = inputDirection;
        up = inputUp;
    }
};

class ShadowMap: public std::enable_shared_from_this<ShadowMap>
{
    public:
    int SHADOW_MAP_WIDTH = 800;
    int SHADOW_MAP_HEIGHT = 600;
    float m_near_plane = 0.001;
    float m_far_plane = 20;
    uint32_t m_descriptorBinding = 4;
    VkDevice& m_device;
    VkPhysicalDevice& m_physical_device;
    int32_t m_queue_family_index;
    VkQueue& m_queue;
    VkImage m_image;
    VkDeviceMemory m_image_memory;
    VkImageView m_image_view;
    VkSampler m_image_sampler;
    VkRenderPass m_render_pass;
    VkFramebuffer m_frame_buffer;
    VkBuffer m_uniform_buffer;
    VkCommandPool m_command_pool;
    VkCommandBuffer m_command_buffer;
    VkPipeline m_pipeline;             
    VkPipelineLayout m_pipeline_layout;
    VkFence m_fence_shadow_mapping;

    ShadowMap(VkPhysicalDevice& physicalDevice, VkDevice& device, uint32_t& queueFamilyIndex, VkQueue& queue, VkDescriptorSetLayout& descrSetLayoutForDrawing, VkDescriptorPool& ext_descriptor_pool):
        m_physical_device(physicalDevice),
        m_device(device), 
        m_queue_family_index(queueFamilyIndex),
        m_queue(queue)
        {
            createImageAndImageView(device);
            createSampler();
            createRenderPass(device);
            createFrameBuffer(device);
            createCommandPoolAndBuffer(device);
            createPipeline(descrSetLayoutForDrawing);
            createFence();
        }
    
        // Helper functions for constructor
        void createImageAndImageView(VkDevice& device)
        {
            // Create Image for depth buffer
            VkImageCreateInfo image_info = {};
            image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
            image_info.pNext = NULL;
            image_info.imageType = VK_IMAGE_TYPE_2D;
            image_info.format = VK_FORMAT_D32_SFLOAT;
            image_info.extent.width = SHADOW_MAP_WIDTH;
            image_info.extent.height = SHADOW_MAP_HEIGHT;
            image_info.extent.depth = 1;
            image_info.mipLevels = 1;
            image_info.arrayLayers = 1;
            image_info.samples = VK_SAMPLE_COUNT_1_BIT;
            image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
            image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
            image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
            image_info.queueFamilyIndexCount = 0;
            image_info.pQueueFamilyIndices = NULL;
            image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
            image_info.flags = 0;
            vkCreateImage(device, &image_info, NULL, &m_image);
            
            // Get memory requirement of the image
            VkMemoryRequirements memRequirements;
            vkGetImageMemoryRequirements(m_device, m_image, &memRequirements);

            // Fine requiered memory properties         
            VkPhysicalDeviceMemoryProperties memProperties; // Has two attributes: memoryTypes (types in every heap) and memoryHeaps (E.g. VRAM and RAM)
            vkGetPhysicalDeviceMemoryProperties(m_physical_device, &memProperties);

            // Find requiered memory type
            uint32_t typeFilter = memRequirements.memoryTypeBits;
            VkMemoryPropertyFlags properties = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
            uint32_t memoryTypeIndex;
            for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) 
            {
                if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) 
                {
                    memoryTypeIndex = i;
                    break;
                }
            }

            // Allocate memory for the image that has correct properties and type
            VkMemoryAllocateInfo allocInfo{};
            allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
            allocInfo.allocationSize = memRequirements.size;
            allocInfo.memoryTypeIndex = memoryTypeIndex;
            auto result = vkAllocateMemory(m_device, &allocInfo, nullptr, &m_image_memory);
            VKL_CHECK_VULKAN_RESULT(result);

            // Bind image to allocated memory
            vkBindImageMemory(m_device, m_image, m_image_memory, 0);
            
            // Create Image View for image (= depth buffer)
            VkImageViewCreateInfo view_info = {};
            view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
            view_info.pNext = NULL;
            view_info.image = m_image;
            view_info.format = VK_FORMAT_D32_SFLOAT;
            view_info.components.r = VK_COMPONENT_SWIZZLE_R;
            view_info.components.g = VK_COMPONENT_SWIZZLE_G;
            view_info.components.b = VK_COMPONENT_SWIZZLE_B;
            view_info.components.a = VK_COMPONENT_SWIZZLE_A;
            view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
            view_info.subresourceRange.baseMipLevel = 0;
            view_info.subresourceRange.levelCount = 1;
            view_info.subresourceRange.baseArrayLayer = 0;
            view_info.subresourceRange.layerCount = 1;
            view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
            view_info.flags = 0;
            vkCreateImageView(device, &view_info, NULL, &m_image_view);
        }    

        void createSampler()
        {
            // General Sampler Settings
            VkSamplerCreateInfo samplerInfo{};
            samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
            samplerInfo.magFilter = VK_FILTER_LINEAR;
            samplerInfo.minFilter = VK_FILTER_LINEAR;
            samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
            samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
            samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
            samplerInfo.anisotropyEnable = VK_FALSE;
            samplerInfo.unnormalizedCoordinates = VK_FALSE; // use [0, texWidth) and [0, texHeight) or [0,1) coordinates on the texture
            samplerInfo.compareEnable = VK_FALSE; // Mainly for percentage-closer-filtering: Should texels be compared to a value before using smaple?
            samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
            samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
            samplerInfo.mipLodBias = 0.0f;
            samplerInfo.minLod = 0.0f;
            samplerInfo.maxLod = 0.0f;

            // Anisotropic filtering settings: Get the maximum setting from hardware
            VkPhysicalDeviceProperties properties{};
            vkGetPhysicalDeviceProperties(m_physical_device, &properties); // Gets a struct with all options and settings of the hardware
            samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;

            // Create Sampler
            if (vkCreateSampler(m_device, &samplerInfo, nullptr, &m_image_sampler) != VK_SUCCESS)
                throw std::runtime_error("failed to create texture sampler!");
        }

        void createRenderPass(VkDevice& device)
        {
            VkAttachmentDescription attachments[1];

            // Depth attachment (shadow map)
            attachments[0].format = VK_FORMAT_D32_SFLOAT;
            attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
            attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
            attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
            attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
            attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
            attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
            attachments[0].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; //Sascha:  VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
            attachments[0].flags = 0;

            // Attachment references from subpasses
            VkAttachmentReference depth_ref;
            depth_ref.attachment = 0;
            depth_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

            // Subpass 0: shadow map rendering
            VkSubpassDescription subpass[1];
            subpass[0].pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
            subpass[0].flags = 0;
            subpass[0].inputAttachmentCount = 0;
            subpass[0].pInputAttachments = NULL;
            subpass[0].colorAttachmentCount = 0;
            subpass[0].pColorAttachments = NULL;
            subpass[0].pResolveAttachments = NULL;
            subpass[0].pDepthStencilAttachment = &depth_ref;
            subpass[0].preserveAttachmentCount = 0;
            subpass[0].pPreserveAttachments = NULL;

            // Create render pass
            VkRenderPassCreateInfo rp_info;
            rp_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
            rp_info.pNext = NULL;
            rp_info.attachmentCount = 1;
            rp_info.pAttachments = attachments;
            rp_info.subpassCount = 1;
            rp_info.pSubpasses = subpass;
            rp_info.dependencyCount = 0;
            rp_info.pDependencies = NULL;
            rp_info.flags = 0;
            auto result = vkCreateRenderPass(device, &rp_info, NULL, &m_render_pass);
            VKL_CHECK_VULKAN_RESULT(result);
        }

        void createFrameBuffer(VkDevice& device)
        {
            VkFramebufferCreateInfo fb_info;
            fb_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
            fb_info.pNext = NULL;
            fb_info.renderPass = m_render_pass;
            fb_info.attachmentCount = 1;
            fb_info.pAttachments = &m_image_view;
            fb_info.width = SHADOW_MAP_WIDTH;
            fb_info.height = SHADOW_MAP_HEIGHT;
            fb_info.layers = 1;
            fb_info.flags = 0;
            vkCreateFramebuffer(device, &fb_info, NULL, &m_frame_buffer);
        }

        void createCommandPoolAndBuffer(VkDevice& device)
        {
            // Create Command Pool
            VkCommandPoolCreateInfo poolInfo{};
            poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
            poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; // We want to rerecord commands at every frame
            poolInfo.queueFamilyIndex = m_queue_family_index; // we use graphics queue, command pools can only allocate command buffers for a single type of queue           
            auto result = vkCreateCommandPool(m_device, &poolInfo, nullptr, &m_command_pool);
            VKL_CHECK_VULKAN_RESULT(result);
            // Create commandbuffer as primary buffer (Can be submitted to a queue for execution, cannot be called from other command buffers)
            // Secondary buffers can only be called by primary buffers, e.g. to re use commands
            VkCommandBufferAllocateInfo allocInfo{};
            allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
            allocInfo.commandPool = m_command_pool;
            allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; // Primary: 
            allocInfo.commandBufferCount = 1;
            result = vkAllocateCommandBuffers(m_device, &allocInfo, &m_command_buffer);
            VKL_CHECK_VULKAN_RESULT(result);
        }

        // Create pipeline: Actually just need pipeline that writes from vertex shader into z-buffer without frag shader but I am not sure how to configure that
        void createPipeline(VkDescriptorSetLayout descriptor_set_layout_for_drawing)
        {
            // Create Pipline Layout
            std::vector<VkDescriptorSetLayout> descriptorSetLayouts{descriptor_set_layout_for_drawing };
            VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
            pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
            pipelineLayoutInfo.setLayoutCount = descriptorSetLayouts.size(); // We have 1 descriptor set, created in function "createDescriptorSetLayout" above
            pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts.data();
            pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional Pus Constants are faster for small information that is updated frequently
            pipelineLayoutInfo.pPushConstantRanges = nullptr; // Optional
            if (vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr, &m_pipeline_layout) != VK_SUCCESS) throw std::runtime_error("\nERROR: Failed to create pipeline layout!");

            // Read precompiled shader from file
            auto vertShaderCode = readFile("assets/shaders_vk/vertShadow.spv");
            VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);


            // Setup Create info for Vertex Shader Stage from Vertex Shader Module
            VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
            vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
            vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
            vertShaderStageInfo.module = vertShaderModule;
            vertShaderStageInfo.pName = "main"; // Define "Entry Point" allowing to combine multiple frag shaders
            vertShaderStageInfo.pSpecializationInfo = nullptr; // allows to set variables inside the shader

            // Save shader stages in array
            VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo}; // {vertShaderStageInfo, fragShaderStageInfo}

            // Create info dynamic state: about which parts of the pipeline are editable after creating it:
            std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
            VkPipelineDynamicStateCreateInfo dynamicState{};
            dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
            dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
            dynamicState.pDynamicStates = dynamicStates.data();

            // Create Info Vertex input: about format of vertices passed to vertex shader
            std::vector<VkVertexInputBindingDescription> vertexinputBindingDescriptions{{
                VkVertexInputBindingDescription{0u, sizeof(float) * 3, VK_VERTEX_INPUT_RATE_VERTEX },
                VkVertexInputBindingDescription{1u, sizeof(float) * 3, VK_VERTEX_INPUT_RATE_VERTEX},
                VkVertexInputBindingDescription{2u, sizeof(float) * 2, VK_VERTEX_INPUT_RATE_VERTEX},
                VkVertexInputBindingDescription{3u, sizeof(float) * 3, VK_VERTEX_INPUT_RATE_VERTEX}
            }};
            std::vector<VkVertexInputAttributeDescription> vertexInputAttributeDescriptions{{
                VkVertexInputAttributeDescription{0u, 0u, VK_FORMAT_R32G32B32_SFLOAT, 0u},
                VkVertexInputAttributeDescription{1u, 1u, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3},
                VkVertexInputAttributeDescription{2u, 2u, VK_FORMAT_R32G32_SFLOAT, 0u},
                VkVertexInputAttributeDescription{3u, 3u, VK_FORMAT_R32G32_SFLOAT, 0u}
            }};

            VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
            vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
            vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputAttributeDescriptions.size()); //Bindings: spacing between data and whether the data is per - vertex or per - instance(see instancing)
            vertexInputInfo.pVertexBindingDescriptions = vertexinputBindingDescriptions.data();
            vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributeDescriptions.size());
            vertexInputInfo.pVertexAttributeDescriptions = vertexInputAttributeDescriptions.data();

            // Create info for Input Assembly: How to interpret vertices: points, line-list etc.
            VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
            inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
            inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
            inputAssembly.primitiveRestartEnable = VK_FALSE;

            // Create info Viewports and scissors: scissor rectangles define in which regions pixels will actually be stored. 
            // Any pixels outside the scissor rectangles will be discarded
            // Assuming viewport and siccossrs are dynamic state, see above
            VkViewport viewport{};
            viewport.x = 0.0f;
            viewport.y = 0.0f;
            viewport.width = (float)SHADOW_MAP_WIDTH;
            viewport.height = (float)SHADOW_MAP_HEIGHT;
            viewport.minDepth = 0.0f;
            viewport.maxDepth = 1.0f;
            VkRect2D scissor{};
            scissor.offset = { 0, 0 };
            scissor.extent.width = SHADOW_MAP_WIDTH;
            scissor.extent.height = SHADOW_MAP_HEIGHT;
            VkPipelineViewportStateCreateInfo viewportState{};
            viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
            viewportState.viewportCount = 1;
            viewportState.scissorCount = 1;

            // Create Info Rasterizer
            VkPipelineRasterizationStateCreateInfo rasterizer{};
            rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
            rasterizer.depthClampEnable = VK_FALSE; // Clamps fragments outside near/far plane rather than discrding, requires GPU feature enabled
            rasterizer.rasterizerDiscardEnable = VK_FALSE; // Lets no geometry through the rasterizer (disables rendering)
            rasterizer.polygonMode = VK_POLYGON_MODE_FILL; // FILL, LINE or POINT
            rasterizer.lineWidth = 1.0f; // lines wider than 1 require wideLines GPU feature
            rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; // Face Culling: front back or nothing
            rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; // Set up order of vertices to be culled (or VK_FRONT_FACE_CLOCKWISE) because of the Y-flip we did in the projection matrix, the vertices are now being drawn in counter-clockwis
            rasterizer.depthBiasEnable = VK_FALSE; // Manipulate depth value based on fragements slope etc
            rasterizer.depthBiasConstantFactor = 0.0f; // Optional
            rasterizer.depthBiasClamp = 0.0f; // Optional
            rasterizer.depthBiasSlopeFactor = 0.0f; // Optional

            VkPipelineDepthStencilStateCreateInfo depthStencil{};
            depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
            depthStencil.depthTestEnable = VK_TRUE;
            depthStencil.depthWriteEnable = VK_TRUE;
            depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
            depthStencil.depthBoundsTestEnable = VK_FALSE;
            depthStencil.minDepthBounds = 0.0f; // Optional
            depthStencil.maxDepthBounds = 1.0f; // Optional
            depthStencil.stencilTestEnable = VK_FALSE;
            depthStencil.front = {}; // Optional
            depthStencil.back = {}; // Optional

            // Create Info Multisampling
            VkPipelineMultisampleStateCreateInfo multisampling{};
            multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
            multisampling.sampleShadingEnable = VK_FALSE;
            multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
            multisampling.minSampleShading = 1.0f; // Optional
            multisampling.pSampleMask = nullptr; // Optional Is this depth and stencil test?
            multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
            multisampling.alphaToOneEnable = VK_FALSE; // Optional

            // Create Info pipeline
            VkGraphicsPipelineCreateInfo pipelineCreateInfo{};
            pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
            pipelineCreateInfo.stageCount = 1;
            pipelineCreateInfo.pStages = shaderStages;
            pipelineCreateInfo.pVertexInputState = &vertexInputInfo;
            pipelineCreateInfo.pInputAssemblyState = &inputAssembly;
            pipelineCreateInfo.pViewportState = &viewportState;
            pipelineCreateInfo.pRasterizationState = &rasterizer;
            pipelineCreateInfo.pMultisampleState = &multisampling;
            pipelineCreateInfo.pDepthStencilState = &depthStencil; // This is the depth buffer
            pipelineCreateInfo.pColorBlendState = nullptr;      // &colorBlending;
            pipelineCreateInfo.pDynamicState = &dynamicState;
            pipelineCreateInfo.layout = m_pipeline_layout;
            pipelineCreateInfo.renderPass = m_render_pass;
            pipelineCreateInfo.subpass = 0;
            pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional for the case that we inherit from a parent pipeline (requires VK_PIPELINE_CREATE_DERIVATIVE_BIT)
            pipelineCreateInfo.basePipelineIndex = -1; // Optional

            // Create grphics pipeline
            // THis function is meant to create multiple pipelines. 2nd argument is for VkPipelineCache, storing data that is reused for multiple pipline creation
            if (vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &m_pipeline) != VK_SUCCESS) throw std::runtime_error("\nERROR: Failed to create graphics pipeline!");

            // Final Step: Destroy Shader Modules since not needed after pipeline creation
            vkDestroyShaderModule(m_device, vertShaderModule, nullptr);
            //vkDestroyShaderModule(m_device, fragShaderModule, nullptr);
        }

            static std::vector<char> readFile(const std::string& filename)
            {
                // Open file, ate = start reading at end of file (see next comment), binary = read as binary
                std::ifstream file(filename, std::ios::ate | std::ios::binary);

                if (!file.is_open()) throw std::runtime_error("\nERROR: Failed to open file " + filename);

                // Use read position at end to determine size of file to allocate a buffer
                size_t fileSize = (size_t)file.tellg();
                std::vector<char> buffer(fileSize);

                // Jump to start of file and read into buffer
                file.seekg(0);
                file.read(buffer.data(), fileSize);

                file.close();
                if (file.is_open()) throw std::runtime_error("\nERROR: Failed to open file " + filename);
                return buffer;
            }

            VkShaderModule createShaderModule(const std::vector<char>& code)
            {
                // Setup Create Info from input
                VkShaderModuleCreateInfo createInfo{};
                createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
                createInfo.codeSize = code.size();
                createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data()); // code.data is stored in byte (char) but must be uint32

                // Create Shader Module
                VkShaderModule shaderModule;
                if (vkCreateShaderModule(m_device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) throw std::runtime_error("failed to create shader module!");
                return shaderModule;
            }
        
        void createFence()
        {
            //Create Fences
            VkFenceCreateInfo fenceInfo{};
            fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
            fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
            if (vkCreateFence(m_device, &fenceInfo, nullptr, &m_fence_shadow_mapping) != VK_SUCCESS)
                throw std::runtime_error("\nERROR: Failed to create compute Fences for a frame!");
        }
        
        //Helper for destroying vulkan assets
        void destroyVulkanAssets()
        {
            vkDestroyFence(m_device, m_fence_shadow_mapping, nullptr);
            vkDestroyPipeline(m_device, m_pipeline, nullptr);
            vkDestroyPipelineLayout(m_device, m_pipeline_layout, nullptr);
            vklDestroyHostCoherentBufferAndItsBackingMemory(m_uniform_buffer);
            vkDestroyCommandPool(m_device, m_command_pool, nullptr);
            vkDestroyFramebuffer(m_device, m_frame_buffer, nullptr);
            vkDestroyRenderPass(m_device, m_render_pass, nullptr);
            vkDestroyImageView(m_device, m_image_view, nullptr);
            vklDestroyDeviceLocalImageAndItsBackingMemory(m_image);
        }

    // Start renderpass for shadows
    void startRecordingCommandBuffer()
    {
        // Wait until last shadow map is finished
        vkWaitForFences(m_device, 1, &m_fence_shadow_mapping, VK_TRUE, UINT64_MAX);

        // Reset the fence
        vkResetFences(m_device, 1, &m_fence_shadow_mapping);
        
        // Create Info for command record, 
        VkCommandBufferBeginInfo beginInfo{};
        beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        beginInfo.flags = 0;// Optional for info about rerecording
        beginInfo.pInheritanceInfo = nullptr; // Optional for secondary buffer: specifies which state to inherit from the calling primary command buffers
        
        // Start recording (if arecording already started , it is reset)
        vkResetCommandBuffer(m_command_buffer,0);
        auto result = vkBeginCommandBuffer(m_command_buffer, &beginInfo);
        //VKL_CHECK_VULKAN_RESULT(result);

        // Configure a render pass and start it
        VkClearValue clear_values[1];
        clear_values[0].depthStencil.depth = 1.0f;
        clear_values[0].depthStencil.stencil = 0;
        VkRenderPassBeginInfo rp_begin;
        rp_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
        rp_begin.pNext = NULL;
        rp_begin.renderPass = m_render_pass;
        rp_begin.framebuffer = m_frame_buffer;
        rp_begin.renderArea.offset.x = 0;
        rp_begin.renderArea.offset.y = 0;
        rp_begin.renderArea.extent.width = SHADOW_MAP_WIDTH;
        rp_begin.renderArea.extent.height = SHADOW_MAP_HEIGHT;
        rp_begin.clearValueCount = 1;
        rp_begin.pClearValues = clear_values;
        vkCmdBeginRenderPass(m_command_buffer, &rp_begin, VK_SUBPASS_CONTENTS_INLINE);

        // Configure and bind pipeline
        VkViewport viewport;
        viewport.height = SHADOW_MAP_HEIGHT;
        viewport.width = SHADOW_MAP_WIDTH;
        viewport.minDepth = 0.0f;
        viewport.maxDepth = 1.0f;
        viewport.x = 0;
        viewport.y = 0;
        vkCmdSetViewport(m_command_buffer, 0, 1, &viewport);
        VkRect2D scissor;
        scissor.extent.width = SHADOW_MAP_WIDTH;
        scissor.extent.height = SHADOW_MAP_HEIGHT;
        scissor.offset.x = 0;
        scissor.offset.y = 0;
        vkCmdSetScissor(m_command_buffer, 0, 1, &scissor);
        vkCmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline);
    }
    
    // Sets a barrier, end render pass then ends commandbuffer and submits it
    void endRecordingCommandbufferAndSubmitToQueue()
    {
        // End the renderpass
        vkCmdEndRenderPass(m_command_buffer);

        VkImageMemoryBarrier imageMemoryBarrier;
        imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
        imageMemoryBarrier.pNext = NULL;
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
        imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
        imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;// VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
        imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
        imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        imageMemoryBarrier.image = m_image;
        imageMemoryBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
        imageMemoryBarrier.subresourceRange.baseMipLevel = 0;
        imageMemoryBarrier.subresourceRange.levelCount = 1;
        imageMemoryBarrier.subresourceRange.baseArrayLayer = 0;
        imageMemoryBarrier.subresourceRange.layerCount = 1;

        vkCmdPipelineBarrier
        (
            /*commandBuffer;*/              m_command_buffer,
            /*srcStageMask:*/               VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
            /*dstStageMask:*/               VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
            /*dependencyFlags*/             VK_DEPENDENCY_BY_REGION_BIT,
            /*memoryBarrierCount*/          0,
            /*pMemoryBarriers*/             NULL,
            /*bufferMemoryBarrierCount*/    0,
            /*pBufferMemoryBarriers*/       NULL,
            /*imageMemoryBarrierCount*/     1,
            /*pImageMemoryBarriers*/        &imageMemoryBarrier
        );

        // End Command Buffer
        vkEndCommandBuffer(m_command_buffer);

        // Submit to queue
        VkPipelineStageFlags shadow_map_wait_stages = 0;
        VkSubmitInfo submit_info = { };
        submit_info.pNext = NULL;
        submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
        submit_info.commandBufferCount = 1;
        submit_info.pCommandBuffers = &m_command_buffer;
        vkQueueSubmit(m_queue, 1, &submit_info, m_fence_shadow_mapping);
    }
};