#pragma once

#include "VideoIOTypes.h"

#include <cstddef>
#include <cstdint>
#include <deque>
#include <mutex>
#include <vector>

enum class OutputFrameSlotState
{
	Free,
	Rendering,
	Completed,
	Scheduled
};

struct SystemOutputFramePoolConfig
{
	unsigned width = 0;
	unsigned height = 0;
	VideoIOPixelFormat pixelFormat = VideoIOPixelFormat::Bgra8;
	unsigned rowBytes = 0;
	std::size_t capacity = 0;
};

struct OutputFrameSlot
{
	VideoIOOutputFrame frame;
	std::size_t index = 0;
	uint64_t generation = 0;
};

struct SystemOutputFramePoolMetrics
{
	std::size_t capacity = 0;
	std::size_t freeCount = 0;
	std::size_t renderingCount = 0;
	std::size_t completedCount = 0;
	std::size_t scheduledCount = 0;
	std::size_t acquiredCount = 0;
	std::size_t readyCount = 0;
	std::size_t consumedCount = 0;
	uint64_t acquireMissCount = 0;
	uint64_t readyUnderrunCount = 0;
};

class SystemOutputFramePool
{
public:
	SystemOutputFramePool() = default;
	explicit SystemOutputFramePool(const SystemOutputFramePoolConfig& config);

	void Configure(const SystemOutputFramePoolConfig& config);
	SystemOutputFramePoolConfig Config() const;

	bool AcquireFreeSlot(OutputFrameSlot& slot);
	bool AcquireRenderingSlot(OutputFrameSlot& slot);
	bool PublishReadySlot(const OutputFrameSlot& slot);
	bool PublishCompletedSlot(const OutputFrameSlot& slot);
	bool ConsumeReadySlot(OutputFrameSlot& slot);
	bool ConsumeCompletedSlot(OutputFrameSlot& slot);
	bool MarkScheduled(const OutputFrameSlot& slot);
	bool MarkScheduledByBuffer(void* bytes);
	bool ReleaseSlot(const OutputFrameSlot& slot);
	bool ReleaseScheduledSlot(const OutputFrameSlot& slot);
	bool ReleaseSlotByBuffer(void* bytes);
	void Clear();

	SystemOutputFramePoolMetrics GetMetrics() const;

private:
	struct StoredSlot
	{
		std::vector<unsigned char> bytes;
		OutputFrameSlotState state = OutputFrameSlotState::Free;
		uint64_t generation = 1;
	};

	bool IsValidSlotLocked(const OutputFrameSlot& slot) const;
	bool TransitionSlotLocked(const OutputFrameSlot& slot, OutputFrameSlotState expectedState, OutputFrameSlotState nextState);
	void FillOutputSlotLocked(std::size_t index, OutputFrameSlot& slot);
	void RemoveReadyIndexLocked(std::size_t index);
	bool ReleaseSlotByIndexLocked(std::size_t index);
	std::size_t FrameByteCount() const;

	mutable std::mutex mMutex;
	SystemOutputFramePoolConfig mConfig;
	std::vector<StoredSlot> mSlots;
	std::deque<std::size_t> mReadySlots;
	uint64_t mAcquireMissCount = 0;
	uint64_t mReadyUnderrunCount = 0;
};
