#include "stdafx.h"
#include "RuntimeParameterUtils.h"

#include <algorithm>
#include <cmath>
#include <cctype>
#include <vector>

namespace
{
std::string TrimText(const std::string& text)
{
	std::size_t start = 0;
	while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
		++start;

	std::size_t end = text.size();
	while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
		--end;

	return text.substr(start, end - start);
}

bool IsFiniteNumber(double value)
{
	return std::isfinite(value) != 0;
}

std::string NormalizeTextValue(const std::string& text, unsigned maxLength)
{
	std::string normalized;
	normalized.reserve(std::min<std::size_t>(text.size(), maxLength));
	for (unsigned char ch : text)
	{
		if (ch < 32 || ch > 126)
			continue;
		if (normalized.size() >= maxLength)
			break;
		normalized.push_back(static_cast<char>(ch));
	}
	return normalized;
}
}

std::string MakeSafePresetFileStem(const std::string& presetName)
{
	std::string trimmed = TrimText(presetName);
	std::string safe;
	safe.reserve(trimmed.size());

	for (unsigned char ch : trimmed)
	{
		if (std::isalnum(ch))
			safe.push_back(static_cast<char>(std::tolower(ch)));
		else if (ch == ' ' || ch == '-' || ch == '_')
		{
			if (safe.empty() || safe.back() == '-')
				continue;
			safe.push_back('-');
		}
	}

	while (!safe.empty() && safe.back() == '-')
		safe.pop_back();

	return safe;
}

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 = NormalizeTextValue(definition.defaultTextValue, definition.maxLength);
		break;
	case ShaderParameterType::Trigger:
		value.numberValues = { 0.0, -1000000.0 };
		break;
	}
	return value;
}

bool NormalizeAndValidateParameterValue(const ShaderParameterDefinition& definition, const JsonValue& value, ShaderParameterValue& normalizedValue, std::string& error)
{
	normalizedValue = DefaultValueForDefinition(definition);

	switch (definition.type)
	{
	case ShaderParameterType::Float:
	{
		if (!value.isNumber())
		{
			error = "Expected numeric value for float parameter '" + definition.id + "'.";
			return false;
		}
		double number = value.asNumber();
		if (!IsFiniteNumber(number))
		{
			error = "Float parameter '" + definition.id + "' must be finite.";
			return false;
		}
		if (!definition.minNumbers.empty())
			number = std::max(number, definition.minNumbers.front());
		if (!definition.maxNumbers.empty())
			number = std::min(number, definition.maxNumbers.front());
		normalizedValue.numberValues = { number };
		return true;
	}
	case ShaderParameterType::Vec2:
	case ShaderParameterType::Color:
	{
		std::vector<double> numbers = JsonArrayToNumbers(value);
		const std::size_t expectedSize = definition.type == ShaderParameterType::Vec2 ? 2 : 4;
		if (numbers.size() != expectedSize)
		{
			error = "Expected array value of size " + std::to_string(expectedSize) + " for parameter '" + definition.id + "'.";
			return false;
		}
		for (std::size_t index = 0; index < numbers.size(); ++index)
		{
			if (!IsFiniteNumber(numbers[index]))
			{
				error = "Parameter '" + definition.id + "' contains a non-finite value.";
				return false;
			}
			if (index < definition.minNumbers.size())
				numbers[index] = std::max(numbers[index], definition.minNumbers[index]);
			if (index < definition.maxNumbers.size())
				numbers[index] = std::min(numbers[index], definition.maxNumbers[index]);
		}
		normalizedValue.numberValues = numbers;
		return true;
	}
	case ShaderParameterType::Boolean:
		if (!value.isBoolean())
		{
			error = "Expected boolean value for parameter '" + definition.id + "'.";
			return false;
		}
		normalizedValue.booleanValue = value.asBoolean();
		return true;
	case ShaderParameterType::Enum:
	{
		if (!value.isString())
		{
			error = "Expected string value for enum parameter '" + definition.id + "'.";
			return false;
		}
		const std::string selectedValue = value.asString();
		for (const ShaderParameterOption& option : definition.enumOptions)
		{
			if (option.value == selectedValue)
			{
				normalizedValue.enumValue = selectedValue;
				return true;
			}
		}
		error = "Enum parameter '" + definition.id + "' received unsupported option '" + selectedValue + "'.";
		return false;
	}
	case ShaderParameterType::Text:
		if (!value.isString())
		{
			error = "Expected string value for text parameter '" + definition.id + "'.";
			return false;
		}
		normalizedValue.textValue = NormalizeTextValue(value.asString(), definition.maxLength);
		return true;
	case ShaderParameterType::Trigger:
		if (!value.isNumber() && !value.isBoolean())
		{
			error = "Expected numeric or boolean value for trigger parameter '" + definition.id + "'.";
			return false;
		}
		normalizedValue.numberValues = { value.isNumber() ? std::max(0.0, std::floor(value.asNumber())) : 0.0, -1000000.0 };
		return true;
	}

	return false;
}
