#include "OpenGLShaderPrograms.h"

#include <cstring>
#include <string>
#include <vector>

namespace
{
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
{
	if (!errorMessage || errorMessageSize <= 0)
		return;

	strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
}

std::size_t RequiredTemporaryRenderTargets(const std::vector<OpenGLRenderer::LayerProgram>& layerPrograms)
{
	// Only one layer renders at a time, so the pool needs to cover the widest
	// layer, not the sum of every intermediate pass in the stack.
	std::size_t requiredTargets = 0;
	for (const OpenGLRenderer::LayerProgram& layerProgram : layerPrograms)
	{
		const std::size_t internalPasses = layerProgram.passes.size() > 0 ? layerProgram.passes.size() - 1 : 0;
		if (internalPasses > requiredTargets)
			requiredTargets = internalPasses;
	}
	return requiredTargets;
}
}

OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeSnapshotProvider& runtimeSnapshotProvider) :
	mRenderer(renderer),
	mRuntimeSnapshotProvider(runtimeSnapshotProvider),
	mGlobalParamsBuffer(renderer),
	mCompiler(renderer, runtimeSnapshotProvider, mTextureBindings)
{
}

bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
	const RuntimeRenderStateSnapshot renderSnapshot =
		mRuntimeSnapshotProvider.PublishRenderStateSnapshot(inputFrameWidth, inputFrameHeight);
	const std::vector<RuntimeRenderState>& layerStates = renderSnapshot.states;
	std::string temporalError;
	const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
	if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError))
	{
		CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
		return false;
	}
	if (!mRenderer.TemporalHistory().EnsureResources(layerStates, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
	{
		CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
		return false;
	}
	if (mRenderer.ResourcesInitialized() &&
		!mRenderer.FeedbackBuffers().EnsureResources(layerStates, inputFrameWidth, inputFrameHeight, temporalError))
	{
		CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
		return false;
	}

	// Initial startup still compiles synchronously; auto-reload uses the build
	// queue so Slang/file work stays off the playback path.
	std::vector<LayerProgram> newPrograms;
	newPrograms.reserve(layerStates.size());

	for (const RuntimeRenderState& state : layerStates)
	{
		LayerProgram layerProgram;
		if (!mCompiler.CompileLayerProgram(state, layerProgram, errorMessageSize, errorMessage))
		{
			for (LayerProgram& program : newPrograms)
				DestroySingleLayerProgram(program);
			return false;
		}
		newPrograms.push_back(layerProgram);
	}

	std::string targetError;
	if (!mRenderer.ReserveTemporaryRenderTargets(RequiredTemporaryRenderTargets(newPrograms), inputFrameWidth, inputFrameHeight, targetError))
	{
		for (LayerProgram& program : newPrograms)
			DestroySingleLayerProgram(program);
		CopyErrorMessage(targetError, errorMessageSize, errorMessage);
		return false;
	}

	DestroyLayerPrograms();
	mRenderer.ReplaceLayerPrograms(newPrograms);
	mCommittedLayerStates = renderSnapshot.states;

	return true;
}

bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
{
	if (!preparedBuild.succeeded)
	{
		CopyErrorMessage(preparedBuild.message, errorMessageSize, errorMessage);
		return false;
	}

	std::string temporalError;
	const unsigned historyCap = mRuntimeSnapshotProvider.GetMaxTemporalHistoryFrames();
	if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.renderSnapshot.states, historyCap, temporalError))
	{
		CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
		return false;
	}
	if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.renderSnapshot.states, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
	{
		CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
		return false;
	}
	if (mRenderer.ResourcesInitialized() &&
		!mRenderer.FeedbackBuffers().EnsureResources(preparedBuild.renderSnapshot.states, inputFrameWidth, inputFrameHeight, temporalError))
	{
		CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
		return false;
	}

	// The prepared build already contains GLSL text for each pass. This commit
	// step performs the short GL work on the render thread.
	std::vector<LayerProgram> newPrograms;
	newPrograms.reserve(preparedBuild.layers.size());

	for (const PreparedLayerShader& preparedLayer : preparedBuild.layers)
	{
		LayerProgram layerProgram;
		if (!mCompiler.CompilePreparedLayerProgram(preparedLayer.state, preparedLayer.passes, layerProgram, errorMessageSize, errorMessage))
		{
			for (LayerProgram& program : newPrograms)
				DestroySingleLayerProgram(program);
			return false;
		}
		newPrograms.push_back(layerProgram);
	}

	std::string targetError;
	if (!mRenderer.ReserveTemporaryRenderTargets(RequiredTemporaryRenderTargets(newPrograms), inputFrameWidth, inputFrameHeight, targetError))
	{
		for (LayerProgram& program : newPrograms)
			DestroySingleLayerProgram(program);
		CopyErrorMessage(targetError, errorMessageSize, errorMessage);
		return false;
	}

	DestroyLayerPrograms();
	mRenderer.ReplaceLayerPrograms(newPrograms);
	mCommittedLayerStates = preparedBuild.renderSnapshot.states;

	return true;
}

bool OpenGLShaderPrograms::CompileDecodeShader(int errorMessageSize, char* errorMessage)
{
	return mCompiler.CompileDecodeShader(errorMessageSize, errorMessage);
}

bool OpenGLShaderPrograms::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
{
	return mCompiler.CompileOutputPackShader(errorMessageSize, errorMessage);
}

void OpenGLShaderPrograms::DestroySingleLayerProgram(LayerProgram& layerProgram)
{
	mRenderer.DestroySingleLayerProgram(layerProgram);
}

void OpenGLShaderPrograms::DestroyLayerPrograms()
{
	mRenderer.DestroyLayerPrograms();
}

void OpenGLShaderPrograms::DestroyDecodeShaderProgram()
{
	mRenderer.DestroyDecodeShaderProgram();
}

void OpenGLShaderPrograms::ResetTemporalHistoryState()
{
	mRenderer.TemporalHistory().ResetState();
}

void OpenGLShaderPrograms::ResetShaderFeedbackState()
{
	mRenderer.FeedbackBuffers().ResetState();
}

bool OpenGLShaderPrograms::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
{
	return mTextureBindings.UpdateTextBindingTexture(state, textBinding, error);
}

bool OpenGLShaderPrograms::UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
{
	return mGlobalParamsBuffer.Update(state, availableSourceHistoryLength, availableTemporalHistoryLength, feedbackAvailable);
}
