#include "RuntimeJson.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;
}

JsonValue ParseOrFail(const std::string& text, const char* message)
{
	JsonValue value;
	std::string error;
	if (!ParseJson(text, value, error))
	{
		std::cerr << "FAIL: " << message << ": " << error << "\n";
		++gFailures;
	}
	return value;
}

void ExpectParseFails(const std::string& text, const char* message)
{
	JsonValue value;
	std::string error;
	Expect(!ParseJson(text, value, error), message);
	Expect(!error.empty(), "failed parse should include an error message");
}

void TestStringsAndEscaping()
{
	const JsonValue value = ParseOrFail(R"({"text":"line\nquote:\" slash:\\ tab:\t"})", "escaped string parses");
	const JsonValue* text = value.find("text");
	Expect(text && text->isString(), "escaped string field is present");
	Expect(text && text->asString().find("line\nquote:\" slash:\\ tab:\t") != std::string::npos, "escaped string unescapes expected characters");

	JsonValue controlString(std::string("a\001b", 3));
	Expect(SerializeJson(controlString, false) == R"("a\u0001b")", "serializer escapes raw control characters");
	ExpectParseFails(std::string("{\"bad\":\"a") + static_cast<char>(1) + "b\"}", "raw control characters are rejected");
}

void TestUnicodeEscapes()
{
	const JsonValue copyright = ParseOrFail(R"({"s":"\u00A9"})", "basic unicode escape parses");
	Expect(copyright.find("s") && copyright.find("s")->asString() == "\xC2\xA9", "basic unicode escape becomes UTF-8");

	const JsonValue music = ParseOrFail(R"({"s":"\uD834\uDD1E"})", "surrogate pair parses");
	Expect(music.find("s") && music.find("s")->asString() == "\xF0\x9D\x84\x9E", "surrogate pair becomes UTF-8");

	ExpectParseFails(R"({"s":"\uD834x"})", "unpaired high surrogate is rejected");
	ExpectParseFails(R"({"s":"\uDD1E"})", "unpaired low surrogate is rejected");
	ExpectParseFails(R"({"s":"\u00XZ"})", "invalid unicode hex is rejected");
}

void TestNumbers()
{
	const JsonValue value = ParseOrFail(R"({"a":0,"b":-12.5e+2,"c":3.25})", "valid numbers parse");
	Expect(value.find("a") && value.find("a")->asNumber(-1.0) == 0.0, "zero parses");
	Expect(value.find("b") && std::fabs(value.find("b")->asNumber() + 1250.0) < 0.0001, "exponent parses");
	Expect(value.find("c") && std::fabs(value.find("c")->asNumber() - 3.25) < 0.0001, "fraction parses");

	ExpectParseFails("01", "leading zero is rejected");
	ExpectParseFails("1.", "fraction without digits is rejected");
	ExpectParseFails("1e", "exponent without digits is rejected");
	ExpectParseFails("1e9999", "non-finite number is rejected");
}

void TestRoundtripAndMutation()
{
	JsonValue root = JsonValue::MakeObject();
	root.set("version", JsonValue(1.0));
	root.set("name", JsonValue("Preset One"));

	JsonValue layers = JsonValue::MakeArray();
	JsonValue layer = JsonValue::MakeObject();
	layer.set("id", JsonValue("layer-a"));
	layer.set("bypass", JsonValue(false));
	layers.pushBack(layer);
	root.set("layers", layers);

	const std::string serialized = SerializeJson(root, true);
	const JsonValue parsed = ParseOrFail(serialized, "serialized preset-style object parses");
	Expect(parsed.find("layers") && parsed.find("layers")->isArray(), "roundtripped layers array exists");

	JsonValue mutableValue = JsonValue::MakeObject();
	mutableValue.set("stale", JsonValue("value"));
	mutableValue.pushBack(JsonValue(7.0));
	Expect(mutableValue.isArray(), "pushBack changes object into array");
	Expect(mutableValue.asObject().empty(), "object storage is cleared when changing to array");
	Expect(mutableValue.asArray().size() == 1, "new array receives pushed value");
}
}

int main()
{
	TestStringsAndEscaping();
	TestUnicodeEscapes();
	TestNumbers();
	TestRoundtripAndMutation();

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

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