#include "SystemOutputFramePool.h"

#include <algorithm>

namespace
{
SystemOutputFramePoolConfig NormalizeConfig(SystemOutputFramePoolConfig config)
{
	if (config.rowBytes == 0)
		config.rowBytes = VideoIORowBytes(config.pixelFormat, config.width);
	return config;
}
}

SystemOutputFramePool::SystemOutputFramePool(const SystemOutputFramePoolConfig& config)
{
	Configure(config);
}

void SystemOutputFramePool::Configure(const SystemOutputFramePoolConfig& config)
{
	std::lock_guard<std::mutex> lock(mMutex);
	mConfig = NormalizeConfig(config);
	mReadySlots.clear();
	mSlots.clear();
	mSlots.resize(mConfig.capacity);

	const std::size_t byteCount = FrameByteCount();
	for (StoredSlot& slot : mSlots)
	{
		slot.bytes.resize(byteCount);
		slot.state = OutputFrameSlotState::Free;
		++slot.generation;
	}

	mAcquireMissCount = 0;
	mReadyUnderrunCount = 0;
}

SystemOutputFramePoolConfig SystemOutputFramePool::Config() const
{
	std::lock_guard<std::mutex> lock(mMutex);
	return mConfig;
}

bool SystemOutputFramePool::AcquireFreeSlot(OutputFrameSlot& slot)
{
	std::lock_guard<std::mutex> lock(mMutex);
	for (std::size_t index = 0; index < mSlots.size(); ++index)
	{
		if (mSlots[index].state != OutputFrameSlotState::Free)
			continue;

		mSlots[index].state = OutputFrameSlotState::Rendering;
		++mSlots[index].generation;
		FillOutputSlotLocked(index, slot);
		return true;
	}

	slot = OutputFrameSlot();
	++mAcquireMissCount;
	return false;
}

bool SystemOutputFramePool::AcquireRenderingSlot(OutputFrameSlot& slot)
{
	return AcquireFreeSlot(slot);
}

bool SystemOutputFramePool::PublishReadySlot(const OutputFrameSlot& slot)
{
	std::lock_guard<std::mutex> lock(mMutex);
	if (!TransitionSlotLocked(slot, OutputFrameSlotState::Rendering, OutputFrameSlotState::Completed))
		return false;

	mReadySlots.push_back(slot.index);
	return true;
}

bool SystemOutputFramePool::PublishCompletedSlot(const OutputFrameSlot& slot)
{
	return PublishReadySlot(slot);
}

bool SystemOutputFramePool::ConsumeReadySlot(OutputFrameSlot& slot)
{
	std::lock_guard<std::mutex> lock(mMutex);
	while (!mReadySlots.empty())
	{
		const std::size_t index = mReadySlots.front();
		mReadySlots.pop_front();
		if (index >= mSlots.size() || mSlots[index].state != OutputFrameSlotState::Completed)
			continue;

		FillOutputSlotLocked(index, slot);
		return true;
	}

	slot = OutputFrameSlot();
	++mReadyUnderrunCount;
	return false;
}

bool SystemOutputFramePool::ConsumeCompletedSlot(OutputFrameSlot& slot)
{
	return ConsumeReadySlot(slot);
}

bool SystemOutputFramePool::MarkScheduled(const OutputFrameSlot& slot)
{
	std::lock_guard<std::mutex> lock(mMutex);
	if (!IsValidSlotLocked(slot))
		return false;
	if (mSlots[slot.index].state != OutputFrameSlotState::Completed)
		return false;

	RemoveReadyIndexLocked(slot.index);
	mSlots[slot.index].state = OutputFrameSlotState::Scheduled;
	return true;
}

bool SystemOutputFramePool::MarkScheduledByBuffer(void* bytes)
{
	if (bytes == nullptr)
		return false;

	std::lock_guard<std::mutex> lock(mMutex);
	for (std::size_t index = 0; index < mSlots.size(); ++index)
	{
		if (mSlots[index].bytes.empty() || mSlots[index].bytes.data() != bytes)
			continue;
		if (mSlots[index].state != OutputFrameSlotState::Completed)
			return false;

		RemoveReadyIndexLocked(index);
		mSlots[index].state = OutputFrameSlotState::Scheduled;
		return true;
	}
	return false;
}

