#include "stdafx.h"
#include "RuntimeJson.h"

#include <cerrno>
#include <cctype>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <limits>
#include <sstream>

namespace
{
int HexDigitValue(char ch)
{
	if (ch >= '0' && ch <= '9')
		return ch - '0';
	if (ch >= 'a' && ch <= 'f')
		return ch - 'a' + 10;
	if (ch >= 'A' && ch <= 'F')
		return ch - 'A' + 10;
	return -1;
}

bool IsHighSurrogate(unsigned int codePoint)
{
	return codePoint >= 0xD800 && codePoint <= 0xDBFF;
}

bool IsLowSurrogate(unsigned int codePoint)
{
	return codePoint >= 0xDC00 && codePoint <= 0xDFFF;
}

void AppendUtf8(unsigned int codePoint, std::ostringstream& output)
{
	if (codePoint <= 0x7F)
	{
		output << static_cast<char>(codePoint);
	}
	else if (codePoint <= 0x7FF)
	{
		output << static_cast<char>(0xC0 | ((codePoint >> 6) & 0x1F));
		output << static_cast<char>(0x80 | (codePoint & 0x3F));
	}
	else if (codePoint <= 0xFFFF)
	{
		output << static_cast<char>(0xE0 | ((codePoint >> 12) & 0x0F));
		output << static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F));
		output << static_cast<char>(0x80 | (codePoint & 0x3F));
	}
	else
	{
		output << static_cast<char>(0xF0 | ((codePoint >> 18) & 0x07));
		output << static_cast<char>(0x80 | ((codePoint >> 12) & 0x3F));
		output << static_cast<char>(0x80 | ((codePoint >> 6) & 0x3F));
		output << static_cast<char>(0x80 | (codePoint & 0x3F));
	}
}

void AppendControlEscape(unsigned char ch, std::ostringstream& output)
{
	const char* digits = "0123456789ABCDEF";
	output << "\\u00" << digits[(ch >> 4) & 0x0F] << digits[ch & 0x0F];
}

class JsonParser
{
public:
	JsonParser(const std::string& text, std::string& error)
		: mText(text), mError(error), mPosition(0)
	{
	}

	bool parse(JsonValue& value)
	{
		skipWhitespace();
		if (!parseValue(value))
			return false;

		skipWhitespace();
		if (mPosition != mText.size())
		{
			setError("Unexpected trailing characters in JSON input.");
			return false;
		}

		return true;
	}

private:
	bool parseValue(JsonValue& value)
	{
		if (mPosition >= mText.size())
		{
			setError("Unexpected end of JSON input.");
			return false;
		}

		char ch = mText[mPosition];
		if (ch == '{')
			return parseObject(value);
		if (ch == '[')
			return parseArray(value);
		if (ch == '"')
		{
			std::string stringValue;
			if (!parseString(stringValue))
				return false;
			value = JsonValue(stringValue);
			return true;
		}
		if (ch == 't')
			return parseLiteral("true", JsonValue(true), value);
		if (ch == 'f')
			return parseLiteral("false", JsonValue(false), value);
		if (ch == 'n')
			return parseLiteral("null", JsonValue(), value);
		if (ch == '-' || std::isdigit(static_cast<unsigned char>(ch)))
			return parseNumber(value);

		setError("Unexpected token while parsing JSON.");
		return false;
	}

	bool parseObject(JsonValue& value)
	{
		value = JsonValue::MakeObject();
		++mPosition;
		skipWhitespace();
		if (consume('}'))
			return true;

		while (mPosition < mText.size())
		{
			std::string key;
			if (!parseString(key))
				return false;

			skipWhitespace();
			if (!consume(':'))
			{
				setError("Expected ':' after JSON object key.");
				return false;
			}

			skipWhitespace();
			JsonValue item;
			if (!parseValue(item))
				return false;

			value.set(key, item);

			skipWhitespace();
			if (consume('}'))
				return true;
			if (!consume(','))
			{
				setError("Expected ',' or '}' in JSON object.");
				return false;
			}
			skipWhitespace();
		}

		setError("Unexpected end of JSON object.");
		return false;
	}

