/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include "Base/Utils.hpp"
#include <vulkan/vulkan.hpp>

namespace OpenVulkano::Vulkan
{
	class MemoryAllocation
	{
	public:
		vk::DeviceMemory memory;
		size_t used;
		const size_t size;
		const uint32_t type;
		const vk::Device device;
		void* mapped;

	private:
		uint32_t mappedCount;
		static constexpr uint32_t CHILD_MAPPED_FLAG = 1u << 31;

	public:
		MemoryAllocation(size_t size, uint32_t type, vk::Device device):
				memory(nullptr), used(0), size(size), type(type), device(device), mapped(nullptr), mappedCount(0)
		{
		}

		~MemoryAllocation()
		{
			if (device && memory)
				device.free(memory);
		}

		[[nodiscard]] size_t FreeSpace() const
		{
			return size - used;
		}

		[[nodiscard]] size_t FreeSpace(size_t alignment) const
		{
			return size - Utils::Align(used, alignment);
		}

		void HandleChildMappingValidation() const;

		void* Map()
		{
			HandleChildMappingValidation();
			if (!mapped)
			{
				mapped = device.mapMemory(memory, 0, size, vk::MemoryMapFlags());
			}
			mappedCount++;
			return mapped;
		}

		void UnMap()
		{
			if (mappedCount > 0)
			{
				mappedCount--;
				if (mappedCount == 0)
				{
					device.unmapMemory(memory);
				}
			}
		}

		[[nodiscard]] bool IsChildMapped() const
		{
			return mappedCount & CHILD_MAPPED_FLAG;
		}

		void* MapChild(size_t offset, vk::DeviceSize size)
		{
			HandleChildMappingValidation();
			mappedCount |= CHILD_MAPPED_FLAG;
			mappedCount++;
			return device.mapMemory(memory, offset, size, vk::MemoryMapFlags());
		}

		void UnMapChild()
		{
			mappedCount &= ~CHILD_MAPPED_FLAG;
			mappedCount--;
			if (mappedCount == 0)
			{
				device.unmapMemory(memory);
			}
		}
	};
}