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

import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorAllocator;
import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorSet;
import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Shader.DescriptorSetLayout;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.vulkan.*;

import java.nio.ByteBuffer;
import java.util.Locale;

import static org.lwjgl.vulkan.VK13.*;

public class VulkanUtils {
    public static final int MAX_IN_FLIGHT = 2;
    public enum OSType {WINDOWS, MACOS, LINUX, UNSUPPORTED}

    public static final int FLOAT_SIZE = 4;
    public static final int INT_SIZE = 4;
    public static final int MATRIX4X4_SIZE = 16 * FLOAT_SIZE;
    public static final int VEC4_SIZE = 4 * FLOAT_SIZE;

    public static void CopyMatrixToBuffer(VulkanContext VkCtx, VulkanBuffer VkBuffer, Matrix4f matrix, int Offset){
        long MappedMemory = VkBuffer.MapMemory(VkCtx);
        ByteBuffer matrixBuffer = MemoryUtil.memByteBuffer(MappedMemory, (int)VkBuffer.GetRequestedSize());
        matrix.get(Offset, matrixBuffer);
        VkBuffer.UnMapMemory(VkCtx);
    }

    public static VulkanBuffer[] CreateHostVisibleBuffers(VulkanContext VkCtx, long BufferSize, int BufferCount, int Usage, String ID, DescriptorSetLayout layout){
        DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator();
        Device device = VkCtx.GetDevice();
        VulkanBuffer[] Result = new VulkanBuffer[BufferCount];
        descriptorAllocator.AddDescriptorSets(device, ID, BufferCount, layout);
        DescriptorSetLayout.LayoutInformation LayoutInfo = layout.GetLayoutInfo();
        for(int i = 0; i < BufferCount; i++){
            Result[i] = new VulkanBuffer(VkCtx, BufferSize, Usage, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
            DescriptorSet descriptorSet = descriptorAllocator.GetDescriptorSet(ID, i);
            descriptorSet.SetBuffer(device, Result[i], Result[i].GetRequestedSize(), layout.GetLayoutInfo().Binding(),layout.GetLayoutInfo().DescriptorType());

        }
        return Result;
    }
    public static VulkanBuffer CreateHostVisibleBuffer(VulkanContext VkCtx, long BufferSize, int Usage, String ID, DescriptorSetLayout layout){
        var Buffer = new VulkanBuffer(VkCtx, BufferSize, Usage, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
        Device device = VkCtx.GetDevice();
        DescriptorSet descriptorSet = VkCtx.GetDescriptorAllocator().AddDescriptorSet(device, ID, layout);
        descriptorSet.SetBuffer(device, Buffer, Buffer.GetRequestedSize(), layout.GetLayoutInfo().Binding(),layout.GetLayoutInfo().DescriptorType());
        return Buffer;
    }

    public static int MemoryTypeFromProperties(VulkanContext vulkanContext, int TypeBits, int ReqMask){
        int result = -1;
        VkMemoryType.Buffer MemoryTypes = vulkanContext.GetPhysicalDevice().GetMemoryProperties().memoryTypes();
        for(int i = 0; i < VK_MAX_MEMORY_TYPES; i++){
            if((TypeBits & 1) == 1 && (MemoryTypes.get(i).propertyFlags() & ReqMask)==ReqMask){
                result = i;
                break;
            }
            TypeBits >>= 1;
        }
        if(result < 0){
            throw new RuntimeException("Failed to find MemoryType");
        }
        return result;
    }

    public static OSType getOS() {
        OSType result;
        String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
        if ((os.contains("mac")) || (os.contains("darwin"))) {
            result = OSType.MACOS;
        } else if (os.contains("win")) {
            result = OSType.WINDOWS;
        } else if (os.contains("nux")) {
            result = OSType.LINUX;
        } else {
            result = OSType.UNSUPPORTED;
        }
        return result;
    }
    public static void ImageBarrier(MemoryStack MemStack, VkCommandBuffer CommandHandle, long Image, int OldLayout,
                                    int NewLayout, long SrcStage, long DstStage, long SrcAccess, long DstAccess,
                                    int AspectMask){
        var ImageBarrier = VkImageMemoryBarrier2.calloc(1, MemStack)
                .sType$Default()
                .oldLayout(OldLayout)
                .newLayout(NewLayout)
                .srcStageMask(SrcStage)
                .dstStageMask(DstStage)
                .srcAccessMask(SrcAccess)
                .dstAccessMask(DstAccess)
                .srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
                .dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED)
                .subresourceRange(it -> it
                        .aspectMask(AspectMask)
                        .baseMipLevel(0)
                        .levelCount(VK_REMAINING_MIP_LEVELS)
                        .baseArrayLayer(0)
                        .layerCount(VK_REMAINING_ARRAY_LAYERS))
                .image(Image);
        VkDependencyInfo DependencyInfo = VkDependencyInfo.calloc(MemStack)
                .sType$Default()
                .pImageMemoryBarriers(ImageBarrier);
        vkCmdPipelineBarrier2(CommandHandle, DependencyInfo);
    }
    public static void vkCheck(int Error, String ErrorMessage) {
        if (Error != VK_SUCCESS) {
            String ErrorCode = switch (Error) {
                case VK_NOT_READY -> "VK_NOT_READY";
                case VK_TIMEOUT -> "VK_TIMEOUT";
                case VK_EVENT_SET -> "VK_EVENT_SET";
                case VK_EVENT_RESET -> "VK_EVENT_RESET";
                case VK_INCOMPLETE -> "VK_INCOMPLETE";
                case VK_ERROR_OUT_OF_HOST_MEMORY -> "VK_ERROR_OUT_OF_HOST_MEMORY";
                case VK_ERROR_OUT_OF_DEVICE_MEMORY -> "VK_ERROR_OUT_OF_DEVICE_MEMORY";
                case VK_ERROR_INITIALIZATION_FAILED -> "VK_ERROR_INITIALIZATION_FAILED";
                case VK_ERROR_DEVICE_LOST -> "VK_ERROR_DEVICE_LOST";
                case VK_ERROR_MEMORY_MAP_FAILED -> "VK_ERROR_MEMORY_MAP_FAILED";
                case VK_ERROR_LAYER_NOT_PRESENT -> "VK_ERROR_LAYER_NOT_PRESENT";
                case VK_ERROR_EXTENSION_NOT_PRESENT -> "VK_ERROR_EXTENSION_NOT_PRESENT";
                case VK_ERROR_FEATURE_NOT_PRESENT -> "VK_ERROR_FEATURE_NOT_PRESENT";
                case VK_ERROR_INCOMPATIBLE_DRIVER -> "VK_ERROR_INCOMPATIBLE_DRIVER";
                case VK_ERROR_TOO_MANY_OBJECTS -> "VK_ERROR_TOO_MANY_OBJECTS";
                case VK_ERROR_FORMAT_NOT_SUPPORTED -> "VK_ERROR_FORMAT_NOT_SUPPORTED";
                case VK_ERROR_FRAGMENTED_POOL -> "VK_ERROR_FRAGMENTED_POOL";
                case VK_ERROR_UNKNOWN -> "VK_ERROR_UNKNOWN";
                default -> "Not mapped";
            };
            throw new RuntimeException(ErrorMessage + ": " + ErrorCode + " [" + Error + "]");
        }
    }
}