	bool parseArray(JsonValue& value)
	{
		value = JsonValue::MakeArray();
		++mPosition;
		skipWhitespace();
		if (consume(']'))
			return true;

		while (mPosition < mText.size())
		{
			JsonValue item;
			if (!parseValue(item))
				return false;

			value.pushBack(item);

			skipWhitespace();
			if (consume(']'))
				return true;
			if (!consume(','))
			{
				setError("Expected ',' or ']' in JSON array.");
				return false;
			}
			skipWhitespace();
		}

		setError("Unexpected end of JSON array.");
		return false;
	}

	bool parseString(std::string& value)
	{
		if (!consume('"'))
		{
			setError("Expected string literal.");
			return false;
		}

		std::ostringstream result;
		while (mPosition < mText.size())
		{
			char ch = mText[mPosition++];
			if (ch == '"')
			{
				value = result.str();
				return true;
			}

			if (ch == '\\')
			{
				if (mPosition >= mText.size())
				{
					setError("Unexpected end of escaped JSON string.");
					return false;
				}

				char escaped = mText[mPosition++];
				switch (escaped)
				{
				case '"': result << '"'; break;
				case '\\': result << '\\'; break;
				case '/': result << '/'; break;
				case 'b': result << '\b'; break;
				case 'f': result << '\f'; break;
				case 'n': result << '\n'; break;
				case 'r': result << '\r'; break;
				case 't': result << '\t'; break;
				case 'u':
					if (!parseUnicodeEscape(result))
						return false;
					break;
				default:
					setError("Invalid escape sequence in JSON string.");
					return false;
				}
			}
			else
			{
				if (static_cast<unsigned char>(ch) < 0x20)
				{
					setError("Unescaped control character in JSON string.");
					return false;
				}
				result << ch;
			}
		}

		setError("Unexpected end of JSON string.");
		return false;
	}

	bool parseHexCodePoint(unsigned int& codePoint)
	{
		if (mPosition + 4 > mText.size())
		{
			setError("Unexpected end of Unicode escape sequence.");
			return false;
		}

		codePoint = 0;
		for (int i = 0; i < 4; ++i)
		{
			const int digit = HexDigitValue(mText[mPosition + i]);
			if (digit < 0)
			{
				setError("Invalid Unicode escape sequence in JSON string.");
				return false;
			}
			codePoint = (codePoint << 4) | static_cast<unsigned int>(digit);
		}

		mPosition += 4;
		return true;
	}

	bool parseUnicodeEscape(std::ostringstream& result)
	{
		unsigned int codePoint = 0;
		if (!parseHexCodePoint(codePoint))
			return false;

		if (IsHighSurrogate(codePoint))
		{
			if (mPosition + 2 > mText.size() || mText[mPosition] != '\\' || mText[mPosition + 1] != 'u')
			{
				setError("High surrogate Unicode escape must be followed by a low surrogate.");
				return false;
			}

			mPosition += 2;
			unsigned int lowSurrogate = 0;
			if (!parseHexCodePoint(lowSurrogate))
				return false;
			if (!IsLowSurrogate(lowSurrogate))
			{
				setError("High surrogate Unicode escape must be followed by a low surrogate.");
				return false;
			}

			codePoint = 0x10000 + (((codePoint - 0xD800) << 10) | (lowSurrogate - 0xDC00));
		}
		else if (IsLowSurrogate(codePoint))
		{
			setError("Low surrogate Unicode escape without preceding high surrogate.");
			return false;
		}

		AppendUtf8(codePoint, result);
		return true;
	}

