#include "GlobalParamsBuffer.h"

#include "GlRenderConstants.h"
#include "Std140Buffer.h"

#include <vector>

GlobalParamsBuffer::GlobalParamsBuffer(OpenGLRenderer& renderer) :
	mRenderer(renderer)
{
}

bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength, bool feedbackAvailable)
{
	std::vector<unsigned char>& buffer = mScratchBuffer;
	buffer.clear();
	buffer.reserve(512);

	AppendStd140Float(buffer, static_cast<float>(state.timeSeconds));
	AppendStd140Vec2(buffer, static_cast<float>(state.inputWidth), static_cast<float>(state.inputHeight));
	AppendStd140Vec2(buffer, static_cast<float>(state.outputWidth), static_cast<float>(state.outputHeight));
	AppendStd140Float(buffer, static_cast<float>(state.utcTimeSeconds));
	AppendStd140Float(buffer, static_cast<float>(state.utcOffsetSeconds));
	AppendStd140Float(buffer, static_cast<float>(state.startupRandom));
	AppendStd140Float(buffer, static_cast<float>(state.frameCount));
	AppendStd140Float(buffer, static_cast<float>(state.mixAmount));
	AppendStd140Float(buffer, static_cast<float>(state.bypass));
	const unsigned effectiveSourceHistoryLength = availableSourceHistoryLength < state.effectiveTemporalHistoryLength
		? availableSourceHistoryLength
		: state.effectiveTemporalHistoryLength;
	const unsigned effectiveTemporalHistoryLength = (state.temporalHistorySource == TemporalHistorySource::PreLayerInput)
		? (availableTemporalHistoryLength < state.effectiveTemporalHistoryLength ? availableTemporalHistoryLength : state.effectiveTemporalHistoryLength)
		: 0u;
	AppendStd140Int(buffer, static_cast<int>(effectiveSourceHistoryLength));
	AppendStd140Int(buffer, static_cast<int>(effectiveTemporalHistoryLength));
	AppendStd140Int(buffer, feedbackAvailable ? 1 : 0);

	for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
	{
		auto valueIt = state.parameterValues.find(definition.id);
		const ShaderParameterValue value = valueIt != state.parameterValues.end()
			? valueIt->second
			: ShaderParameterValue();

		switch (definition.type)
		{
		case ShaderParameterType::Float:
			AppendStd140Float(buffer, value.numberValues.empty() ? 0.0f : static_cast<float>(value.numberValues[0]));
			break;
		case ShaderParameterType::Vec2:
			AppendStd140Vec2(buffer,
				value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 0.0f,
				value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 0.0f);
			break;
		case ShaderParameterType::Color:
			AppendStd140Vec4(buffer,
				value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 1.0f,
				value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 1.0f,
				value.numberValues.size() > 2 ? static_cast<float>(value.numberValues[2]) : 1.0f,
				value.numberValues.size() > 3 ? static_cast<float>(value.numberValues[3]) : 1.0f);
			break;
		case ShaderParameterType::Boolean:
			AppendStd140Int(buffer, value.booleanValue ? 1 : 0);
			break;
		case ShaderParameterType::Enum:
		{
			int selectedIndex = 0;
			for (std::size_t optionIndex = 0; optionIndex < definition.enumOptions.size(); ++optionIndex)
			{
				if (definition.enumOptions[optionIndex].value == value.enumValue)
				{
					selectedIndex = static_cast<int>(optionIndex);
					break;
				}
			}
			AppendStd140Int(buffer, selectedIndex);
			break;
		}
		case ShaderParameterType::Text:
			break;
		case ShaderParameterType::Trigger:
			AppendStd140Int(buffer, value.numberValues.empty() ? 0 : static_cast<int>(value.numberValues[0]));
			AppendStd140Float(buffer, value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : -1000000.0f);
			break;
		}
	}

	buffer.resize(AlignStd140(buffer.size(), 16), 0);

	glBindBuffer(GL_UNIFORM_BUFFER, mRenderer.GlobalParamsUBO());
	if (mRenderer.GlobalParamsUBOSize() != static_cast<GLsizeiptr>(buffer.size()))
	{
		glBufferData(GL_UNIFORM_BUFFER, static_cast<GLsizeiptr>(buffer.size()), buffer.data(), GL_DYNAMIC_DRAW);
		mRenderer.SetGlobalParamsUBOSize(static_cast<GLsizeiptr>(buffer.size()));
	}
	else
	{
		glBufferSubData(GL_UNIFORM_BUFFER, 0, static_cast<GLsizeiptr>(buffer.size()), buffer.data());
	}
	glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mRenderer.GlobalParamsUBO());
	glBindBuffer(GL_UNIFORM_BUFFER, 0);

	return true;
}
