#include "RuntimeParameterUtils.h"

#include <cmath>
#include <iostream>
#include <string>

namespace
{
int gFailures = 0;

void Expect(bool condition, const char* message)
{
	if (condition)
		return;

	std::cerr << "FAIL: " << message << "\n";
	++gFailures;
}

ShaderParameterDefinition MakeFloatDefinition()
{
	ShaderParameterDefinition definition;
	definition.id = "gain";
	definition.type = ShaderParameterType::Float;
	definition.defaultNumbers = { 0.5 };
	definition.minNumbers = { 0.0 };
	definition.maxNumbers = { 1.0 };
	return definition;
}

void TestSafePresetFileStems()
{
	Expect(MakeSafePresetFileStem("  Show Look 01  ") == "show-look-01", "preset names are trimmed and dashed");
	Expect(MakeSafePresetFileStem("A__B---C") == "a-b-c", "duplicate separators collapse");
	Expect(MakeSafePresetFileStem("../Unsafe Name!") == "unsafe-name", "unsafe punctuation is dropped");
	Expect(MakeSafePresetFileStem("!!!") == "", "names without alphanumeric characters become empty");
}

void TestFloatNormalization()
{
	ShaderParameterDefinition definition = MakeFloatDefinition();
	ShaderParameterValue value;
	std::string error;

	Expect(NormalizeAndValidateParameterValue(definition, JsonValue(2.0), value, error), "float parameter accepts numeric values");
	Expect(value.numberValues.size() == 1 && value.numberValues.front() == 1.0, "float parameter clamps to max");

	error.clear();
	Expect(NormalizeAndValidateParameterValue(definition, JsonValue(-10.0), value, error), "float parameter accepts low numeric values");
	Expect(value.numberValues.size() == 1 && value.numberValues.front() == 0.0, "float parameter clamps to min");

	error.clear();
	Expect(!NormalizeAndValidateParameterValue(definition, JsonValue("bad"), value, error), "float parameter rejects non-numeric values");
	Expect(!error.empty(), "float rejection includes an error");
}

void TestVectorNormalization()
{
	ShaderParameterDefinition definition;
	definition.id = "offset";
	definition.type = ShaderParameterType::Vec2;
	definition.defaultNumbers = { 0.0, 0.0 };
	definition.minNumbers = { -1.0, -2.0 };
	definition.maxNumbers = { 1.0, 2.0 };

	JsonValue input = JsonValue::MakeArray();
	input.pushBack(JsonValue(5.0));
	input.pushBack(JsonValue(-5.0));

	ShaderParameterValue value;
	std::string error;
	Expect(NormalizeAndValidateParameterValue(definition, input, value, error), "vec2 parameter accepts arrays");
	Expect(value.numberValues.size() == 2 && value.numberValues[0] == 1.0 && value.numberValues[1] == -2.0, "vec2 parameter clamps each component");

	JsonValue shortInput = JsonValue::MakeArray();
	shortInput.pushBack(JsonValue(0.0));
	error.clear();
	Expect(!NormalizeAndValidateParameterValue(definition, shortInput, value, error), "vec2 parameter rejects wrong component count");
}

void TestColorAndBooleanNormalization()
{
	ShaderParameterDefinition colorDefinition;
	colorDefinition.id = "tint";
	colorDefinition.type = ShaderParameterType::Color;
	colorDefinition.defaultNumbers = { 1.0, 1.0, 1.0, 1.0 };
	colorDefinition.minNumbers = { 0.0, 0.0, 0.0, 0.0 };
	colorDefinition.maxNumbers = { 1.0, 1.0, 1.0, 1.0 };

	JsonValue colorInput = JsonValue::MakeArray();
	colorInput.pushBack(JsonValue(-0.5));
	colorInput.pushBack(JsonValue(0.25));
	colorInput.pushBack(JsonValue(1.5));
	colorInput.pushBack(JsonValue(0.75));

	ShaderParameterValue value;
	std::string error;
	Expect(NormalizeAndValidateParameterValue(colorDefinition, colorInput, value, error), "color parameter accepts four-component arrays");
	Expect(value.numberValues.size() == 4 &&
		value.numberValues[0] == 0.0 &&
		value.numberValues[1] == 0.25 &&
		value.numberValues[2] == 1.0 &&
		value.numberValues[3] == 0.75, "color parameter clamps each component");

	JsonValue shortColor = JsonValue::MakeArray();
	shortColor.pushBack(JsonValue(1.0));
	shortColor.pushBack(JsonValue(1.0));
	shortColor.pushBack(JsonValue(1.0));
	error.clear();
	Expect(!NormalizeAndValidateParameterValue(colorDefinition, shortColor, value, error), "color parameter rejects wrong component count");

	ShaderParameterDefinition boolDefinition;
	boolDefinition.id = "enabled";
	boolDefinition.type = ShaderParameterType::Boolean;
	boolDefinition.defaultBoolean = true;

	ShaderParameterValue defaultValue = DefaultValueForDefinition(boolDefinition);
	Expect(defaultValue.booleanValue, "boolean default is copied from definition");

	error.clear();
	Expect(NormalizeAndValidateParameterValue(boolDefinition, JsonValue(false), value, error), "boolean parameter accepts boolean values");
	Expect(!value.booleanValue, "boolean parameter stores selected value");

	error.clear();
	Expect(!NormalizeAndValidateParameterValue(boolDefinition, JsonValue("false"), value, error), "boolean parameter rejects string values");
}

void TestEnumAndDefaults()
{
	ShaderParameterDefinition definition;
	definition.id = "mode";
	definition.type = ShaderParameterType::Enum;
	definition.defaultEnumValue = "soft";
	definition.enumOptions = {
		{ "soft", "Soft" },
		{ "hard", "Hard" }
	};

	ShaderParameterValue defaultValue = DefaultValueForDefinition(definition);
	Expect(defaultValue.enumValue == "soft", "enum default is copied from definition");

	ShaderParameterValue value;
	std::string error;
	Expect(NormalizeAndValidateParameterValue(definition, JsonValue("hard"), value, error), "enum accepts listed options");
	Expect(value.enumValue == "hard", "enum stores selected option");

	error.clear();
	Expect(!NormalizeAndValidateParameterValue(definition, JsonValue("other"), value, error), "enum rejects unknown options");
}

void TestTextNormalization()
{
	ShaderParameterDefinition definition;
	definition.id = "titleText";
	definition.type = ShaderParameterType::Text;
	definition.defaultTextValue = "DEFAULT";
	definition.maxLength = 6;

	ShaderParameterValue defaultValue = DefaultValueForDefinition(definition);
	Expect(defaultValue.textValue == "DEFAUL", "text default is clamped to max length");

	ShaderParameterValue value;
	std::string error;
	Expect(NormalizeAndValidateParameterValue(definition, JsonValue("ABC\tDEF\x01GHI"), value, error), "text accepts string values");
	Expect(value.textValue == "ABCDEF", "text drops non-printable characters and clamps length");

	error.clear();
	Expect(!NormalizeAndValidateParameterValue(definition, JsonValue(12.0), value, error), "text rejects non-string values");
}

void TestTriggerNormalization()
{
	ShaderParameterDefinition definition;
	definition.id = "burst";
	definition.type = ShaderParameterType::Trigger;

	ShaderParameterValue defaultValue = DefaultValueForDefinition(definition);
	Expect(defaultValue.numberValues.size() == 2 && defaultValue.numberValues[0] == 0.0, "trigger defaults to an unfired count");
	Expect(defaultValue.numberValues[1] < 0.0, "trigger default time is safely in the past");

	ShaderParameterValue value;
	std::string error;
	Expect(NormalizeAndValidateParameterValue(definition, JsonValue(4.8), value, error), "trigger accepts numeric counts");
	Expect(value.numberValues.size() == 2 && value.numberValues[0] == 4.0, "trigger count is floored to an integer");

	error.clear();
	Expect(NormalizeAndValidateParameterValue(definition, JsonValue(true), value, error), "trigger accepts boolean pulse values");

	error.clear();
	Expect(!NormalizeAndValidateParameterValue(definition, JsonValue("fire"), value, error), "trigger rejects string values");
}
}

int main()
{
	TestSafePresetFileStems();
	TestFloatNormalization();
	TestVectorNormalization();
	TestColorAndBooleanNormalization();
	TestEnumAndDefaults();
	TestTextNormalization();
	TestTriggerNormalization();

	if (gFailures != 0)
	{
		std::cerr << gFailures << " RuntimeParameterUtils test failure(s).\n";
		return 1;
	}

	std::cout << "RuntimeParameterUtils tests passed.\n";
	return 0;
}
