#pragma once

#include "GLExtensions.h"
#include "VideoIOTypes.h"

#include <functional>
#include <vector>

class OpenGLRenderer;
class HealthTelemetry;
class RuntimeSnapshotProvider;

struct RenderPipelineFrameContext
{
	VideoIOState videoState;
	VideoIOCompletion completion;
};

class OpenGLRenderPipeline
{
public:
	using RenderEffectCallback = std::function<void()>;
	using OutputReadyCallback = std::function<void()>;
	using PaintCallback = std::function<void()>;

	OpenGLRenderPipeline(
		OpenGLRenderer& renderer,
		RuntimeSnapshotProvider& runtimeSnapshotProvider,
		HealthTelemetry& healthTelemetry,
		RenderEffectCallback renderEffect,
		OutputReadyCallback outputReady,
		PaintCallback paint);
	~OpenGLRenderPipeline();

	bool RenderFrame(const RenderPipelineFrameContext& context, VideoIOOutputFrame& outputFrame);

private:
	enum class OutputReadbackMode
	{
		AsyncPbo,
		Synchronous,
		CachedOnly
	};

	struct AsyncReadbackSlot
	{
		GLuint pixelPackBuffer = 0;
		GLsync fence = nullptr;
		std::size_t sizeBytes = 0;
		bool inFlight = false;
	};

	struct OutputReadbackTiming
	{
		double fenceWaitMilliseconds = 0.0;
		double mapMilliseconds = 0.0;
		double copyMilliseconds = 0.0;
		double cachedCopyMilliseconds = 0.0;
		double asyncQueueMilliseconds = 0.0;
		double asyncQueueBufferMilliseconds = 0.0;
		double asyncQueueSetupMilliseconds = 0.0;
		double asyncQueueReadPixelsMilliseconds = 0.0;
		double asyncQueueFenceMilliseconds = 0.0;
		double syncReadMilliseconds = 0.0;
		bool asyncReadbackMissed = false;
		bool cachedFallbackUsed = false;
		bool syncFallbackUsed = false;
	};

	bool EnsureAsyncReadbackBuffers(std::size_t requiredBytes);
	void ResetAsyncReadbackState();
	void FlushAsyncReadbackPipeline();
	bool QueueAsyncReadback(const VideoIOState& state, OutputReadbackTiming& timing);
	bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds, OutputReadbackTiming& timing);
	void CacheOutputFrame(const VideoIOOutputFrame& outputFrame);
	bool TryCopyCachedOutputFrame(VideoIOOutputFrame& outputFrame, OutputReadbackTiming& timing) const;
	void ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes, OutputReadbackTiming& timing);
	void PackOutputForBgra8(const VideoIOState& state);
	void PackOutputFor10Bit(const VideoIOState& state);
	OutputReadbackTiming ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);
	static OutputReadbackMode ReadOutputReadbackModeFromEnvironment();
	static std::size_t ReadAsyncReadbackDepthFromEnvironment();

	OpenGLRenderer& mRenderer;
	RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
	HealthTelemetry& mHealthTelemetry;
	RenderEffectCallback mRenderEffect;
	OutputReadyCallback mOutputReady;
	PaintCallback mPaint;
	OutputReadbackMode mOutputReadbackMode = OutputReadbackMode::AsyncPbo;
	std::vector<AsyncReadbackSlot> mAsyncReadbackSlots;
	std::size_t mAsyncReadbackDepth = 0;
	std::size_t mAsyncReadbackWriteIndex = 0;
	std::size_t mAsyncReadbackReadIndex = 0;
	std::size_t mAsyncReadbackBytes = 0;
	GLenum mAsyncReadbackFormat = GL_BGRA;
	GLenum mAsyncReadbackType = GL_UNSIGNED_INT_8_8_8_8_REV;
	GLuint mAsyncReadbackFramebuffer = 0;
	std::vector<unsigned char> mCachedOutputFrame;
};
