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.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.vulkan.VK13.*;

public class VulkanBuffer {
    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 Usage, int RequestMask){
        RequestedSize = Size;
        MappedMemory = NULL;
        try(var MemStack = MemoryStack.stackPush()){
            Device device = vulkanContext.GetDevice();
            var BufferCreationInfo = VkBufferCreateInfo.calloc(MemStack)
                    .sType$Default()
                    .size(Size)
                    .usage(Usage)
                    .sharingMode(VK_SHARING_MODE_EXCLUSIVE);
            LongBuffer LongPointer = MemStack.mallocLong(1);
            vkCheck(vkCreateBuffer(device.FetchVulkanDevice(), BufferCreationInfo, null, LongPointer),"Failed to create Vulkan Buffer");
            Buffer = LongPointer.get(0);

            var MemoryRequirements = VkMemoryRequirements.calloc(MemStack);
            vkGetBufferMemoryRequirements(device.FetchVulkanDevice(), Buffer, MemoryRequirements);

            var MemoryAllocation = VkMemoryAllocateInfo.calloc(MemStack)
                    .sType$Default()
                    .allocationSize(MemoryRequirements.size())
                    .memoryTypeIndex(VulkanUtils.MemoryTypeFromProperties(vulkanContext, MemoryRequirements.memoryTypeBits(), RequestMask));

            vkCheck(vkAllocateMemory(device.FetchVulkanDevice(),MemoryAllocation,null,LongPointer),"Failed to allocate memory for Vulkan");
            AllocationSize = MemoryAllocation.allocationSize();
            Memory = LongPointer.get(0);
            pointerBuffer = MemoryUtil.memAllocPointer(1);
            if (Buffer == 0) {
                Logger.error("Vulkan crash blocked: Native bufferHandle cannot be 0!");
                throw new IllegalArgumentException("Vulkan crash blocked: Native bufferHandle cannot be 0!");
            }
            if (Memory == 0) {
                Logger.error("Vulkan crash blocked: Native memoryHandle cannot be 0! Device out of memory or allocation failed.");
                throw new IllegalArgumentException("Vulkan crash blocked: Native memoryHandle cannot be 0! Device out of memory or allocation failed.");
            }
            vkCheck(vkBindBufferMemory(device.FetchVulkanDevice(),Buffer,Memory,0),"Failed to Bind Vulkan Memory Buffer");
        }
    }
    public void cleanup(VulkanContext vulkanContext){
        MemoryUtil.memFree(pointerBuffer);
        VkDevice vulkanDevice = vulkanContext.GetDevice().FetchVulkanDevice();
        vkDestroyBuffer(vulkanDevice,Buffer, null);
        vkFreeMemory(vulkanDevice, Memory, null);
        Logger.debug("Freed Vulkan Memory Buffer");
    }

    public long GetBuffer(){
        return Buffer;
    }
    public long GetRequestedSize(){
        return RequestedSize;
    }
    public long MapMemory(VulkanContext vulkanContext){
        if(MappedMemory == NULL){
            vkCheck(vkMapMemory(vulkanContext.GetDevice().FetchVulkanDevice(), Memory,0,AllocationSize,0,pointerBuffer),"Failed to map Vulkan Buffer");
            MappedMemory = pointerBuffer.get(0);
        }
        return MappedMemory;
    }
    public void UnMapMemory(VulkanContext vulkanContext){
        if(MappedMemory != NULL){
            vkUnmapMemory(vulkanContext.GetDevice().FetchVulkanDevice(), Memory);
            MappedMemory = NULL;
        }
    }
}
