#include "json.qh"

#include "test.qh"

STRING_ITERATOR(_json, string_null, 0);

int _json_buffer; ///< Store interleaved keys/values in a string buffer
string _json_temp; ///< Last read string
string _json_ns; ///< Current namespace
int _json_keys; ///< Current keys

#define JSON_BEGIN() int __i = STRING_ITERATOR_SAVE(_json)
#define JSON_FAIL(reason) goto fail
#define JSON_END() \
	return true; \
LABEL(fail) \
	STRING_ITERATOR_LOAD(_json, __i); \
	return false;

ERASEABLE
bool _json_parse_object()
{
	JSON_BEGIN();
	if (STRING_ITERATOR_GET(_json) != '{')
		JSON_FAIL("expected '{'");
	WITH(int, _json_keys, bufstr_add(_json_buffer, "", 0), _json_parse_members());
	if (STRING_ITERATOR_GET(_json) != '}')
		JSON_FAIL("expected '}'");
	JSON_END();
}

	ERASEABLE
	bool _json_parse_members()
	{
		JSON_BEGIN();
		for (;;)
		{
			if (!_json_parse_pair())
				JSON_FAIL("expected pair");
			if (STRING_ITERATOR_PEEK(_json) == ',')
			{
				STRING_ITERATOR_NEXT(_json);
				continue;
			}
			break;
		}
		JSON_END();
	}

		ERASEABLE
		bool _json_parse_pair()
		{
			JSON_BEGIN();
			if (!_json_parse_string(false))
				JSON_FAIL("expected string");
			string key = _json_temp;
			bufstr_set(_json_buffer, _json_keys, cons(bufstr_get(_json_buffer, _json_keys), key));
			if (_json_ns)
				key = strcat(_json_ns, ".", key);
			bufstr_add(_json_buffer, key, 0);
			if (STRING_ITERATOR_GET(_json) != ':')
				JSON_FAIL("expected ':'");
			bool ret = false;
			WITH(string, _json_ns, key, ret = _json_parse_value());
			if (!ret)
				JSON_FAIL("expected value");
			JSON_END();
		}

ERASEABLE
bool _json_parse_array()
{
	JSON_BEGIN();
	if (STRING_ITERATOR_GET(_json) != '[')
		JSON_FAIL("expected '['");
	int len = bufstr_add(_json_buffer, "0", 0);
	if (len)
		bufstr_set(_json_buffer, len - 1, strcat(bufstr_get(_json_buffer, len - 1), ".length"));
	bool required = false;
	for (int n = 0; ; ++n)
	{
		string key = ftos(n);
		if (_json_ns)
			key = strcat(_json_ns, ".", key);
		int it = bufstr_add(_json_buffer, key, 0);
		bool ret = false;
		WITH(string, _json_ns, key, ret = _json_parse_value());
		if (!ret)
		{
			bufstr_free(_json_buffer, it);
			if (required)
				JSON_FAIL("expected value");
			else
				break;
		}
		bufstr_set(_json_buffer, len, ftos(n + 1));
		if (STRING_ITERATOR_PEEK(_json) == ',')
		{
			STRING_ITERATOR_NEXT(_json);
			required = true;
			continue;
		}
		break;
	}
	if (STRING_ITERATOR_GET(_json) != ']')
		JSON_FAIL("expected ']'");
	JSON_END();
}

ERASEABLE
bool _json_parse_value()
{
	JSON_BEGIN();
	if (!(_json_parse_string(true)
	||    _json_parse_number()
	||    _json_parse_object()
	||    _json_parse_array()
	||    _json_parse_true()
	||    _json_parse_false()
	||    _json_parse_null()))
		JSON_FAIL("expected value");
	JSON_END();
}

	ERASEABLE
	bool _json_parse_true()
	{
		JSON_BEGIN();
		if (!(STRING_ITERATOR_GET(_json) == 't'
		&&    STRING_ITERATOR_GET(_json) == 'r'
		&&    STRING_ITERATOR_GET(_json) == 'u'
		&&    STRING_ITERATOR_GET(_json) == 'e'))
			JSON_FAIL("expected 'true'");
		bufstr_add(_json_buffer, "1", 0);
		JSON_END();
	}

	ERASEABLE
	bool _json_parse_false()
	{
		JSON_BEGIN();
		if (!(STRING_ITERATOR_GET(_json) == 'f'
		&&    STRING_ITERATOR_GET(_json) == 'a'
		&&    STRING_ITERATOR_GET(_json) == 'l'
		&&    STRING_ITERATOR_GET(_json) == 's'
		&&    STRING_ITERATOR_GET(_json) == 'e'))
			JSON_FAIL("expected 'false'");
		bufstr_add(_json_buffer, "0", 0);
		JSON_END();
	}

	ERASEABLE
	bool _json_parse_null()
	{
		JSON_BEGIN();
		if (!(STRING_ITERATOR_GET(_json) == 'n'
		&&    STRING_ITERATOR_GET(_json) == 'u'
		&&    STRING_ITERATOR_GET(_json) == 'l'
		&&    STRING_ITERATOR_GET(_json) == 'l'))
			JSON_FAIL("expected 'null'");
		bufstr_add(_json_buffer, "", 0);
		JSON_END();
	}

