package net.halbear.Terrain4J.EngineCore.Vulkan.Util;

import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.vma.VmaAllocationCreateInfo;
import org.lwjgl.util.vma.VmaAllocatorCreateInfo;
import org.lwjgl.vulkan.VkBufferCreateInfo;
import org.lwjgl.vulkan.VkDevice;
import org.lwjgl.vulkan.VkMemoryAllocateInfo;
import org.lwjgl.vulkan.VkMemoryRequirements;
import org.tinylog.Logger;

import java.nio.LongBuffer;

import static net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils.vkCheck;
import static org.lwjgl.system.MemoryUtil.NULL;
import static org.lwjgl.util.vma.Vma.*;
import static org.lwjgl.vulkan.VK13.*;

public class VulkanBuffer {

    private final long Allocation;
    //private final long AllocationSize;
    private final long Buffer;
    //private final long Memory;
    private final PointerBuffer pointerBuffer;
    private final long RequestedSize;
    private long MappedMemory;

    public VulkanBuffer(VulkanContext vulkanContext, long Size, int BufferUsage,int VmaUsage,int VmaFlags, int RequiredFlags){
        RequestedSize = Size;
        MappedMemory = NULL;
        try(var MemStack = MemoryStack.stackPush()){
            //Device device = vulkanContext.GetDevice();
            var BufferCreationInfo = VkBufferCreateInfo.calloc(MemStack)
                    .sType$Default()
                    .size(Size)
                    .usage(BufferUsage)
                    .sharingMode(VK_SHARING_MODE_EXCLUSIVE);
            //vkCheck(vkCreateBuffer(device.FetchVulkanDevice(), BufferCreationInfo, null, LongPointer),"Failed to create Vulkan Buffer");

            VmaAllocationCreateInfo AllocatorInfo = VmaAllocationCreateInfo.calloc(MemStack)
                    .usage(VmaUsage)
                    .flags(VmaFlags)
                    .requiredFlags(RequiredFlags);

            PointerBuffer AllocationPointer = MemStack.callocPointer(1);
            LongBuffer LongPointer = MemStack.mallocLong(1);

            vkCheck(vmaCreateBuffer(vulkanContext.GetVkMemoryAllocator().GetVmaAllocator(),BufferCreationInfo,AllocatorInfo,LongPointer,AllocationPointer,null),
                    "Failed to create VMA buffer");

            Buffer = LongPointer.get(0);
            Allocation = AllocationPointer.get(0);
            pointerBuffer = MemoryUtil.memAllocPointer(1);
        }
    }
    public void cleanup(VulkanContext vulkanContext){
        MemoryUtil.memFree(pointerBuffer);
        UnMapMemory(vulkanContext);
        vmaDestroyBuffer(vulkanContext.GetVkMemoryAllocator().GetVmaAllocator(), Buffer, Allocation);
    }
    public void Flush(VulkanContext VkCtx){
        vmaFlushAllocation(VkCtx.GetVkMemoryAllocator().GetVmaAllocator(),Allocation,0,VK_WHOLE_SIZE);
    }
    public long GetBuffer(){
        return Buffer;
    }
    public long GetRequestedSize(){
        return RequestedSize;
    }
    public long MapMemory(VulkanContext vulkanContext){
        if(MappedMemory == NULL){
            vkCheck(vmaMapMemory(vulkanContext.GetVkMemoryAllocator().GetVmaAllocator(),Allocation,pointerBuffer),"Failed to map Vulkan Buffer");
            MappedMemory = pointerBuffer.get(0);
        }
        return MappedMemory;
    }
    public void UnMapMemory(VulkanContext vulkanContext){
        if(MappedMemory != NULL){
            vmaUnmapMemory(vulkanContext.GetVkMemoryAllocator().GetVmaAllocator(),Allocation);
            MappedMemory = NULL;
        }
    }
}
