#pragma once

#include "RuntimeLayerModel.h"
#include "RuntimeShaderPrepareWorker.h"
#include "RuntimeShaderRenderer.h"

#include <windows.h>

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

class RuntimeRenderScene
{
public:
	RuntimeRenderScene() = default;
	RuntimeRenderScene(const RuntimeRenderScene&) = delete;
	RuntimeRenderScene& operator=(const RuntimeRenderScene&) = delete;
	~RuntimeRenderScene();

	bool StartPrepareWorker(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error);
	bool CommitRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers, std::string& error, bool* structuralChange = nullptr);
	bool HasLayers();
	void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture = 0);
	void ShutdownGl();

private:
	struct LayerProgram
	{
		std::string layerId;
		std::string shaderId;
		std::string sourceFingerprint;
		std::string pendingFingerprint;
		std::vector<RuntimePreparedShaderProgram> pendingPreparedPrograms;
		struct PassProgram
		{
			std::string passId;
			std::vector<std::string> inputNames;
			std::string outputName;
			std::unique_ptr<RuntimeShaderRenderer> renderer;
		};
		std::vector<PassProgram> passes;
	};

	void ConsumePreparedPrograms();
	void ReleasePendingPrograms(LayerProgram& layer);
	void TryCommitPendingPrograms(LayerProgram& layer);
	bool EnsureLayerTargets(unsigned width, unsigned height);
	void DestroyLayerTargets();
	GLuint RenderLayer(LayerProgram& layer, uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture, GLuint layerInputTexture, GLuint outputFramebuffer, bool renderToOutput);
	LayerProgram* FindLayer(const std::string& layerId);
	const LayerProgram* FindLayer(const std::string& layerId) const;
	LayerProgram::PassProgram* FindPass(LayerProgram& layer, const std::string& passId);
	static std::string Fingerprint(const RuntimeShaderArtifact& artifact);

	RuntimeShaderPrepareWorker mPrepareWorker;
	std::vector<LayerProgram> mLayers;
	std::vector<std::string> mLayerOrder;
	GLuint mLayerFramebuffers[4] = {};
	GLuint mLayerTextures[4] = {};
	unsigned mLayerTargetWidth = 0;
	unsigned mLayerTargetHeight = 0;
};
