#include "OpenGLRenderer.h"

#include "GlRenderConstants.h"

namespace
{
	void ConfigureByteFrameTexture(unsigned width, unsigned height)
	{
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	}
}

bool OpenGLRenderer::InitializeResources(unsigned inputFrameWidth, unsigned inputFrameHeight, unsigned captureTextureWidth, unsigned outputFrameWidth, unsigned outputFrameHeight, unsigned outputPackTextureWidth, std::string& error)
{
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
	glDisable(GL_DEPTH_TEST);

	glGenBuffers(1, &mTextureUploadBuffer);

	glGenTextures(1, &mCaptureTexture);
	glBindTexture(GL_TEXTURE_2D, mCaptureTexture);
	ConfigureByteFrameTexture(captureTextureWidth, inputFrameHeight);
	glBindTexture(GL_TEXTURE_2D, 0);

	glGenRenderbuffers(1, &mIdColorBuf);
	glGenRenderbuffers(1, &mIdDepthBuf);
	glGenVertexArrays(1, &mFullscreenVAO);
	glGenBuffers(1, &mGlobalParamsUBO);

	if (!mRenderTargets.Create(RenderTargetId::Decoded, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "decode", error))
		return false;
	if (!mRenderTargets.Create(RenderTargetId::LayerTemp, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "layer", error))
		return false;
	if (!mRenderTargets.Create(RenderTargetId::Composite, inputFrameWidth, inputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "composite", error))
		return false;

	glBindFramebuffer(GL_FRAMEBUFFER, CompositeFramebuffer());
	glBindRenderbuffer(GL_RENDERBUFFER, mIdDepthBuf);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, inputFrameWidth, inputFrameHeight);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER, mIdDepthBuf);
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	{
		error = "Cannot initialize framebuffer.";
		return false;
	}

	if (!mRenderTargets.Create(RenderTargetId::Output, outputFrameWidth, outputFrameHeight, GL_RGBA16F, GL_RGBA, GL_FLOAT, "output", error))
		return false;
	if (!mRenderTargets.Create(RenderTargetId::OutputPack, outputPackTextureWidth, outputFrameHeight, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, "output pack", error))
		return false;

	glBindTexture(GL_TEXTURE_2D, 0);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glBindVertexArray(mFullscreenVAO);
	glBindVertexArray(0);
	glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsUBO);
	glBufferData(GL_UNIFORM_BUFFER, 1024, NULL, GL_DYNAMIC_DRAW);
	glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsUBO);
	glBindBuffer(GL_UNIFORM_BUFFER, 0);

	mResourcesInitialized = true;
	return true;
}

void OpenGLRenderer::SetDecodeShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader)
{
	mDecodeProgram = program;
	mDecodeVertexShader = vertexShader;
	mDecodeFragmentShader = fragmentShader;
	mDecodePackedResolutionLocation = program != 0 ? glGetUniformLocation(program, "uPackedVideoResolution") : -1;
	mDecodeDecodedResolutionLocation = program != 0 ? glGetUniformLocation(program, "uDecodedVideoResolution") : -1;
	mDecodeInputPixelFormatLocation = program != 0 ? glGetUniformLocation(program, "uInputPixelFormat") : -1;
}

void OpenGLRenderer::SetOutputPackShaderProgram(GLuint program, GLuint vertexShader, GLuint fragmentShader)
{
	mOutputPackProgram = program;
	mOutputPackVertexShader = vertexShader;
	mOutputPackFragmentShader = fragmentShader;
	mOutputPackResolutionLocation = program != 0 ? glGetUniformLocation(program, "uOutputVideoResolution") : -1;
	mOutputPackActiveWordsLocation = program != 0 ? glGetUniformLocation(program, "uActiveV210Words") : -1;
	mOutputPackFormatLocation = program != 0 ? glGetUniformLocation(program, "uOutputPackFormat") : -1;
}

bool OpenGLRenderer::ReserveTemporaryRenderTargets(std::size_t count, unsigned width, unsigned height, std::string& error)
{
	return mRenderTargets.ReserveTemporaryTargets(count, width, height, GL_RGBA16F, GL_RGBA, GL_FLOAT, error);
}

void OpenGLRenderer::ResizeView(int width, int height)
{
	mViewWidth = width;
	mViewHeight = height;
}

