#pragma once

#include "GLExtensions.h"
#include "RenderTargetPool.h"
#include "ShaderFeedbackBuffers.h"
#include "ShaderTypes.h"
#include "TemporalHistoryBuffers.h"

#include <windows.h>

#include <filesystem>
#include <gl/gl.h>
#include <string>
#include <vector>

class OpenGLRenderer
{
public:
	struct LayerProgram
	{
		struct TextureBinding
		{
			std::string samplerName;
			std::filesystem::path sourcePath;
			GLuint texture = 0;
		};

		struct TextBinding
		{
			std::string parameterId;
			std::string samplerName;
			std::string fontId;
			GLuint texture = 0;
			std::string renderedText;
			unsigned renderedWidth = 0;
			unsigned renderedHeight = 0;
		};

		std::string layerId;
		std::string shaderId;

		struct PassProgram
		{
			std::string passId;
			std::vector<std::string> inputNames;
			std::string outputName;
			GLuint shaderTextureBase = 0;
			GLuint program = 0;
			GLuint vertexShader = 0;
			GLuint fragmentShader = 0;
			std::vector<TextureBinding> textureBindings;
			std::vector<TextBinding> textBindings;
		};

		std::vector<PassProgram> passes;
	};

	GLuint CaptureTexture() const { return mCaptureTexture; }
	GLuint DecodedTexture() const { return mRenderTargets.Texture(RenderTargetId::Decoded); }
	GLuint LayerTempTexture() const { return mRenderTargets.Texture(RenderTargetId::LayerTemp); }
	GLuint CompositeTexture() const { return mRenderTargets.Texture(RenderTargetId::Composite); }
	GLuint OutputTexture() const { return mRenderTargets.Texture(RenderTargetId::Output); }
	GLuint OutputPackTexture() const { return mRenderTargets.Texture(RenderTargetId::OutputPack); }
	GLuint TextureUploadBuffer() const { return mTextureUploadBuffer; }
	GLuint DecodeFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Decoded); }
	GLuint LayerTempFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::LayerTemp); }
	GLuint CompositeFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Composite); }
	GLuint OutputFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::Output); }
	GLuint OutputPackFramebuffer() const { return mRenderTargets.Framebuffer(RenderTargetId::OutputPack); }
	GLuint FullscreenVertexArray() const { return mFullscreenVAO; }
	GLuint GlobalParamsUBO() const { return mGlobalParamsUBO; }
	GLuint DecodeProgram() const { return mDecodeProgram; }
	GLuint OutputPackProgram() const { return mOutputPackProgram; }
	GLint DecodePackedResolutionLocation() const { return mDecodePackedResolutionLocation; }
	GLint DecodeDecodedResolutionLocation() const { return mDecodeDecodedResolutionLocation; }
	GLint DecodeInputPixelFormatLocation() const { return mDecodeInputPixelFormatLocation; }
	GLint OutputPackResolutionLocation() const { return mOutputPackResolutionLocation; }
	GLint OutputPackActiveWordsLocation() const { return mOutputPackActiveWordsLocation; }
	GLint OutputPackFormatLocation() const { return mOutputPackFormatLocation; }
	GLsizeiptr GlobalParamsUBOSize() const { return mGlobalParamsUBOSize; }
	void SetGlobalParamsUBOSize(GLsizeiptr size) { mGlobalParamsUBOSize = size; }
	bool ResourcesInitialized() const { return mResourcesInitialized; }
	void ReplaceLayerPrograms(std::vector<LayerProgram>& newPrograms) { mLayerPrograms.swap(newPrograms); }
	std::vector<LayerProgram>& LayerPrograms() { return mLayerPrograms; }
	const std::vector<LayerProgram>& LayerPrograms() const { return mLayerPrograms; }
	bool ReserveTemporaryRenderTargets(std::size_t count, unsigned width, unsigned height, std::string& error);
	const RenderTarget& TemporaryRenderTarget(std::size_t index) const { return mRenderTargets.TemporaryTarget(index); }
	std::size_t TemporaryRenderTargetCount() const { return mRenderTargets.TemporaryTargetCount(); }
	TemporalHistoryBuffers& TemporalHistory() { return mTemporalHistory; }
	const TemporalHistoryBuffers& TemporalHistory() const { return mTemporalHistory; }
	ShaderFeedbackBuffers& FeedbackBuffers() { return mFeedbackBuffers; }
	const ShaderFeedbackBuffers& FeedbackBuffers() const { return mFeedbackBuffers; }
	void SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
	void SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader);
	bool InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error);
	void ResizeView(int width, int height);
	void PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigned outputFrameHeight);
	void DestroyResources();
	void DestroySingleLayerProgram(LayerProgram& layerProgram);
	void DestroyLayerPrograms();
	void DestroyDecodeShaderProgram();
	void DestroyOutputPackShaderProgram();

private:
	GLuint mCaptureTexture = 0;
	GLuint mTextureUploadBuffer = 0;
	GLuint mIdColorBuf = 0;
	GLuint mIdDepthBuf = 0;
	GLuint mFullscreenVAO = 0;
	GLuint mGlobalParamsUBO = 0;
	GLuint mDecodeProgram = 0;
	GLuint mDecodeVertexShader = 0;
	GLuint mDecodeFragmentShader = 0;
	GLint mDecodePackedResolutionLocation = -1;
	GLint mDecodeDecodedResolutionLocation = -1;
	GLint mDecodeInputPixelFormatLocation = -1;
	GLuint mOutputPackProgram = 0;
	GLuint mOutputPackVertexShader = 0;
	GLuint mOutputPackFragmentShader = 0;
	GLint mOutputPackResolutionLocation = -1;
	GLint mOutputPackActiveWordsLocation = -1;
	GLint mOutputPackFormatLocation = -1;
	GLsizeiptr mGlobalParamsUBOSize = 0;
	bool mResourcesInitialized = false;
	int mViewWidth = 0;
	int mViewHeight = 0;
	std::vector<LayerProgram> mLayerPrograms;
	RenderTargetPool mRenderTargets;
	TemporalHistoryBuffers mTemporalHistory;
	ShaderFeedbackBuffers mFeedbackBuffers;
};
