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

import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.GPUSynchronisation.Fence;
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 Queue {
    private final int QueueFamilyIndex;
    private final VkQueue VulkanQueue;

    public Queue(VulkanContext vkContext, int QueueFamilyIndex, int QueueIndex){
        Logger.debug("Vulkan: Creating A Queue");

        this.QueueFamilyIndex = QueueFamilyIndex;
        try(var MemStack = MemoryStack.stackPush()){
            PointerBuffer QueuePointer = MemStack.mallocPointer(1);
            vkGetDeviceQueue(vkContext.GetDevice().FetchVulkanDevice(), QueueFamilyIndex, QueueIndex, QueuePointer);
            long Queue = QueuePointer.get(0);
            VulkanQueue = new VkQueue(Queue, vkContext.GetDevice().FetchVulkanDevice());
        }
    }

    public int GetQueueFamilyIndex(){
        return QueueFamilyIndex;
    }
    public VkQueue GetVulkanQueue(){
        return VulkanQueue;
    }
    public void WaitIdle(){
        vkQueueWaitIdle(VulkanQueue);
    }

    public void Submit(VkCommandBufferSubmitInfo.Buffer CommandBuffers, VkSemaphoreSubmitInfo.Buffer WaitSemaphores, VkSemaphoreSubmitInfo.Buffer SignalSemaphores, Fence fence){
        try(var MemStack = MemoryStack.stackPush()){
            var SubmitInfo = VkSubmitInfo2.calloc(1, MemStack)
                    .sType$Default()
                    .pCommandBufferInfos(CommandBuffers)
                    .pSignalSemaphoreInfos(SignalSemaphores);
            if (WaitSemaphores != null){
                SubmitInfo.pWaitSemaphoreInfos(WaitSemaphores);
            }
            long FenceHandle = fence != null ? fence.GetVulkanFence() : VK_NULL_HANDLE;
            vkCheck(vkQueueSubmit2(VulkanQueue, SubmitInfo, FenceHandle),"Failed to submit command queue");
        }
    }

    public static class PresentQueue extends Queue{
        public PresentQueue(VulkanContext vulkanContext, int QueueIndex){
            super(vulkanContext, GetPresentQueueFamilyIndex(vulkanContext), QueueIndex);
        }
        private static int GetPresentQueueFamilyIndex(VulkanContext vulkanContext){
            int Index = -1;
            try(var MemStack = MemoryStack.stackPush()){
                var QueuePropertiesBuffer = vulkanContext.GetPhysicalDevice().GetQueueFamilyProperties();
                int QueueFamilyCount = QueuePropertiesBuffer.capacity();
                IntBuffer intBuffer = MemStack.mallocInt(1);
                for(int i = 0; i < QueueFamilyCount; i++){
                    KHRSurface.vkGetPhysicalDeviceSurfaceSupportKHR(vulkanContext.GetPhysicalDevice().GetPhysicalDevice(), i, vulkanContext.GetSurface().GetVkSurface(), intBuffer);
                    boolean SupportsPresentation = intBuffer.get(0) == VK_TRUE;
                    if(SupportsPresentation){
                        Index = i;
                        break;
                    }
                }
            }
            if (Index < 0){
                throw new RuntimeException("Could Not Present Image Queue Family Index");
            }
            return Index;
        }
    }

    public static class GraphicsQueue extends Queue {
        public GraphicsQueue(VulkanContext vulkanContext, int QueueIndex){
            super(vulkanContext, GetGraphicsQueueFamilyIndex(vulkanContext),QueueIndex);
        }
        private static int GetGraphicsQueueFamilyIndex(VulkanContext vulkanContext){
            int index = -1;
            var QueuePropertiesBuffer = vulkanContext.GetPhysicalDevice().GetQueueFamilyProperties();
            int QueueFamilyCount = QueuePropertiesBuffer.capacity();
            for(int i = 0; i < QueueFamilyCount; i++){
                VkQueueFamilyProperties Properties = QueuePropertiesBuffer.get(i);
                boolean GraphicsQueue = (Properties.queueFlags() & VK_QUEUE_GRAPHICS_BIT)!= 0;
                if (GraphicsQueue){
                    index = i;
                    break;
                }
            }
            if (index < 0){
                throw new RuntimeException("Failed to fetch graphics queue family");
            }
            return index;
        }
    }

}
