#include "RuntimeShaderParams.h"

#include "Std140Buffer.h"

#include <chrono>

namespace
{
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition)
{
	ShaderParameterValue value;
	switch (definition.type)
	{
	case ShaderParameterType::Float:
		value.numberValues = definition.defaultNumbers.empty() ? std::vector<double>{ 0.0 } : definition.defaultNumbers;
		break;
	case ShaderParameterType::Vec2:
		value.numberValues = definition.defaultNumbers.size() == 2 ? definition.defaultNumbers : std::vector<double>{ 0.0, 0.0 };
		break;
	case ShaderParameterType::Color:
		value.numberValues = definition.defaultNumbers.size() == 4 ? definition.defaultNumbers : std::vector<double>{ 1.0, 1.0, 1.0, 1.0 };
		break;
	case ShaderParameterType::Boolean:
		value.booleanValue = definition.defaultBoolean;
		break;
	case ShaderParameterType::Enum:
		value.enumValue = definition.defaultEnumValue;
		break;
	case ShaderParameterType::Text:
		value.textValue = definition.defaultTextValue;
		break;
	case ShaderParameterType::Trigger:
		value.numberValues = { 0.0, -1000000.0 };
		break;
	}
	return value;
}

int EnumIndexForDefault(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
{
	for (std::size_t optionIndex = 0; optionIndex < definition.enumOptions.size(); ++optionIndex)
	{
		if (definition.enumOptions[optionIndex].value == value.enumValue)
			return static_cast<int>(optionIndex);
	}
	return 0;
}

double UtcSecondsOfDay()
{
	const auto now = std::chrono::system_clock::now();
	const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
	const long long secondsPerDay = 24 * 60 * 60;
	long long daySeconds = seconds % secondsPerDay;
	if (daySeconds < 0)
		daySeconds += secondsPerDay;
	return static_cast<double>(daySeconds);
}
}

std::vector<unsigned char> BuildRuntimeShaderGlobalParamsStd140(
	const RuntimeShaderArtifact& artifact,
	uint64_t frameIndex,
	unsigned width,
	unsigned height)
{
	std::vector<unsigned char> buffer;
	buffer.reserve(512);

	AppendStd140Float(buffer, static_cast<float>(frameIndex) / 60.0f);
	AppendStd140Vec2(buffer, static_cast<float>(width), static_cast<float>(height));
	AppendStd140Vec2(buffer, static_cast<float>(width), static_cast<float>(height));
	AppendStd140Float(buffer, static_cast<float>(UtcSecondsOfDay()));
	AppendStd140Float(buffer, 0.0f);
	AppendStd140Float(buffer, 0.37f);
	AppendStd140Float(buffer, static_cast<float>(frameIndex));
	AppendStd140Float(buffer, 1.0f);
	AppendStd140Float(buffer, 0.0f);
	AppendStd140Int(buffer, 0);
	AppendStd140Int(buffer, 0);
	AppendStd140Int(buffer, 0);

	for (const ShaderParameterDefinition& definition : artifact.parameterDefinitions)
	{
		const auto valueIt = artifact.parameterValues.find(definition.id);
		const ShaderParameterValue value = valueIt == artifact.parameterValues.end()
			? DefaultValueForDefinition(definition)
			: valueIt->second;
		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:
			AppendStd140Int(buffer, EnumIndexForDefault(definition, value));
			break;
		case ShaderParameterType::Text:
		{
			const auto metricsIt = artifact.textTextureMetrics.find(definition.id);
			const RuntimeTextTextureMetrics metrics = metricsIt == artifact.textTextureMetrics.end()
				? RuntimeTextTextureMetrics()
				: metricsIt->second;
			AppendStd140Float(buffer, metrics.activeWidthScale);
			AppendStd140Float(buffer, metrics.aspect);
			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);
	return buffer;
}
