#pragma once

#include "../frames/SystemFrameTypes.h"
#include "VideoIOEdges.h"
#include "VideoIOTypes.h"

#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <thread>

namespace RenderCadenceCompositor
{
struct VideoOutputThreadConfig
{
	std::size_t targetBufferedFrames = 4;
	std::chrono::milliseconds idleSleep = std::chrono::milliseconds(1);
};

struct VideoOutputThreadMetrics
{
	uint64_t scheduledFrames = 0;
	uint64_t completedPollMisses = 0;
	uint64_t scheduleFailures = 0;
};

template <typename SystemFrameExchange>
class VideoOutputThread
{
public:
	VideoOutputThread(IVideoOutputEdge& output, SystemFrameExchange& exchange, VideoOutputThreadConfig config = VideoOutputThreadConfig()) :
		mOutput(output),
		mExchange(exchange),
		mConfig(config)
	{
	}

	VideoOutputThread(const VideoOutputThread&) = delete;
	VideoOutputThread& operator=(const VideoOutputThread&) = delete;

	~VideoOutputThread()
	{
		Stop();
	}

	bool Start()
	{
		if (mRunning)
			return true;
		mStopping = false;
		mThread = std::thread([this]() { ThreadMain(); });
		mRunning = true;
		return true;
	}

	void Stop()
	{
		mStopping = true;
		if (mThread.joinable())
			mThread.join();
		mRunning = false;
	}

	VideoOutputThreadMetrics Metrics() const
	{
		VideoOutputThreadMetrics metrics;
		metrics.scheduledFrames = mScheduledFrames.load();
		metrics.completedPollMisses = mCompletedPollMisses.load();
		metrics.scheduleFailures = mScheduleFailures.load();
		return metrics;
	}

private:
	void ThreadMain()
	{
		while (!mStopping)
		{
			const auto exchangeMetrics = mExchange.Metrics();
			const auto outputMetrics = mOutput.Metrics();
			const std::size_t bufferedFrames = outputMetrics.actualBufferedFramesAvailable
				? static_cast<std::size_t>(outputMetrics.actualBufferedFrames)
				: exchangeMetrics.scheduledCount;
			if (bufferedFrames >= mConfig.targetBufferedFrames)
			{
				std::this_thread::sleep_for(mConfig.idleSleep);
				continue;
			}
			SystemFrame frame;
			if (!mExchange.ConsumeCompletedForSchedule(frame))
			{
				++mCompletedPollMisses;
				std::this_thread::sleep_for(mConfig.idleSleep);
				continue;
			}

			VideoIOOutputFrame outputFrame;
			outputFrame.bytes = frame.bytes;
			outputFrame.nativeBuffer = frame.bytes;
			outputFrame.rowBytes = frame.rowBytes;
			outputFrame.width = frame.width;
			outputFrame.height = frame.height;
			outputFrame.pixelFormat = frame.pixelFormat;

			if (!mOutput.ScheduleFrame(outputFrame))
			{
				++mScheduleFailures;
				mExchange.ReleaseScheduledByBytes(frame.bytes);
				std::this_thread::sleep_for(mConfig.idleSleep);
				continue;
			}

			++mScheduledFrames;
		}
	}

	IVideoOutputEdge& mOutput;
	SystemFrameExchange& mExchange;
	VideoOutputThreadConfig mConfig;
	std::thread mThread;
	std::atomic<bool> mStopping{ false };
	std::atomic<bool> mRunning{ false };
	std::atomic<uint64_t> mScheduledFrames{ 0 };
	std::atomic<uint64_t> mCompletedPollMisses{ 0 };
	std::atomic<uint64_t> mScheduleFailures{ 0 };
};
}
