#pragma once

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

#include <array>
#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:
	struct AsyncReadbackSlot
	{
		GLuint pixelPackBuffer = 0;
		GLsync fence = nullptr;
		std::size_t sizeBytes = 0;
		bool inFlight = false;
	};

	bool EnsureAsyncReadbackBuffers(std::size_t requiredBytes);
	void ResetAsyncReadbackState();
	void FlushAsyncReadbackPipeline();
	void QueueAsyncReadback(const VideoIOState& state);
	bool TryConsumeAsyncReadback(VideoIOOutputFrame& outputFrame, GLuint64 timeoutNanoseconds);
	void CacheOutputFrame(const VideoIOOutputFrame& outputFrame);
	void ReadOutputFrameSynchronously(const VideoIOState& state, void* destinationBytes);
	void PackOutputFor10Bit(const VideoIOState& state);
	void ReadOutputFrame(const VideoIOState& state, VideoIOOutputFrame& outputFrame);

	OpenGLRenderer& mRenderer;
	RuntimeSnapshotProvider& mRuntimeSnapshotProvider;
	HealthTelemetry& mHealthTelemetry;
	RenderEffectCallback mRenderEffect;
	OutputReadyCallback mOutputReady;
	PaintCallback mPaint;
	std::array<AsyncReadbackSlot, 3> mAsyncReadbackSlots;
	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;
};
