package net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel;

import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.Texture;
import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.Images.TextureCache;
import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.Pipeline.IndexedLinkedHashMap;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandBuffer;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.CommandPool;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.GPUSynchronisation.TransferBuffer;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Queues.Queue;
import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanBuffer;
import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils;
import org.lwjgl.system.MemoryUtil;
import org.tinylog.Logger;
import org.w3c.dom.Text;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;

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

public class MaterialsCache {
    private static final int MATERIAL_SIZE = VulkanUtils.VEC4_SIZE + VulkanUtils.INT_SIZE * 4;
    private final IndexedLinkedHashMap<String, VulkanMaterial> MaterialsMap;
    private VulkanBuffer MaterialsBuffer;

    public MaterialsCache(){
        MaterialsMap = new IndexedLinkedHashMap<>();
    }

    //create staging buffer and GPU only accessibly buffer, add any located textures
    public void LoadMaterials(VulkanContext VkCtx, List<MaterialData> Materials, TextureCache textureCache, CommandPool commandPool, Queue queue){
        int MaterialCount = Materials.size();
        int BufferSize = MATERIAL_SIZE * MaterialCount;
        var SrcBuffer = new VulkanBuffer(VkCtx, BufferSize,
                VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
        MaterialsBuffer = new VulkanBuffer(VkCtx, BufferSize,
                VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

        var CmdBuffer = new CommandBuffer(VkCtx, commandPool, true, true);
        CmdBuffer.BeginRecording();

        TransferBuffer transferBuffer = new TransferBuffer(SrcBuffer,MaterialsBuffer);
        long MappedMemory = SrcBuffer.MapMemory(VkCtx);
        ByteBuffer data = MemoryUtil.memByteBuffer(MappedMemory, (int)SrcBuffer.GetRequestedSize());

        int Offset = 0;
        for(int i = 0; i < MaterialCount; i++){
            var Material = Materials.get(i);
            String TexturePath = Material.TexturePath();
            boolean ValidTexture = TexturePath != null && !TexturePath.isEmpty();
            boolean TransparentTexture;
            if(ValidTexture){
                Logger.debug("Loading Texture [{}]",TexturePath);
                Texture newTexture = textureCache.AddTexture(VkCtx, TexturePath, TexturePath, VK_FORMAT_R8G8B8A8_SRGB);
                TransparentTexture = newTexture.HasTransparency();
            } else{
                TransparentTexture = Material.DiffuseColour().w < 1.0f;
            }
            VulkanMaterial newMaterial = new VulkanMaterial(Material.ID(),TransparentTexture);
            MaterialsMap.put(newMaterial.ID(), newMaterial);
            Logger.trace(Material.toString());
            data.position(Offset);
            Material.DiffuseColour().get(data);
            Logger.debug("Set Diffuse Colour -> [{}]",Material.DiffuseColour());
            data.putInt(Offset + VulkanUtils.VEC4_SIZE, ValidTexture ? 1 : 0);
            data.putInt(Offset + VulkanUtils.VEC4_SIZE + VulkanUtils.INT_SIZE, textureCache.GetPosition(TexturePath));
            //pad data because the minimum size of data in the shader layout is a multiple of Vec4,
            // compensate with 2 more as we only need 6 currently
            data.putInt(Offset + VulkanUtils.VEC4_SIZE + VulkanUtils.INT_SIZE * 2,0);
            data.putInt(Offset + VulkanUtils.VEC4_SIZE + VulkanUtils.INT_SIZE * 3,0);
            Offset += MATERIAL_SIZE;
        }
        data.position(0);

        SrcBuffer.UnMapMemory(VkCtx);
        transferBuffer.RecordTransferCommand(CmdBuffer);
        CmdBuffer.EndRecording();
        CmdBuffer.SubmitAndWait(VkCtx,queue);
        CmdBuffer.cleanup(VkCtx,commandPool);
        transferBuffer.SrcBuffer().cleanup(VkCtx);
    }


    public void CleanUp(VulkanContext VkCtx){
        if(MaterialsBuffer != null){
            MaterialsBuffer.cleanup(VkCtx);
        }
    }

    public VulkanMaterial GetMaterial(String ID){return MaterialsMap.get(ID);}

    public VulkanBuffer GetMaterialsBuffer(){return MaterialsBuffer;}

    public int GetPosition(String ID){
        int result = -1;
        if(ID != null){
            result = MaterialsMap.GetIndexOf(ID);
        } else{
            Logger.warn("Missing Material [{}]",ID);
        }
        return result;
    }
}
