/* Copyright (c) 2017-2018 Hans-Kristian Arntzen
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "command_pool.hpp"

namespace Vulkan
{
CommandPool::CommandPool(VkDevice device, uint32_t queue_family_index)
    : device(device)
{
	VkCommandPoolCreateInfo info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
	info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
	info.queueFamilyIndex = queue_family_index;
	vkCreateCommandPool(device, &info, nullptr, &pool);
}

CommandPool::CommandPool(CommandPool &&other) noexcept
{
	*this = std::move(other);
}

CommandPool &CommandPool::operator=(CommandPool &&other) noexcept
{
	if (this != &other)
	{
		device = other.device;

		// Free our owned resources first.
		if (!buffers.empty())
			vkFreeCommandBuffers(device, pool, buffers.size(), buffers.data());
		if (!secondary_buffers.empty())
			vkFreeCommandBuffers(device, pool, secondary_buffers.size(), secondary_buffers.data());
		if (pool != VK_NULL_HANDLE)
			vkDestroyCommandPool(device, pool, nullptr);

		// Adopt other's resources.
		pool = other.pool;
		buffers = std::move(other.buffers);
		secondary_buffers = std::move(other.secondary_buffers);
		index = other.index;
		secondary_index = other.secondary_index;
#ifdef VULKAN_DEBUG
		in_flight = std::move(other.in_flight);
#endif

		// Leave other in a destructor-safe state (no double-free).
		other.pool = VK_NULL_HANDLE;
		other.index = 0;
		other.secondary_index = 0;
		other.buffers.clear();
		other.secondary_buffers.clear();
#ifdef VULKAN_DEBUG
		other.in_flight.clear();
#endif
	}
	return *this;
}

CommandPool::~CommandPool()
{
	if (!buffers.empty())
		vkFreeCommandBuffers(device, pool, buffers.size(), buffers.data());
	if (!secondary_buffers.empty())
		vkFreeCommandBuffers(device, pool, secondary_buffers.size(), secondary_buffers.data());
	if (pool != VK_NULL_HANDLE)
		vkDestroyCommandPool(device, pool, nullptr);
}

void CommandPool::signal_submitted(VkCommandBuffer cmd)
{
#ifdef VULKAN_DEBUG
	VK_ASSERT(in_flight.find(cmd) != end(in_flight));
	in_flight.erase(cmd);
#else
	(void)cmd;
#endif
}

VkCommandBuffer CommandPool::request_secondary_command_buffer()
{
	if (secondary_index < secondary_buffers.size())
	{
		VkCommandBuffer ret = secondary_buffers[secondary_index++];
#ifdef VULKAN_DEBUG
		VK_ASSERT(in_flight.find(ret) == end(in_flight));
		in_flight.insert(ret);
#endif
		return ret;
	}
	else
	{
		VkCommandBuffer cmd;
		VkCommandBufferAllocateInfo info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
		info.commandPool = pool;
		info.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
		info.commandBufferCount = 1;

		vkAllocateCommandBuffers(device, &info, &cmd);
#ifdef VULKAN_DEBUG
		VK_ASSERT(in_flight.find(cmd) == end(in_flight));
		in_flight.insert(cmd);
#endif
		secondary_buffers.push_back(cmd);
		secondary_index++;
		return cmd;
	}
}

VkCommandBuffer CommandPool::request_command_buffer()
{
	if (index < buffers.size())
	{
		VkCommandBuffer ret = buffers[index++];
#ifdef VULKAN_DEBUG
		VK_ASSERT(in_flight.find(ret) == end(in_flight));
		in_flight.insert(ret);
#endif
		return ret;
	}
	else
	{
		VkCommandBuffer cmd;
		VkCommandBufferAllocateInfo info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
		info.commandPool = pool;
		info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
		info.commandBufferCount = 1;

		vkAllocateCommandBuffers(device, &info, &cmd);
#ifdef VULKAN_DEBUG
		VK_ASSERT(in_flight.find(cmd) == end(in_flight));
		in_flight.insert(cmd);
#endif
		buffers.push_back(cmd);
		index++;
		return cmd;
	}
}

void CommandPool::begin()
{
#ifdef VULKAN_DEBUG
	VK_ASSERT(in_flight.empty());
#endif
	if (index > 0 || secondary_index > 0)
		vkResetCommandPool(device, pool, 0);
	index = 0;
	secondary_index = 0;
}
}
