#include "common.h"

#include "kernel/vc4_packet.h"

/*
 * https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#vkGetPhysicalDeviceMemoryProperties
 */
void rpi_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties)
{
	assert(physicalDevice);
	assert(pMemoryProperties);

	if(memoryHeaps[0].size == 0)
	{
		//TODO is this the correct way of getting amount of video mem?
		char buf[4096];
		int fd = open("/proc/meminfo", O_RDONLY);
		read(fd, buf, 4096);
		close(fd);
		char* cma = strstr(buf, "CmaTotal");
		char* cmaend = strstr(cma, "\n");
		char cmaAmount[4096];
		char* cmaPtr = cmaAmount;
		while(cma != cmaend)
		{
			if(*cma >= '0' && *cma <= '9')
			{
				//number
				*cmaPtr = *cma; //copy char
				cmaPtr++;
			}

			cma++;
		}
		*cmaPtr = '\0';
		unsigned amount = atoi(cmaAmount);
		//printf("%i\n", amount);

		//all heaps share the same memory
		for(int c = 0; c < numMemoryHeaps; ++c)
		{
			memoryHeaps[c].size = amount * 1000; //kB to B
		}
	}

	pMemoryProperties->memoryTypeCount = numMemoryTypes;
	for(int c = 0; c < numMemoryTypes; ++c)
	{
		pMemoryProperties->memoryTypes[c] = memoryTypes[c];
	}

	pMemoryProperties->memoryHeapCount = numMemoryHeaps;
	for(int c = 0; c < numMemoryHeaps; ++c)
	{
		pMemoryProperties->memoryHeaps[c] = memoryHeaps[c];
	}
}

/*
 * https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#vkAllocateMemory
 */
VkResult rpi_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory)
{
	assert(device);
	assert(pAllocateInfo);
	assert(pMemory);

	uint32_t bo = vc4_bo_alloc(controlFd, pAllocateInfo->allocationSize, "vkAllocateMemory");
	if(!bo)
	{
		return VK_ERROR_OUT_OF_DEVICE_MEMORY;
	}

	_deviceMemory* mem = ALLOCATE(sizeof(_deviceMemory), 1, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
	if(!mem)
	{
		return VK_ERROR_OUT_OF_HOST_MEMORY;
	}

	mem->bo = bo;
	mem->size = pAllocateInfo->allocationSize;
	mem->memTypeIndex = pAllocateInfo->memoryTypeIndex;
	mem->mappedPtr = 0;

	*pMemory = mem;

	//TODO max number of allocations

	return VK_SUCCESS;
}

/*
 * https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#vkMapMemory
 */
VkResult rpi_vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData)
{
	assert(device);
	assert(memory);
	assert(size);
	assert(ppData);

	assert(memoryTypes[((_deviceMemory*)memory)->memTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
	assert(!((_deviceMemory*)memory)->mappedPtr);
	assert(offset < ((_deviceMemory*)memory)->size);
	if(size != VK_WHOLE_SIZE)
	{
		assert(size > 0);
		assert(size <= ((_deviceMemory*)memory)->size - offset);
	}

	//TODO check ppdata alignment
	//TODO multiple instances?

	void* ptr = vc4_bo_map(controlFd, ((_deviceMemory*)memory)->bo, offset, size);
	if(!ptr)
	{
		return VK_ERROR_MEMORY_MAP_FAILED;
	}

	((_deviceMemory*)memory)->mappedPtr = ptr;
	((_deviceMemory*)memory)->mappedOffset = offset;
	((_deviceMemory*)memory)->mappedSize = size;
	*ppData = ptr;

	return VK_SUCCESS;
}

/*
 * https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#vkUnmapMemory
 */
void rpi_vkUnmapMemory(VkDevice device, VkDeviceMemory memory)
{
	assert(device);
	assert(memory);

	vc4_bo_unmap_unsynchronized(controlFd, ((_deviceMemory*)memory)->mappedPtr, ((_deviceMemory*)memory)->mappedSize);
	((_deviceMemory*)memory)->mappedPtr = 0;
	((_deviceMemory*)memory)->mappedSize = 0;
	((_deviceMemory*)memory)->mappedOffset = 0;
}

void rpi_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator)
{
	assert(device);

	_deviceMemory* mem = memory;
	if(mem)
	{
		vc4_bo_free(controlFd, mem->bo, mem->mappedPtr, mem->size);
		FREE(mem);
	}
}

/*
 * https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#vkFlushMappedMemoryRanges
 */
VKAPI_ATTR VkResult VKAPI_CALL rpi_vkFlushMappedMemoryRanges(
	VkDevice                                    device,
	uint32_t                                    memoryRangeCount,
	const VkMappedMemoryRange*                  pMemoryRanges)
{
	//TODO

	return VK_SUCCESS;
}

/*
 * https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#vkInvalidateMappedMemoryRanges
 */
VKAPI_ATTR VkResult VKAPI_CALL rpi_vkInvalidateMappedMemoryRanges(
	VkDevice                                    device,
	uint32_t                                    memoryRangeCount,
	const VkMappedMemoryRange*                  pMemoryRanges)
{
	//TODO

	return VK_SUCCESS;
}

VKAPI_ATTR void VKAPI_CALL rpi_vkGetPhysicalDeviceMemoryProperties2(
	VkPhysicalDevice                            physicalDevice,
	VkPhysicalDeviceMemoryProperties2*          pMemoryProperties)
{
	assert(physicalDevice);
	rpi_vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties);
}

VKAPI_ATTR void VKAPI_CALL rpi_vkGetDeviceMemoryCommitment(
	VkDevice                                    device,
	VkDeviceMemory                              memory,
	VkDeviceSize*                               pCommittedMemoryInBytes)
{
	//TODO
}
