#include "stdafx.h"
#include "ShaderManifestParser.h"

namespace ShaderManifestParsing
{
namespace
{
bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
{
	if (typeName == "float")
	{
		type = ShaderParameterType::Float;
		return true;
	}
	if (typeName == "vec2")
	{
		type = ShaderParameterType::Vec2;
		return true;
	}
	if (typeName == "color")
	{
		type = ShaderParameterType::Color;
		return true;
	}
	if (typeName == "bool")
	{
		type = ShaderParameterType::Boolean;
		return true;
	}
	if (typeName == "enum")
	{
		type = ShaderParameterType::Enum;
		return true;
	}
	if (typeName == "text")
	{
		type = ShaderParameterType::Text;
		return true;
	}
	if (typeName == "trigger")
	{
		type = ShaderParameterType::Trigger;
		return true;
	}
	return false;
}

bool ParseParameterNumberField(const JsonValue& parameterJson, const char* fieldName, std::vector<double>& values, const std::filesystem::path& manifestPath, std::string& error)
{
	if (const JsonValue* fieldValue = parameterJson.find(fieldName))
		return NumberListFromJsonValue(*fieldValue, values, fieldName, manifestPath, error);
	return true;
}

bool ParseParameterDefault(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* defaultValue = parameterJson.find("default");
	if (!defaultValue)
		return true;

	if (definition.type == ShaderParameterType::Boolean)
	{
		if (!defaultValue->isBoolean())
		{
			error = "Boolean parameter default must be a boolean for: " + definition.id;
			return false;
		}
		definition.defaultBoolean = defaultValue->asBoolean(false);
		return true;
	}

	if (definition.type == ShaderParameterType::Enum)
	{
		if (!defaultValue->isString())
		{
			error = "Enum parameter default must be a string for: " + definition.id;
			return false;
		}
		definition.defaultEnumValue = defaultValue->asString();
		return true;
	}

	if (definition.type == ShaderParameterType::Text)
	{
		if (!defaultValue->isString())
		{
			error = "Text parameter default must be a string for: " + definition.id;
			return false;
		}
		definition.defaultTextValue = defaultValue->asString();
		return true;
	}

	return NumberListFromJsonValue(*defaultValue, definition.defaultNumbers, "default", manifestPath, error);
}

bool ParseParameterOptions(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* optionsValue = nullptr;
	if (!OptionalArrayField(parameterJson, "options", optionsValue, manifestPath, error) || !optionsValue)
	{
		error = "Enum parameter is missing 'options' in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	for (const JsonValue& optionJson : optionsValue->asArray())
	{
		if (!optionJson.isObject())
		{
			error = "Enum parameter option must be an object in: " + ManifestPathMessage(manifestPath);
			return false;
		}

		ShaderParameterOption option;
		if (!RequireStringField(optionJson, "value", option.value, manifestPath, error) ||
			!RequireStringField(optionJson, "label", option.label, manifestPath, error))
		{
			error = "Enum parameter option is missing 'value' or 'label' in: " + ManifestPathMessage(manifestPath);
			return false;
		}
		definition.enumOptions.push_back(option);
	}

	bool defaultFound = definition.defaultEnumValue.empty();
	for (const ShaderParameterOption& option : definition.enumOptions)
	{
		if (option.value == definition.defaultEnumValue)
		{
			defaultFound = true;
			break;
		}
	}

	if (!defaultFound)
	{
		error = "Enum parameter default is not present in its option list for: " + definition.id;
		return false;
	}

	return true;
}

bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
{
	if (!parameterJson.isObject())
	{
		error = "Shader parameter entry must be an object in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	std::string typeName;
	if (!RequireStringField(parameterJson, "id", definition.id, manifestPath, error) ||
		!RequireStringField(parameterJson, "label", definition.label, manifestPath, error) ||
		!RequireStringField(parameterJson, "type", typeName, manifestPath, error))
	{
		error = "Shader parameter is missing required fields in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	if (!ParseShaderParameterType(typeName, definition.type))
	{
		error = "Unsupported parameter type '" + typeName + "' in: " + ManifestPathMessage(manifestPath);
		return false;
	}
	if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
		return false;

	if (!OptionalStringField(parameterJson, "description", definition.description, "", manifestPath, error))
		return false;

	if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
		!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
		!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
		!ParseParameterNumberField(parameterJson, "step", definition.stepNumbers, manifestPath, error))
	{
		return false;
	}

	if (definition.type == ShaderParameterType::Text)
	{
		if (const JsonValue* fontValue = parameterJson.find("font"))
		{
			if (!fontValue->isString())
			{
				error = "Text parameter 'font' must be a string for: " + definition.id;
				return false;
			}
			definition.fontId = fontValue->asString();
			if (!definition.fontId.empty() && !ValidateShaderIdentifier(definition.fontId, "parameters[].font", manifestPath, error))
				return false;
		}
		if (const JsonValue* maxLengthValue = parameterJson.find("maxLength"))
		{
			if (!maxLengthValue->isNumber() || maxLengthValue->asNumber() < 1.0 || maxLengthValue->asNumber() > 256.0)
			{
				error = "Text parameter 'maxLength' must be a number from 1 to 256 for: " + definition.id;
				return false;
			}
			definition.maxLength = static_cast<unsigned>(maxLengthValue->asNumber());
		}
	}

	if (definition.type == ShaderParameterType::Enum)
		return ParseParameterOptions(parameterJson, definition, manifestPath, error);

	return true;
}
}

bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* parametersValue = nullptr;
	if (!OptionalArrayField(manifestJson, "parameters", parametersValue, manifestPath, error))
		return false;
	if (!parametersValue)
		return true;

	for (const JsonValue& parameterJson : parametersValue->asArray())
	{
		ShaderParameterDefinition definition;
		if (!ParseParameterDefinition(parameterJson, definition, manifestPath, error))
			return false;
		shaderPackage.parameters.push_back(definition);
	}

	return true;
}
}