	bool parseNumber(JsonValue& value)
	{
		std::size_t start = mPosition;

		if (mText[mPosition] == '-')
			++mPosition;

		if (mPosition >= mText.size())
		{
			setError("Invalid JSON number.");
			return false;
		}

		if (mText[mPosition] == '0')
		{
			++mPosition;
			if (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
			{
				setError("JSON numbers must not contain leading zeroes.");
				return false;
			}
		}
		else if (mText[mPosition] >= '1' && mText[mPosition] <= '9')
		{
			while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
				++mPosition;
		}
		else
		{
			setError("Invalid JSON number.");
			return false;
		}

		if (mPosition < mText.size() && mText[mPosition] == '.')
		{
			++mPosition;
			if (mPosition >= mText.size() || !std::isdigit(static_cast<unsigned char>(mText[mPosition])))
			{
				setError("JSON number fraction must contain at least one digit.");
				return false;
			}
			while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
				++mPosition;
		}

		if (mPosition < mText.size() && (mText[mPosition] == 'e' || mText[mPosition] == 'E'))
		{
			++mPosition;
			if (mPosition < mText.size() && (mText[mPosition] == '+' || mText[mPosition] == '-'))
				++mPosition;
			if (mPosition >= mText.size() || !std::isdigit(static_cast<unsigned char>(mText[mPosition])))
			{
				setError("JSON number exponent must contain at least one digit.");
				return false;
			}
			while (mPosition < mText.size() && std::isdigit(static_cast<unsigned char>(mText[mPosition])))
				++mPosition;
		}

		std::string token = mText.substr(start, mPosition - start);
		char* endPtr = nullptr;
		errno = 0;
		double parsed = strtod(token.c_str(), &endPtr);
		if (endPtr == token.c_str() || *endPtr != '\0' || errno == ERANGE || !std::isfinite(parsed))
		{
			setError("Invalid JSON number.");
			return false;
		}

		value = JsonValue(parsed);
		return true;
	}

	bool parseLiteral(const char* literal, const JsonValue& literalValue, JsonValue& value)
	{
		std::size_t length = strlen(literal);
		if (mText.compare(mPosition, length, literal) != 0)
		{
			setError("Invalid JSON literal.");
			return false;
		}

		mPosition += length;
		value = literalValue;
		return true;
	}

	void skipWhitespace()
	{
		while (mPosition < mText.size() && std::isspace(static_cast<unsigned char>(mText[mPosition])))
			++mPosition;
	}

	bool consume(char expected)
	{
		if (mPosition < mText.size() && mText[mPosition] == expected)
		{
			++mPosition;
			return true;
		}
		return false;
	}

	void setError(const std::string& error)
	{
		if (mError.empty())
			mError = error;
	}

	const std::string& mText;
	std::string& mError;
	std::size_t mPosition;
};

void SerializeJsonImpl(const JsonValue& value, std::ostringstream& output, bool pretty, int indentLevel)
{
	auto indent = [&](int level) {
		if (!pretty)
			return;
		for (int i = 0; i < level; ++i)
			output << "  ";
	};

	switch (value.type())
	{
	case JsonValue::Type::Null:
		output << "null";
		break;
	case JsonValue::Type::Boolean:
		output << (value.asBoolean() ? "true" : "false");
		break;
	case JsonValue::Type::Number:
	{
		double number = value.asNumber();
		if (std::isfinite(number))
		{
			output << std::setprecision(15) << number;
		}
		else
		{
			output << "0";
		}
		break;
	}
	case JsonValue::Type::String:
	{
		output << '"';
		for (char ch : value.asString())
		{
			switch (ch)
			{
			case '"': output << "\\\""; break;
			case '\\': output << "\\\\"; break;
			case '\b': output << "\\b"; break;
			case '\f': output << "\\f"; break;
			case '\n': output << "\\n"; break;
			case '\r': output << "\\r"; break;
			case '\t': output << "\\t"; break;
			default:
				if (static_cast<unsigned char>(ch) < 0x20)
					AppendControlEscape(static_cast<unsigned char>(ch), output);
				else
					output << ch;
				break;
			}
		}
		output << '"';
		break;
	}
	case JsonValue::Type::Array:
	{
		output << "[";
		const std::vector<JsonValue>& array = value.asArray();
		if (!array.empty())
		{
			if (pretty)
				output << "\n";
			for (std::size_t i = 0; i < array.size(); ++i)
			{
				indent(indentLevel + 1);
				SerializeJsonImpl(array[i], output, pretty, indentLevel + 1);
				if (i + 1 != array.size())
					output << ",";
				if (pretty)
					output << "\n";
			}
			indent(indentLevel);
		}
		output << "]";
		break;
	}
	case JsonValue::Type::Object:
	{
		output << "{";
		const std::map<std::string, JsonValue>& object = value.asObject();
		if (!object.empty())
		{
			if (pretty)
				output << "\n";
			std::size_t index = 0;
			for (const auto& item : object)
			{
				indent(indentLevel + 1);
				SerializeJsonImpl(JsonValue(item.first), output, pretty, indentLevel + 1);
				output << (pretty ? ": " : ":");
				SerializeJsonImpl(item.second, output, pretty, indentLevel + 1);
				if (++index != object.size())
					output << ",";
				if (pretty)
					output << "\n";
			}
			indent(indentLevel);
		}
		output << "}";
		break;
	}
	}
}
}