void OpenGLRenderer::PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigned outputFrameHeight)
{
	int destWidth = mViewWidth;
	int destHeight = mViewHeight;
	int destX = 0;
	int destY = 0;

	if (outputFrameWidth > 0 && outputFrameHeight > 0 && mViewWidth > 0 && mViewHeight > 0)
	{
		const double frameAspect = static_cast<double>(outputFrameWidth) / static_cast<double>(outputFrameHeight);
		const double viewAspect = static_cast<double>(mViewWidth) / static_cast<double>(mViewHeight);

		if (viewAspect > frameAspect)
		{
			destHeight = mViewHeight;
			destWidth = static_cast<int>(destHeight * frameAspect + 0.5);
			destX = (mViewWidth - destWidth) / 2;
		}
		else
		{
			destWidth = mViewWidth;
			destHeight = static_cast<int>(destWidth / frameAspect + 0.5);
			destY = (mViewHeight - destHeight) / 2;
		}
	}

	glBindFramebuffer(GL_READ_FRAMEBUFFER, OutputFramebuffer());
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	glDisable(GL_SCISSOR_TEST);
	glViewport(0, 0, mViewWidth, mViewHeight);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	glBlitFramebuffer(0, 0, outputFrameWidth, outputFrameHeight, destX, destY, destX + destWidth, destY + destHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);

	SwapBuffers(hdc);
}

void OpenGLRenderer::DestroyResources()
{
	if (mFullscreenVAO != 0)
		glDeleteVertexArrays(1, &mFullscreenVAO);
	if (mGlobalParamsUBO != 0)
		glDeleteBuffers(1, &mGlobalParamsUBO);
	if (mIdColorBuf != 0)
		glDeleteRenderbuffers(1, &mIdColorBuf);
	if (mIdDepthBuf != 0)
		glDeleteRenderbuffers(1, &mIdDepthBuf);
	if (mCaptureTexture != 0)
		glDeleteTextures(1, &mCaptureTexture);
	if (mTextureUploadBuffer != 0)
		glDeleteBuffers(1, &mTextureUploadBuffer);
	mRenderTargets.Destroy();

	mFullscreenVAO = 0;
	mGlobalParamsUBO = 0;
	mIdColorBuf = 0;
	mIdDepthBuf = 0;
	mCaptureTexture = 0;
	mTextureUploadBuffer = 0;
	mGlobalParamsUBOSize = 0;
	mResourcesInitialized = false;

	mTemporalHistory.DestroyResources();
	mFeedbackBuffers.DestroyResources();
	DestroyLayerPrograms();
	DestroyDecodeShaderProgram();
	DestroyOutputPackShaderProgram();
}

void OpenGLRenderer::DestroySingleLayerProgram(LayerProgram& layerProgram)
{
	for (LayerProgram::PassProgram& passProgram : layerProgram.passes)
	{
		for (LayerProgram::TextureBinding& binding : passProgram.textureBindings)
		{
			if (binding.texture != 0)
			{
				glDeleteTextures(1, &binding.texture);
				binding.texture = 0;
			}
		}
		passProgram.textureBindings.clear();

		for (LayerProgram::TextBinding& binding : passProgram.textBindings)
		{
			if (binding.texture != 0)
			{
				glDeleteTextures(1, &binding.texture);
				binding.texture = 0;
			}
		}
		passProgram.textBindings.clear();

		if (passProgram.program != 0)
		{
			glDeleteProgram(passProgram.program);
			passProgram.program = 0;
		}

		if (passProgram.fragmentShader != 0)
		{
			glDeleteShader(passProgram.fragmentShader);
			passProgram.fragmentShader = 0;
		}

		if (passProgram.vertexShader != 0)
		{
			glDeleteShader(passProgram.vertexShader);
			passProgram.vertexShader = 0;
		}
	}
	layerProgram.passes.clear();
}

void OpenGLRenderer::DestroyLayerPrograms()
{
	for (LayerProgram& layerProgram : mLayerPrograms)
		DestroySingleLayerProgram(layerProgram);
	mLayerPrograms.clear();
}

void OpenGLRenderer::DestroyDecodeShaderProgram()
{
	if (mDecodeProgram != 0)
	{
		glDeleteProgram(mDecodeProgram);
		mDecodeProgram = 0;
	}
	mDecodePackedResolutionLocation = -1;
	mDecodeDecodedResolutionLocation = -1;
	mDecodeInputPixelFormatLocation = -1;

	if (mDecodeFragmentShader != 0)
	{
		glDeleteShader(mDecodeFragmentShader);
		mDecodeFragmentShader = 0;
	}

	if (mDecodeVertexShader != 0)
	{
		glDeleteShader(mDecodeVertexShader);
		mDecodeVertexShader = 0;
	}
}

void OpenGLRenderer::DestroyOutputPackShaderProgram()
{
	if (mOutputPackProgram != 0)
	{
		glDeleteProgram(mOutputPackProgram);
		mOutputPackProgram = 0;
	}
	mOutputPackResolutionLocation = -1;
	mOutputPackActiveWordsLocation = -1;
	mOutputPackFormatLocation = -1;

	if (mOutputPackFragmentShader != 0)
	{
		glDeleteShader(mOutputPackFragmentShader);
		mOutputPackFragmentShader = 0;
	}

	if (mOutputPackVertexShader != 0)
	{
		glDeleteShader(mOutputPackVertexShader);
		mOutputPackVertexShader = 0;
	}
}
