#include "VulkanVertexBuffer.h"
#include "utilities/Logger.h"
#include <cstdint>
#include <vulkan/vulkan_core.h>
#include <cstring>

void VulkanVertexBuffer::Initialize(FVertexBufferConfig InConfig)
{
	Config = InConfig;
}

void VulkanVertexBuffer::Cleanup()
{
	vkDestroyBuffer(Config.Device, VertexBuffer, nullptr);
	vkFreeMemory(Config.Device, VertexBufferMemory, nullptr);
}

void VulkanVertexBuffer::CreateVertexBuffer(const std::vector<Vertex>& InVertices)
{
	VkBufferCreateInfo BufferInfo{};
	BufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
	BufferInfo.size = sizeof(InVertices[0]) * InVertices.size();
	BufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
	BufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

	if (vkCreateBuffer(Config.Device, &BufferInfo, nullptr, &VertexBuffer) != VK_SUCCESS)
	{
		Log::Error("Failed to create vertex buffer!");
	}
	else
	{
		Log::Info("Successfully created vertex buffer.");
	}

	VkMemoryRequirements MemoryRequirements;
	vkGetBufferMemoryRequirements(Config.Device, VertexBuffer, &MemoryRequirements);

	VkMemoryAllocateInfo AllocateInfo{};
	AllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
	AllocateInfo.allocationSize = MemoryRequirements.size;
	AllocateInfo.memoryTypeIndex = FindMemoryType(MemoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

	if (vkAllocateMemory(Config.Device, &AllocateInfo, nullptr, &VertexBufferMemory) != VK_SUCCESS)
	{
		Log::Error("Failed to allocate vertex buffer memory!");
	}
	else
	{
		Log::Info("Successfully allocated vertex buffer memory.");
	}

	vkBindBufferMemory(Config.Device, VertexBuffer, VertexBufferMemory, 0);

	void* Data;
	vkMapMemory(Config.Device, VertexBufferMemory, 0, BufferInfo.size, 0, &Data);
	memcpy(Data, InVertices.data(), (size_t)BufferInfo.size);
	vkUnmapMemory(Config.Device, VertexBufferMemory);
}

uint32_t VulkanVertexBuffer::FindMemoryType(uint32_t TypeFilter, VkMemoryPropertyFlags Properties)
{
	VkPhysicalDeviceMemoryProperties MemoryProperties;
	vkGetPhysicalDeviceMemoryProperties(Config.PhysicalDevice, &MemoryProperties);

	for (uint32_t i = 0; i < MemoryProperties.memoryTypeCount; i++)
	{
		if ((TypeFilter & (1 << i)) && (MemoryProperties.memoryTypes[i].propertyFlags & Properties) == Properties)
		{
			return i;
		}
	}

	Log::Error("Failed to find suitable memory type!");
}
