#include "crash.h"


#if defined(_MSC_VER)
typedef unsigned __int8  u8;
typedef unsigned __int32 u32;
#else
typedef uint8_t  u8;
typedef uint32_t u32;
#endif


static qbool UTF8_NextCodePoint(u32* codePoint, u32* byteCount, const char* input)
{
	if (!codePoint || !byteCount || !input || *input == '\0')
		return qfalse;

	u8 byte0 = (u8)input[0];
	if (byte0 <= 127) {
		*codePoint = (u32)byte0;
		*byteCount = 1;
		return qtrue;
	}

	// Starts with 110?
	if ((byte0 >> 5) == 6) {
		const u8 byte1 = (u8)input[1];
		*codePoint = ((u32)byte1 & 63) | (((u32)byte0 & 31) << 6);
		*byteCount = 2;
		return qtrue;
	}

	// Starts with 1110?
	if ((byte0 >> 4) == 14) {
		const u8 byte1 = (u8)input[1];
		const u8 byte2 = (u8)input[2];
		*codePoint = ((u32)byte2 & 63) | (((u32)byte1 & 63) << 6) | (((u32)byte0 & 15) << 12);
		*byteCount = 3;
		return qtrue;
	}
	
	// Starts with 11110?
	if ((byte0 >> 3) == 30) {
		const u8 byte1 = (u8)input[1];
		const u8 byte2 = (u8)input[2];
		const u8 byte3 = (u8)input[3];
		*codePoint = ((u32)byte3 & 63) | (((u32)byte2 & 63) << 6) | (((u32)byte1 & 63) << 12) | (((u32)byte0 & 7) << 18);
		*byteCount = 4;
		return qtrue;
	}
	
	return qfalse;
}

typedef struct {
	u32 CodePoint;
	char OutputChar;
} ShortEscape;

#define ENTRY(HexValue, Char) { 0x##HexValue, Char }
static const ShortEscape ShortEscapeCodePoints[] = {
	ENTRY(0022, '"'),
	ENTRY(005C, '\\'),
	ENTRY(002F, '/'),
	ENTRY(0008, 'b'),
	ENTRY(000C, 'f'),
	ENTRY(000A, 'n'),
	ENTRY(000D, 'r'),
	ENTRY(0009, 't')
};
#undef ENTRY

static const char HexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

static qbool UTF8_NeedsEscaping(u32* newLength, u32 codePoint)
{
	if (!newLength)
		return qfalse;

	const u32 count = (u32)ARRAY_LEN(ShortEscapeCodePoints);
	for (u32 i = 0; i < count; i++) {
		if (codePoint == ShortEscapeCodePoints[i].CodePoint) {
			*newLength = 2; // Of form: "\A".
			return qtrue;
		}
	}

	// Range: 0x0000 - 0x001F
	if (codePoint <= 0x001F) {
		*newLength = 6; // Of form: "\uABCD".
		return qtrue;
	}

	return qfalse;
}

static void UTF8_WriteCodePoint(u32* newLength, char* output, u32 codePoint, const char* input)
{
	if (!newLength || !output || !input)
		return;

	const u32 count = (u32)ARRAY_LEN(ShortEscapeCodePoints);
	for (u32 i = 0; i < count; i++) {
		if (codePoint == ShortEscapeCodePoints[i].CodePoint) {
			*newLength = 2;
			output[0] = '\\';
			output[1] = ShortEscapeCodePoints[i].OutputChar;
			return;
		}
	}

	// Range: 0x0000 - 0x001F
	if (codePoint <= 0x001F) {
		*newLength = 6;
		output[0] = '\\';
		output[1] = 'u';
		output[2] = HexDigits[(codePoint >> 12) & 15];
		output[3] = HexDigits[(codePoint >> 8) & 15];
		output[4] = HexDigits[(codePoint >> 4) & 15];
		output[5] = HexDigits[codePoint & 15];
		return;
	}

	for (u32 i = 0; i < *newLength; ++i) {
		output[i] = input[i];
	}
}

static u32 UTF8_LengthForJSON(const char* input)
{
	if (!input)
		return 0;

	const char* s = input;
	u32 codePoint = 0;
	u32 byteCount = 0;
	u32 newLength = 0;
	while (UTF8_NextCodePoint(&codePoint, &byteCount, s)) {
		s += byteCount;
		UTF8_NeedsEscaping(&byteCount, codePoint);
		newLength += byteCount;
	}

	return newLength;
}

static void UTF8_CleanForJSON(char* output, const char* input)
{
	if (!output || !input)
		return;

	const char* in = input;
	char* out = output;
	u32 codePoint = 0;
	u32 inputByteCount = 0;
	while (UTF8_NextCodePoint(&codePoint, &inputByteCount, in)) {
		u32 outputByteCount = inputByteCount;
		UTF8_WriteCodePoint(&outputByteCount, out, codePoint, in);
		in += inputByteCount;
		out += outputByteCount;
	}

	*out = '\0';
}

