/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/

module;

#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
#include "vulkan/vulkan.h"

#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
export module Crafter.Graphics:VulkanBuffer;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
import std;
import :Device;

namespace Crafter {
    export class VulkanBufferBase {
        public:
        VkDeviceAddress address;
        std::uint32_t size;
        VkBuffer buffer = VK_NULL_HANDLE;
        VkDeviceMemory memory;
    };

    export template<typename T>
    class VulkanBufferMapped {
        public:
        T* value;
    };
    export class VulkanBufferMappedEmpty {};
    template<typename T, bool Mapped>
    using VulkanBufferMappedConditional =
        std::conditional_t<
            Mapped,
            VulkanBufferMapped<T>,
            VulkanBufferMappedEmpty
        >;

    export template <typename T, bool Mapped> 
    class VulkanBuffer : public VulkanBufferBase, public VulkanBufferMappedConditional<T, Mapped> {
    public:
        VulkanBuffer() = default;
        void Create(VkBufferUsageFlags2 usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count) {
            size = count * sizeof(T);

            // Carry usage in the maintenance5 flags2 chain so 64-bit bits
            // (e.g. VK_BUFFER_USAGE_2_MEMORY_DECOMPRESSION_BIT_EXT, bit 35)
            // are not truncated.
            VkBufferUsageFlags2CreateInfo usageFlags2 {
                .sType = VK_STRUCTURE_TYPE_BUFFER_USAGE_FLAGS_2_CREATE_INFO,
                .usage = usageFlags,
            };
            VkBufferCreateInfo bufferCreateInfo {};
			bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
            bufferCreateInfo.pNext = &usageFlags2;
            bufferCreateInfo.usage = 0;
			bufferCreateInfo.size = sizeof(T)*count;
            Device::CheckVkResult(vkCreateBuffer(Device::device, &bufferCreateInfo, nullptr, &buffer));
        
            VkMemoryRequirements memReqs;
            vkGetBufferMemoryRequirements(Device::device, buffer, &memReqs);
            VkMemoryAllocateInfo memAlloc {
                .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
                .allocationSize = memReqs.size,
                .memoryTypeIndex = Device::GetMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags)
            };

            VkMemoryAllocateFlagsInfoKHR allocFlagsInfo {
                .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR,
                .flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR,
            };
            memAlloc.pNext = &allocFlagsInfo;
            Device::CheckVkResult(vkAllocateMemory(Device::device, &memAlloc, nullptr, &memory));
        
            Device::CheckVkResult(vkBindBufferMemory(Device::device, buffer, memory, 0));

            VkBufferDeviceAddressInfo addressInfo = {
                .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO,
                .buffer = buffer
            };
            address = vkGetBufferDeviceAddress(Device::device, &addressInfo);

            if constexpr(Mapped) {
                Device::CheckVkResult(vkMapMemory(Device::device, memory, 0, memReqs.size, 0, reinterpret_cast<void**>(&(VulkanBufferMappedConditional<T, true>::value))));
            }
        }

        void Clear() {
            if constexpr(Mapped) {
                vkUnmapMemory(Device::device, memory);
            }
            vkDestroyBuffer(Device::device, buffer, nullptr);
            vkFreeMemory(Device::device, memory, nullptr);
            buffer = VK_NULL_HANDLE;
        }

        void Resize(VkBufferUsageFlags2 usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count) {
            if(buffer != VK_NULL_HANDLE) {
               Clear();
            }
            Create(usageFlags, memoryPropertyFlags, count);
        }

        void Copy(VkCommandBuffer cmd, VulkanBuffer& dst) {
            VkBufferCopy copyRegion = {
                .srcOffset = 0,
                .dstOffset = 0,
                .size = size
            };

            vkCmdCopyBuffer(
                cmd,
                buffer,
                dst.buffer,
                1,
                &copyRegion
            );
        }

        void Copy(VkCommandBuffer cmd, VulkanBuffer& dst, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask) {
            Copy(cmd, dst);

            VkBufferMemoryBarrier barrier = {
                .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
                .srcAccessMask = srcAccessMask,
                .dstAccessMask = dstAccessMask,
                .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
                .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
                .buffer = buffer,
                .offset = 0,
                .size = VK_WHOLE_SIZE
            };

            vkCmdPipelineBarrier(
                cmd,
                srcStageMask,
                dstStageMask,
                0,
                0, NULL,
                1, &barrier,
                0, NULL
            );
        }

        void FlushDevice() requires(Mapped) {
            VkMappedMemoryRange range = {
                .sType  = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
                .memory = memory,
                .offset = 0,
                .size   = VK_WHOLE_SIZE
            };
            vkFlushMappedMemoryRanges(Device::device, 1, &range);
        }

        void FlushDevice(VkCommandBuffer cmd, VkAccessFlags dstAccessMask, VkPipelineStageFlags dstStageMask) requires(Mapped) {
            FlushDevice();
            VkBufferMemoryBarrier barrier = {
                .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
                .srcAccessMask = VK_ACCESS_HOST_WRITE_BIT,
                .dstAccessMask = dstAccessMask,
                .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
                .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
                .buffer = buffer,
                .offset = 0,
                .size = VK_WHOLE_SIZE
            };

            vkCmdPipelineBarrier(
                cmd,
                VK_PIPELINE_STAGE_HOST_BIT,
                dstStageMask,
                0,
                0, NULL,
                1, &barrier,
                0, NULL
            );
        }

        void FlushHost() requires(Mapped) {
            VkMappedMemoryRange range = {
                .sType  = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
                .memory = memory,
                .offset = 0,
                .size   = VK_WHOLE_SIZE
            };
            vkInvalidateMappedMemoryRanges(Device::device, 1, &range);
        }
    
        VulkanBuffer(VulkanBuffer&& other) {
            buffer = other.buffer;
            memory = other.memory;
            size = other.size;
            other.buffer = VK_NULL_HANDLE;
            address = other.address;
            if constexpr(Mapped) {
                VulkanBufferMappedConditional<T, true>::value = other.VulkanBufferMappedConditional<T, true>::value;
            }
        };

        ~VulkanBuffer() {
            if(buffer != VK_NULL_HANDLE) {
               Clear();
            }
        }

        VulkanBuffer(VulkanBuffer&) = delete;
        VulkanBuffer& operator=(const VulkanBuffer&) = delete;
    };
}
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