ERASEABLE
bool _json_parse_string(bool add)
{
	JSON_BEGIN();
	if (STRING_ITERATOR_GET(_json) != '"')
		JSON_FAIL("expected opening '\"'");
	string s = "";
	for (int c; (c = STRING_ITERATOR_GET(_json)); )
	{
		if (c == '"')
		{
			STRING_ITERATOR_UNGET(_json);
			break;
		}
		else if (c == '\\')
		{
			string esc;
			switch (STRING_ITERATOR_GET(_json))
			{
				default:
					JSON_FAIL("expected ( '\"' | '\\' | 'n' | 't' )");
				case '"':  esc = "\"";  break;
				case '\\': esc = "\\";  break;
				case 'n':  esc = "\n";  break;
				case 't':  esc = "\t";  break;
				case 'u':  esc = "\\u"; break; // TODO
				case '/':  esc = "/";   break;
			}
			s = strcat(s, esc);
		}
		else
			s = strcat(s, chr2str(c));
	}
	if (STRING_ITERATOR_GET(_json) != '"')
		JSON_FAIL("expected closing '\"'");
	if (add)
		bufstr_add(_json_buffer, s, 0);
	_json_temp = s;
	JSON_END();
}

ERASEABLE
bool _json_parse_number()
{
	JSON_BEGIN();
	if (!(_json_parse_float() || _json_parse_int()))
		JSON_FAIL("expected number");
	JSON_END();
}

	ERASEABLE
	bool _json_parse_float()
	{
		JSON_BEGIN();
		string s = "";
		bool needdot = true;
		for (int c; (c = STRING_ITERATOR_GET(_json)); )
		{
			if (c < '0' || c > '9')
			{
				if (c == '.' && needdot) // fine
					needdot = false;
				else
				{
					STRING_ITERATOR_UNGET(_json);
					break;
				}
			}
			s = strcat(s, chr2str(c));
		}
		if (s == "")
			JSON_FAIL("expected float");
		bufstr_add(_json_buffer, s, 0);
		JSON_END();
	}

	ERASEABLE
	bool _json_parse_int()
	{
		JSON_BEGIN();
		string s = "";
		for (int c; (c = STRING_ITERATOR_GET(_json)); )
		{
			if (c < '0' || c > '9')
			{
				STRING_ITERATOR_UNGET(_json);
				break;
			}
			if (s == "" && c == '0')
				JSON_FAIL("expected [1-9]");
			s = strcat(s, chr2str(c));
		}
		if (s == "")
			JSON_FAIL("expected int");
		if (ftos(stof(s)) != s)
			JSON_FAIL("expected int");
		bufstr_add(_json_buffer, s, 0);
		JSON_END();
	}

ERASEABLE
int json_parse(string in, bool() func)
{
	string trimmed = "";
	LABEL(trim)
	{
		int o = strstrofs(in, "\"", 0);
		if (o >= 0)
		{
			string part = substring(in, 0, o + 1);
			in = substring(in, o + 1, -1);
			part = strreplace(" ", "", part);
			part = strreplace("\n", "", part);
			trimmed = strcat(trimmed, part);
			goto trim_str;
		}
		else
		{
			string part = in;
			part = strreplace(" ", "", part);
			part = strreplace("\n", "", part);
			trimmed = strcat(trimmed, part);
			goto done;
		}
	}
	LABEL(trim_str)
	{
		int o = strstrofs(in, "\"", 0);
		int esc = strstrofs(in, "\\\"", 0);
		if (o < esc || esc < 0) // simple string
		{
			string part = substring(in, 0, o + 1);
			in = substring(in, o + 1, -1);
			trimmed = strcat(trimmed, part);
			goto trim;
		}
		else // has escape
		{
			string part = substring(in, 0, esc + 2);
			in = substring(in, esc + 2, -1);
			trimmed = strcat(trimmed, part);
			goto trim_str;
		}
	}
	LABEL(done);

	STRING_ITERATOR_SET(_json, trimmed, 0);
	_json_buffer = buf_create();
	bool ret = func();
	if (!ret)
	{
		buf_del(_json_buffer);
		_json_buffer = -1;
	}
	return _json_buffer;
}

ERASEABLE
string json_get(int buf, string key)
{
	for (int i = 1, n = buf_getsize(buf); i < n; i += 2)
		if (bufstr_get(buf, i) == key)
			return bufstr_get(buf, i + 1);
	return string_null;
}

ERASEABLE
void json_del(int buf)
{
	buf_del(buf);
}

ERASEABLE
void json_dump(int buf)
{
	for (int i = 0, n = buf_getsize(buf); i < n; ++i)
		print(bufstr_get(buf, i), "\n");
}

#undef JSON_BEGIN
#undef JSON_FAIL
#undef JSON_END

TEST(json, Parse)
{
	string s = "{\n"
	"    \"m_string\": \"\\\"string\\\"\",\n"
	"    \"m_int\": 123,\n"
	"    \"m_bool\": true,\n"
	"    \"m_null\": null,\n"
	"    \"m_obj\": { },\n"
	"    \"m_arr\": [ ]\n"
	"}";
	print(s, "\n");
	int buf = json_parse(s, _json_parse_object);
	EXPECT_NE(-1, buf);
	json_dump(buf);
	SUCCEED();
}