JsonValue::JsonValue()
	: mType(Type::Null), mBooleanValue(false), mNumberValue(0.0)
{
}

JsonValue::JsonValue(bool value)
	: mType(Type::Boolean), mBooleanValue(value), mNumberValue(0.0)
{
}

JsonValue::JsonValue(double value)
	: mType(Type::Number), mBooleanValue(false), mNumberValue(value)
{
}

JsonValue::JsonValue(const char* value)
	: mType(Type::String), mBooleanValue(false), mNumberValue(0.0), mStringValue(value ? value : "")
{
}

JsonValue::JsonValue(const std::string& value)
	: mType(Type::String), mBooleanValue(false), mNumberValue(0.0), mStringValue(value)
{
}

JsonValue JsonValue::MakeArray()
{
	JsonValue value;
	value.reset(Type::Array);
	return value;
}

JsonValue JsonValue::MakeObject()
{
	JsonValue value;
	value.reset(Type::Object);
	return value;
}

bool JsonValue::asBoolean(bool fallback) const
{
	return mType == Type::Boolean ? mBooleanValue : fallback;
}

double JsonValue::asNumber(double fallback) const
{
	return mType == Type::Number ? mNumberValue : fallback;
}

const std::string& JsonValue::asString() const
{
	static const std::string emptyString;
	return mType == Type::String ? mStringValue : emptyString;
}

const std::vector<JsonValue>& JsonValue::asArray() const
{
	static const std::vector<JsonValue> emptyArray;
	return mType == Type::Array ? mArrayValue : emptyArray;
}

const std::map<std::string, JsonValue>& JsonValue::asObject() const
{
	static const std::map<std::string, JsonValue> emptyObject;
	return mType == Type::Object ? mObjectValue : emptyObject;
}

std::vector<JsonValue>& JsonValue::array()
{
	if (mType != Type::Array)
		reset(Type::Array);
	return mArrayValue;
}

std::map<std::string, JsonValue>& JsonValue::object()
{
	if (mType != Type::Object)
		reset(Type::Object);
	return mObjectValue;
}

void JsonValue::pushBack(const JsonValue& value)
{
	array().push_back(value);
}

void JsonValue::set(const std::string& key, const JsonValue& value)
{
	object()[key] = value;
}

const JsonValue* JsonValue::find(const std::string& key) const
{
	if (mType != Type::Object)
		return nullptr;

	auto iterator = mObjectValue.find(key);
	return iterator != mObjectValue.end() ? &iterator->second : nullptr;
}

void JsonValue::reset(Type type)
{
	mType = type;
	mBooleanValue = false;
	mNumberValue = 0.0;
	mStringValue.clear();
	mArrayValue.clear();
	mObjectValue.clear();
}

bool ParseJson(const std::string& text, JsonValue& value, std::string& error)
{
	error.clear();
	JsonParser parser(text, error);
	return parser.parse(value);
}

std::string SerializeJson(const JsonValue& value, bool pretty)
{
	std::ostringstream output;
	SerializeJsonImpl(value, output, pretty, 0);
	return output.str();
}

std::vector<double> JsonArrayToNumbers(const JsonValue& value)
{
	std::vector<double> numbers;
	for (const JsonValue& item : value.asArray())
	{
		if (item.isNumber())
			numbers.push_back(item.asNumber());
	}
	return numbers;
}