bool SystemOutputFramePool::ReleaseSlot(const OutputFrameSlot& slot)
{
	std::lock_guard<std::mutex> lock(mMutex);
	if (!IsValidSlotLocked(slot) || mSlots[slot.index].state == OutputFrameSlotState::Free)
		return false;

	return ReleaseSlotByIndexLocked(slot.index);
}

bool SystemOutputFramePool::ReleaseScheduledSlot(const OutputFrameSlot& slot)
{
	std::lock_guard<std::mutex> lock(mMutex);
	return TransitionSlotLocked(slot, OutputFrameSlotState::Scheduled, OutputFrameSlotState::Free);
}

bool SystemOutputFramePool::ReleaseSlotByBuffer(void* bytes)
{
	if (bytes == nullptr)
		return false;

	std::lock_guard<std::mutex> lock(mMutex);
	for (std::size_t index = 0; index < mSlots.size(); ++index)
	{
		if (!mSlots[index].bytes.empty() && mSlots[index].bytes.data() == bytes)
			return ReleaseSlotByIndexLocked(index);
	}
	return false;
}

void SystemOutputFramePool::Clear()
{
	std::lock_guard<std::mutex> lock(mMutex);
	mReadySlots.clear();
	for (StoredSlot& slot : mSlots)
	{
		slot.state = OutputFrameSlotState::Free;
		++slot.generation;
	}
}

SystemOutputFramePoolMetrics SystemOutputFramePool::GetMetrics() const
{
	std::lock_guard<std::mutex> lock(mMutex);
	SystemOutputFramePoolMetrics metrics;
	metrics.capacity = mSlots.size();
	metrics.readyCount = mReadySlots.size();
	metrics.acquireMissCount = mAcquireMissCount;
	metrics.readyUnderrunCount = mReadyUnderrunCount;

	for (const StoredSlot& slot : mSlots)
	{
		switch (slot.state)
		{
		case OutputFrameSlotState::Free:
			++metrics.freeCount;
			break;
		case OutputFrameSlotState::Rendering:
			++metrics.renderingCount;
			++metrics.acquiredCount;
			break;
		case OutputFrameSlotState::Completed:
			++metrics.completedCount;
			break;
		case OutputFrameSlotState::Scheduled:
			++metrics.scheduledCount;
			break;
		}
	}

	return metrics;
}

bool SystemOutputFramePool::IsValidSlotLocked(const OutputFrameSlot& slot) const
{
	return slot.index < mSlots.size() && mSlots[slot.index].generation == slot.generation;
}

bool SystemOutputFramePool::TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState)
{
	if (!IsValidSlotLocked(slot) || mSlots[slot.index].state != expectedState)
		return false;

	mSlots[slot.index].state = nextState;
	return true;
}

void SystemOutputFramePool::FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot)
{
	StoredSlot& storedSlot = mSlots[index];
	slot.index = index;
	slot.generation = storedSlot.generation;
	slot.frame.bytes = storedSlot.bytes.empty() ? nullptr : storedSlot.bytes.data();
	slot.frame.rowBytes = static_cast<long>(mConfig.rowBytes);
	slot.frame.width = mConfig.width;
	slot.frame.height = mConfig.height;
	slot.frame.pixelFormat = mConfig.pixelFormat;
	slot.frame.nativeFrame = nullptr;
	slot.frame.nativeBuffer = slot.frame.bytes;
}

void SystemOutputFramePool::RemoveReadyIndexLocked(std::size_t index)
{
	mReadySlots.erase(std::remove(mReadySlots.begin(), mReadySlots.end(), index), mReadySlots.end());
}

bool SystemOutputFramePool::ReleaseSlotByIndexLocked(std::size_t index)
{
	if (index >= mSlots.size() || mSlots[index].state == OutputFrameSlotState::Free)
		return false;

	RemoveReadyIndexLocked(index);
	mSlots[index].state = OutputFrameSlotState::Free;
	return true;
}

std::size_t SystemOutputFramePool::FrameByteCount() const
{
	return static_cast<std::size_t>(mConfig.rowBytes) * static_cast<std::size_t>(mConfig.height);
}