typedef struct {
	unsigned int itemIndices[16];
	FILE* file;
	unsigned int level;
} JSONWriter;

static JSONWriter writer;

static void JSONW_Write(const char* string)
{
	if (!string)
		return;

	fwrite(string, strlen(string), 1, writer.file);
}

static void JSONW_WriteNewLine()
{
	JSONW_Write("\n");
	for (u32 i = 0; i < writer.level; i++) {
		JSONW_Write("\t");
	}
}

static void JSONW_WriteClean(const char* string)
{
	static char buffer[4096];

	if (!string)
		return;

	const u32 length = UTF8_LengthForJSON(string);
	if (length >= sizeof(buffer)) {
		JSONW_Write("bufferSizeTooSmall");
		return;
	}

	UTF8_CleanForJSON(buffer, string);
	JSONW_Write(buffer);
}

void JSONW_BeginFile(FILE* file)
{
	memset(&writer, 0, sizeof(writer));
	writer.file = file;
	
	JSONW_Write("{");
	++writer.level;
}

void JSONW_EndFile()
{
	JSONW_Write("\r\n}");
}

void JSONW_BeginObject()
{
	if (writer.itemIndices[writer.level] > 0)
		JSONW_Write(",");

	JSONW_WriteNewLine();
	JSONW_Write("{");
	++writer.level;
	writer.itemIndices[writer.level] = 0;
}

void JSONW_BeginNamedObject(const char* name)
{
	if (!name)
		return;

	if (writer.itemIndices[writer.level] > 0)
		JSONW_Write(",");

	JSONW_WriteNewLine();
	JSONW_Write("\"");
	JSONW_WriteClean(name);
	JSONW_Write("\":");
	JSONW_WriteNewLine();
	JSONW_Write("{");
	++writer.level;
	writer.itemIndices[writer.level] = 0;
}

void JSONW_EndObject()
{
	--writer.level;
	JSONW_WriteNewLine();
	JSONW_Write("}");
	++writer.itemIndices[writer.level];
}

void JSONW_BeginArray()
{
	if (writer.itemIndices[writer.level] > 0)
		JSONW_Write(",");

	JSONW_WriteNewLine();
	JSONW_Write("[");
	++writer.level;
	writer.itemIndices[writer.level] = 0;
}

void JSONW_BeginNamedArray(const char* name)
{
	if (!name)
		return;

	if (writer.itemIndices[writer.level] > 0)
		JSONW_Write(",");

	JSONW_WriteNewLine();
	JSONW_Write("\"");
	JSONW_WriteClean(name);
	JSONW_Write("\":");
	JSONW_WriteNewLine();
	JSONW_Write("[");
	++writer.level;
	writer.itemIndices[writer.level] = 0;
}

void JSONW_EndArray()
{
	--writer.level;
	JSONW_WriteNewLine();
	JSONW_Write("]");
	++writer.itemIndices[writer.level];
}

void JSONW_IntegerValue(const char* name, int number)
{
	if (!name)
		return;

	JSONW_StringValue(name, "%d", number);
}

void JSONW_HexValue(const char* name, uint64_t number)
{
	if (!name)
		return;

	JSONW_StringValue(name, Q_itohex(number, qtrue, qtrue));
}

void JSONW_BooleanValue(const char* name, qbool value)
{
	JSONW_StringValue(name, value ? "true" : "false");
}

void JSONW_StringValue(const char* name, const char* format, ...)
{
	static char buffer[4096];

	if (!name || !format || *format == '\0')
		return;

	va_list ap;
	va_start(ap, format);
	Q_vsnprintf(buffer, sizeof(buffer), format, ap);
	va_end(ap);

	if (writer.itemIndices[writer.level] > 0)
		JSONW_Write(", ");

	JSONW_WriteNewLine();
	JSONW_Write("\"");
	JSONW_Write(name);
	JSONW_Write("\": \"");
	JSONW_WriteClean(buffer);
	JSONW_Write("\"");
	++writer.itemIndices[writer.level];
}

void JSONW_UnnamedHex(uint64_t number)
{
	JSONW_UnnamedString(Q_itohex(number, qtrue, qtrue));
}

void JSONW_UnnamedString(const char* format, ...)
{
	static char buffer[4096];

	if (!format || *format == '\0')
		return;

	va_list ap;
	va_start(ap, format);
	Q_vsnprintf(buffer, sizeof(buffer), format, ap);
	va_end(ap);

	if (writer.itemIndices[writer.level] > 0)
		JSONW_Write(", ");

	JSONW_WriteNewLine();
	JSONW_Write("\"");
	JSONW_WriteClean(buffer);
	JSONW_Write("\"");
	++writer.itemIndices[writer.level];
}
