#pragma once

#include "RenderCadenceClock.h"
#include "RuntimeLayerModel.h"
#include "RuntimeShaderArtifact.h"
#include "RuntimeRenderScene.h"

#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <string>
#include <thread>

class SystemFrameExchange;
class InputFrameMailbox;
class InputFrameTexture;
class Bgra8ReadbackPipeline;

class RenderThread
{
public:
	struct Config
	{
		unsigned width = 1920;
		unsigned height = 1080;
		double frameDurationMilliseconds = 1000.0 / 59.94;
		std::size_t pboDepth = 6;
	};

	struct Metrics
	{
		uint64_t renderedFrames = 0;
		uint64_t completedReadbacks = 0;
		uint64_t acquireMisses = 0;
		uint64_t pboQueueMisses = 0;
		uint64_t clockOverruns = 0;
		uint64_t skippedFrames = 0;
		uint64_t shaderBuildsCommitted = 0;
		uint64_t shaderBuildFailures = 0;
		double renderFrameMilliseconds = 0.0;
		double renderFrameBudgetUsedPercent = 0.0;
		double renderFrameMaxMilliseconds = 0.0;
		double readbackQueueMilliseconds = 0.0;
		double completedReadbackCopyMilliseconds = 0.0;
		uint64_t inputFramesReceived = 0;
		uint64_t inputFramesDropped = 0;
		uint64_t inputConsumeMisses = 0;
		uint64_t inputUploadMisses = 0;
		std::size_t inputReadyFrames = 0;
		std::size_t inputReadingFrames = 0;
		double inputLatestAgeMilliseconds = 0.0;
		double inputUploadMilliseconds = 0.0;
		bool inputFormatSupported = true;
		bool inputSignalPresent = false;
	};

	RenderThread(SystemFrameExchange& frameExchange, Config config);
	RenderThread(SystemFrameExchange& frameExchange, InputFrameMailbox* inputMailbox, Config config);
	RenderThread(const RenderThread&) = delete;
	RenderThread& operator=(const RenderThread&) = delete;
	~RenderThread();

	bool Start(std::string& error);
	void Stop();
	void SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact);
	void SubmitRuntimeRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);

	Metrics GetMetrics() const;
	bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }

private:
	void ThreadMain();
	void SignalStarted();
	void SignalStartupFailure(const std::string& error);
	void CountRendered();
	void CountCompleted();
	void CountAcquireMiss();
	void PublishReadbackMetrics(const Bgra8ReadbackPipeline& readback);
	void PublishInputMetrics(const InputFrameTexture& inputTexture);
	void TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene);
	bool TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact);
	bool TryTakePendingRenderLayers(std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);

	SystemFrameExchange& mFrameExchange;
	InputFrameMailbox* mInputMailbox = nullptr;
	Config mConfig;
	std::thread mThread;
	std::atomic<bool> mStopping{ false };
	std::atomic<bool> mRunning{ false };

	mutable std::mutex mStartupMutex;
	std::condition_variable mStartupCondition;
	bool mStarted = false;
	std::string mStartupError;

	std::atomic<uint64_t> mRenderedFrames{ 0 };
	std::atomic<uint64_t> mCompletedReadbacks{ 0 };
	std::atomic<uint64_t> mAcquireMisses{ 0 };
	std::atomic<uint64_t> mPboQueueMisses{ 0 };
	std::atomic<uint64_t> mClockOverruns{ 0 };
	std::atomic<uint64_t> mSkippedFrames{ 0 };
	std::atomic<uint64_t> mShaderBuildsCommitted{ 0 };
	std::atomic<uint64_t> mShaderBuildFailures{ 0 };
	std::atomic<double> mRenderFrameMilliseconds{ 0.0 };
	std::atomic<double> mRenderFrameBudgetUsedPercent{ 0.0 };
	std::atomic<double> mRenderFrameMaxMilliseconds{ 0.0 };
	std::atomic<double> mReadbackQueueMilliseconds{ 0.0 };
	std::atomic<double> mCompletedReadbackCopyMilliseconds{ 0.0 };
	std::atomic<uint64_t> mInputFramesReceived{ 0 };
	std::atomic<uint64_t> mInputFramesDropped{ 0 };
	std::atomic<uint64_t> mInputConsumeMisses{ 0 };
	std::atomic<uint64_t> mInputUploadMisses{ 0 };
	std::atomic<std::size_t> mInputReadyFrames{ 0 };
	std::atomic<std::size_t> mInputReadingFrames{ 0 };
	std::atomic<double> mInputLatestAgeMilliseconds{ 0.0 };
	std::atomic<double> mInputUploadMilliseconds{ 0.0 };
	std::atomic<bool> mInputFormatSupported{ true };
	std::atomic<bool> mInputSignalPresent{ false };

	std::mutex mShaderArtifactMutex;
	bool mHasPendingShaderArtifact = false;
	RuntimeShaderArtifact mPendingShaderArtifact;

	std::mutex mRenderLayersMutex;
	bool mHasPendingRenderLayers = false;
	std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> mPendingRenderLayers;
};
