package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers;

import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.GPUSynchronisation.Fence;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Queues.Queue;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.*;
import org.tinylog.Logger;

import java.nio.IntBuffer;

import static net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils.vkCheck;
import static org.lwjgl.vulkan.VK13.*;
public class CommandBuffer {
    private final boolean OneTimeSubmit;
    private final boolean Primary;
    private final VkCommandBuffer VulkanCommandBuffer;

    public CommandBuffer(VulkanContext vulkanContext, CommandPool commandPool, boolean primary, boolean oneTimeSubmit){
        Logger.debug("Creating Command Buffer");
        OneTimeSubmit = oneTimeSubmit;
        Primary = primary;
        VkDevice VulkanDevice = vulkanContext.GetDevice().FetchVulkanDevice();

        try(var MemStack = MemoryStack.stackPush()){
            var CommandBufferAllocateInfo = VkCommandBufferAllocateInfo.calloc(MemStack)
                    .sType$Default()
                    .commandPool(commandPool.GetVulkanCommandPool())
                    .level(primary ? VK_COMMAND_BUFFER_LEVEL_PRIMARY : VK_COMMAND_BUFFER_LEVEL_SECONDARY)
                    .commandBufferCount(1);
            PointerBuffer pointerBuffer = MemStack.mallocPointer(1);
            vkCheck(vkAllocateCommandBuffers(VulkanDevice, CommandBufferAllocateInfo, pointerBuffer),"Failed to allocated Vulkan Renderer Command Buffer");
            VulkanCommandBuffer = new VkCommandBuffer(pointerBuffer.get(0),VulkanDevice);
        }
    }

    public void BeginRecording(){
        BeginRecording(null);
    }
    public void BeginRecording(InheritanceInfo inheritanceInfo){
        try(var MemStack = MemoryStack.stackPush()){
            var CommandBufferInfo = VkCommandBufferBeginInfo.calloc(MemStack).sType$Default();
            if (OneTimeSubmit){
                CommandBufferInfo.flags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
            }
            if(!Primary){
                if (inheritanceInfo == null){
                    throw new RuntimeException("Secondary Command Buffer lacks correct inheritance information");
                }
                int ColourFormatCount = inheritanceInfo.ColourFormats.length;
                IntBuffer ColourFormatPointer = MemStack.callocInt(inheritanceInfo.ColourFormats.length);
                for(int i = 0; i < ColourFormatCount; i++){
                    ColourFormatPointer.put(0,inheritanceInfo.ColourFormats[i]);
                }
                var RenderInfo = VkCommandBufferInheritanceRenderingInfo.calloc(MemStack)
                        .sType$Default()
                        .depthAttachmentFormat(inheritanceInfo.DepthFormat)
                        .pColorAttachmentFormats(ColourFormatPointer)
                        .rasterizationSamples(inheritanceInfo.RasterizationSamples);
                var VulkanInheritanceInfo = VkCommandBufferInheritanceInfo.calloc(MemStack)
                        .sType$Default()
                        .pNext(RenderInfo);
                CommandBufferInfo.pInheritanceInfo(VulkanInheritanceInfo);
            }
            vkCheck(vkBeginCommandBuffer(VulkanCommandBuffer, CommandBufferInfo),"Failed to begin Command Buffer");
        }
    }

    public void SubmitAndWait(VulkanContext vulkanContext, Queue queue){
        Fence fence = new Fence(vulkanContext, true);
        fence.Reset(vulkanContext);
        try(var MemStack = MemoryStack.stackPush()){
            var Commands = VkCommandBufferSubmitInfo.calloc(1, MemStack)
                    .sType$Default()
                    .commandBuffer(VulkanCommandBuffer);
            queue.Submit(Commands, null, null, fence);
        }
        fence.FenceWait(vulkanContext);
        fence.cleanup(vulkanContext);
    }

    public record InheritanceInfo(int DepthFormat, int[] ColourFormats, int RasterizationSamples){}

    public void cleanup(VulkanContext vulkanContext,CommandPool commandPool){
        Logger.trace("Destroyed Command Buffer");
        vkFreeCommandBuffers(vulkanContext.GetDevice().FetchVulkanDevice(), commandPool.GetVulkanCommandPool(), VulkanCommandBuffer);
    }
    public void EndRecording(){
        vkCheck(vkEndCommandBuffer(VulkanCommandBuffer), "Failed to end command buffer recording");
    }
    public VkCommandBuffer GetVulkanCommandBuffer(){
        return VulkanCommandBuffer;
    }
    public void ResetCommandBuffer(){
        vkResetCommandBuffer(VulkanCommandBuffer, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
    }
}
