#include <psemek/fonts/bmfont.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/exception.hpp>

#include <boost/preprocessor/stringize.hpp>

#define RAPIDJSON_ASSERT(x) if (!(x)) throw ::psemek::util::exception("Error parsing font description: " BOOST_PP_STRINGIZE(x));
#define RAPIDJSON_NOEXCEPT_ASSERT(x)

#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>

namespace psemek::fonts
{

	namespace
	{

		std::string_view to_string(rapidjson::ParseErrorCode const & code)
		{
			using namespace rapidjson;

			switch (code)
			{
			case kParseErrorNone: return "no error";
			case kParseErrorDocumentEmpty: return "document is empty";
			case kParseErrorDocumentRootNotSingular: return "document root must not be followed by other values";
			case kParseErrorValueInvalid: return "invalid value";
			case kParseErrorObjectMissName: return "missing a name for object member";
			case kParseErrorObjectMissColon: return "missing a colon after a name of object member";
			case kParseErrorObjectMissCommaOrCurlyBracket: return "missing a comma or '}' after an object member";
			case kParseErrorArrayMissCommaOrSquareBracket: return "missing a comma or ']' after an array element";
			case kParseErrorStringUnicodeEscapeInvalidHex: return R"(Incorrect hex digit after \\u escape in string)";
			case kParseErrorStringUnicodeSurrogateInvalid: return "surrogate pair in string is invalid";
			case kParseErrorStringEscapeInvalid: return "invalid escape character in string";
			case kParseErrorStringMissQuotationMark: return "missing a closing quotation mark in string";
			case kParseErrorStringInvalidEncoding: return "invalid encoding in string";
			case kParseErrorNumberTooBig: return "number too big to be stored in double";
			case kParseErrorNumberMissFraction: return "missing fraction part in number";
			case kParseErrorNumberMissExponent: return "missing exponent in number";
			case kParseErrorTermination: return "parsing was terminated";
			case kParseErrorUnspecificSyntaxError: return "unspecific syntax error";
			default: return "(unknown error)";
			}
		}

	}

	bmfont_data bmfont_data::parse(io::istream && stream)
	{
		auto description_str = io::read_full(std::move(stream)).string();

		rapidjson::Document document;
		document.ParseInsitu(description_str.data());

		if (document.HasParseError())
			throw util::exception(util::to_string("Error msdf font description: ", to_string(document.GetParseError()), " at ", document.GetErrorOffset()));

		bmfont_data result;

		{
			auto const & info = document["info"];
			result.name = info["face"].GetString();
			int size = info["size"].GetInt();
			result.size = {size, size};
		}

		if (document.HasMember("distanceField"))
		{
			auto const & sdf = document["distanceField"];
			result.sdf_scale = sdf["distanceRange"].GetFloat();
		}

		{
			auto const & sdf = document["common"];
			result.baseline = sdf["base"].GetFloat();
		}

		{
			auto chars = document["chars"].GetArray();

			for (auto const & charInfo : chars)
			{
				char32_t id = charInfo["id"].GetUint();

				auto & data = result.glyphs[id];
				data.start_x = charInfo["x"].GetFloat();
				data.start_y = charInfo["y"].GetFloat();
				data.size_x = charInfo["width"].GetFloat();
				data.size_y = charInfo["height"].GetFloat();
				data.offset_x = charInfo["xoffset"].GetFloat();
				data.offset_y = charInfo["yoffset"].GetFloat();
				data.advance = charInfo["xadvance"].GetFloat();

				data.offset_y = - data.offset_y - data.size_y + result.size[1];
			}
		}

		return result;
	}

}
