
/* vim: set et ts=3 sw=3 sts=3 ft=c:
 *
 * Copyright (C) 2014 James McLaughlin.  All rights reserved.
 * https://github.com/udp/json-builder
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "json-builder.h"
#include "emyg-dtoa/emyg-dtoa.h"

#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <math.h>

#ifdef _MSC_VER
#define snprintf _snprintf
#endif

const static json_serialize_opts default_opts = {
    json_serialize_mode_single_line, 0, 3 /* indent_size */
};

typedef struct json_builder_value {
	json_value value;

	int is_builder_value;

	size_t additional_length_allocated;
	size_t length_iterated;

} json_builder_value;

static int builderize(json_value *value) {
	if (((json_builder_value *)value)->is_builder_value) return 1;

	if (value->type == json_object) {
		unsigned int i;

		/* Values straight out of the parser have the names of object entries
		 * allocated in the same allocation as the values array itself.  This is
		 * not desirable when manipulating values because the names would be easy
		 * to clobber.
		 */
		for (i = 0; i < value->u.object.length; ++i) {
			json_char *name_copy;
			json_object_entry *entry = &value->u.object.values[i];

			if (!(name_copy = (json_char *)malloc((entry->name_length + 1) * sizeof(json_char)))) return 0;

			memcpy(name_copy, entry->name, entry->name_length + 1);
			entry->name = name_copy;
		}
	}

	((json_builder_value *)value)->is_builder_value = 1;

	return 1;
}

const size_t json_builder_extra = sizeof(json_builder_value) - sizeof(json_value);

/* These flags are set up from the opts before serializing to make the
 * serializer conditions simpler.
 */
const int f_spaces_around_brackets = (1 << 0);
const int f_spaces_after_commas = (1 << 1);
const int f_spaces_after_colons = (1 << 2);
const int f_tabs = (1 << 3);

static int get_serialize_flags(json_serialize_opts opts) {
	int flags = 0;

	if (opts.mode == json_serialize_mode_packed) return 0;

	if (opts.mode == json_serialize_mode_multiline) {
		if (opts.opts & json_serialize_opt_use_tabs) flags |= f_tabs;
	} else {
		if (!(opts.opts & json_serialize_opt_pack_brackets)) flags |= f_spaces_around_brackets;

		if (!(opts.opts & json_serialize_opt_no_space_after_comma)) flags |= f_spaces_after_commas;
	}

	if (!(opts.opts & json_serialize_opt_no_space_after_colon)) flags |= f_spaces_after_colons;

	return flags;
}

json_value *json_array_new(size_t length) {
	json_value *value = (json_value *)calloc(1, sizeof(json_builder_value));

	if (!value) return NULL;

	((json_builder_value *)value)->is_builder_value = 1;

	value->type = json_array;

	if (!(value->u.array.values = (json_value **)malloc(length * sizeof(json_value *)))) {
		free(value);
		return NULL;
	}

	((json_builder_value *)value)->additional_length_allocated = length;

	return value;
}

json_value *json_array_push(json_value *array, json_value *value) {
	assert(array->type == json_array);

	if (!builderize(array) || !builderize(value)) return NULL;

	if (((json_builder_value *)array)->additional_length_allocated > 0) {
		--((json_builder_value *)array)->additional_length_allocated;
	} else {
		json_value **values_new =
		    (json_value **)realloc(array->u.array.values, sizeof(json_value *) * (array->u.array.length + 1));

		if (!values_new) return NULL;

		array->u.array.values = values_new;
	}

	array->u.array.values[array->u.array.length] = value;
	++array->u.array.length;

	value->parent = array;

	return value;
}

json_value *json_object_new(size_t length) {
	json_value *value = (json_value *)calloc(1, sizeof(json_builder_value));

	if (!value) return NULL;

	((json_builder_value *)value)->is_builder_value = 1;

	value->type = json_object;

	if (!(value->u.object.values = (json_object_entry *)calloc(length, sizeof(*value->u.object.values)))) {
		free(value);
		return NULL;
	}

	((json_builder_value *)value)->additional_length_allocated = length;

	return value;
}

json_value *json_object_push(json_value *object, const json_char *name, json_value *value) {
	return json_object_push_length(object, strlen(name), name, value);
}

json_value *json_object_push_length(json_value *object, unsigned int name_length, const json_char *name,
                                    json_value *value) {
	json_char *name_copy;

	assert(object->type == json_object);

	if (!(name_copy = (json_char *)malloc((name_length + 1) * sizeof(json_char)))) return NULL;

	memcpy(name_copy, name, name_length * sizeof(json_char));
	name_copy[name_length] = 0;

	if (!json_object_push_nocopy(object, name_length, name_copy, value)) {
		free(name_copy);
		return NULL;
	}

	return value;
}

json_value *json_object_push_nocopy(json_value *object, unsigned int name_length, json_char *name, json_value *value) {
	json_object_entry *entry;

	assert(object->type == json_object);

	if (!builderize(object) || !builderize(value)) return NULL;

	if (((json_builder_value *)object)->additional_length_allocated > 0) {
		--((json_builder_value *)object)->additional_length_allocated;
	} else {
		json_object_entry *values_new = (json_object_entry *)realloc(
		    object->u.object.values, sizeof(*object->u.object.values) * (object->u.object.length + 1));

		if (!values_new) return NULL;

		object->u.object.values = values_new;
	}

	entry = object->u.object.values + object->u.object.length;

	entry->name_length = name_length;
	entry->name = name;
	entry->value = value;

	++object->u.object.length;

	value->parent = object;

	return value;
}

json_value *json_string_new(const json_char *buf) {
	return json_string_new_length(strlen(buf), buf);
}

json_value *json_string_new_length(unsigned int length, const json_char *buf) {
	json_value *value;
	json_char *copy = (json_char *)malloc((length + 1) * sizeof(json_char));

	if (!copy) return NULL;

	memcpy(copy, buf, length * sizeof(json_char));
	copy[length] = 0;

	if (!(value = json_string_new_nocopy(length, copy))) {
		free(copy);
		return NULL;
	}

	return value;
}

json_value *json_string_new_nocopy(unsigned int length, json_char *buf) {
	json_value *value = (json_value *)calloc(1, sizeof(json_builder_value));

	if (!value) return NULL;

	((json_builder_value *)value)->is_builder_value = 1;

	value->type = json_string;
	value->u.string.length = length;
	value->u.string.ptr = buf;

	return value;
}

json_value *json_integer_new(json_int_t integer) {
	json_value *value = (json_value *)calloc(1, sizeof(json_builder_value));

	if (!value) return NULL;

	((json_builder_value *)value)->is_builder_value = 1;

	value->type = json_integer;
	value->u.integer = integer;

	return value;
}

json_value *json_double_new(double dbl) {
	json_value *value = (json_value *)calloc(1, sizeof(json_builder_value));

	if (!value) return NULL;

	((json_builder_value *)value)->is_builder_value = 1;

	value->type = json_double;
	value->u.dbl = dbl;

	return value;
}

json_value *json_boolean_new(int b) {
	json_value *value = (json_value *)calloc(1, sizeof(json_builder_value));

	if (!value) return NULL;

	((json_builder_value *)value)->is_builder_value = 1;

	value->type = json_boolean;
	value->u.boolean = b;

	return value;
}

json_value *json_null_new(void) {
	json_value *value = (json_value *)calloc(1, sizeof(json_builder_value));

	if (!value) return NULL;

	((json_builder_value *)value)->is_builder_value = 1;

	value->type = json_null;

	return value;
}

void json_object_sort(json_value *object, json_value *proto) {
	unsigned int i, out_index = 0;

	if (!builderize(object)) return; /* TODO error */

	assert(object->type == json_object);
	assert(proto->type == json_object);

	for (i = 0; i < proto->u.object.length; ++i) {
		unsigned int j;
		json_object_entry proto_entry = proto->u.object.values[i];

		for (j = 0; j < object->u.object.length; ++j) {
			json_object_entry entry = object->u.object.values[j];

			if (entry.name_length != proto_entry.name_length) continue;

			if (memcmp(entry.name, proto_entry.name, entry.name_length) != 0) continue;

			object->u.object.values[j] = object->u.object.values[out_index];
			object->u.object.values[out_index] = entry;

			++out_index;
		}
	}
}

json_value *json_object_merge(json_value *objectA, json_value *objectB) {
	unsigned int i;

	assert(objectA->type == json_object);
	assert(objectB->type == json_object);
	assert(objectA != objectB);

	if (!builderize(objectA) || !builderize(objectB)) return NULL;

	if (objectB->u.object.length <= ((json_builder_value *)objectA)->additional_length_allocated) {
		((json_builder_value *)objectA)->additional_length_allocated -= objectB->u.object.length;
	} else {
		json_object_entry *values_new;

		unsigned int alloc = objectA->u.object.length + ((json_builder_value *)objectA)->additional_length_allocated +
		                     objectB->u.object.length;

		if (!(values_new = (json_object_entry *)realloc(objectA->u.object.values, sizeof(json_object_entry) * alloc))) {
			return NULL;
		}

		objectA->u.object.values = values_new;
	}

	for (i = 0; i < objectB->u.object.length; ++i) {
		json_object_entry *entry = &objectA->u.object.values[objectA->u.object.length + i];

		*entry = objectB->u.object.values[i];
		entry->value->parent = objectA;
	}

	objectA->u.object.length += objectB->u.object.length;

	free(objectB->u.object.values);
	free(objectB);

	return objectA;
}

static size_t measure_string(unsigned int length, const json_char *str) {
	unsigned int i;
	size_t measured_length = 0;

	for (i = 0; i < length; ++i) {
		json_char c = str[i];

		switch (c) {
			case 0:
			case 0xb:
				measured_length += 6;
				break;
			case '"':
			case '\\':
			case '\b':
			case '\f':
			case '\n':
			case '\r':
			case '\t':

				measured_length += 2;
				break;

			default:

				++measured_length;
				break;
		};
	};

	return measured_length;
}

#define PRINT_ESCAPED(c)                                                                                               \
	do {                                                                                                               \
		*buf++ = '\\';                                                                                                 \
		*buf++ = (c);                                                                                                  \
	} while (0);

static size_t serialize_string(json_char *buf, unsigned int length, const json_char *str) {
	json_char *orig_buf = buf;
	unsigned int i;

	for (i = 0; i < length; ++i) {
		json_char c = str[i];

		switch (c) {
			case 0:
				*buf++ = '\\';
				*buf++ = 'u';
				*buf++ = '0';
				*buf++ = '0';
				*buf++ = '0';
				*buf++ = '0';
				continue;
			case '"':
				PRINT_ESCAPED('\"');
				continue;
			case '\\':
				PRINT_ESCAPED('\\');
				continue;
			case '\b':
				PRINT_ESCAPED('b');
				continue;
			case '\f':
				PRINT_ESCAPED('f');
				continue;
			case '\n':
				PRINT_ESCAPED('n');
				continue;
			case '\r':
				PRINT_ESCAPED('r');
				continue;
			case '\t':
				PRINT_ESCAPED('t');
				continue;
			case 0xb:
				*buf++ = '\\';
				*buf++ = 'u';
				*buf++ = '0';
				*buf++ = '0';
				*buf++ = '0';
				*buf++ = 'b';
				continue;

			default:

				*buf++ = c;
				break;
		};
	};

	return buf - orig_buf;
}

size_t json_measure(json_value *value) {
	return json_measure_ex(value, default_opts);
}

#define MEASURE_NEWLINE()                                                                                              \
	do {                                                                                                               \
		++newlines;                                                                                                    \
		indents += depth;                                                                                              \
	} while (0);

size_t json_measure_ex(json_value *value, json_serialize_opts opts) {
	size_t total = 1; /* null terminator */
	size_t newlines = 0;
	size_t depth = 0;
	size_t indents = 0;
	int flags;
	int bracket_size, comma_size, colon_size;

	flags = get_serialize_flags(opts);

	/* to reduce branching
	 */
	bracket_size = flags & f_spaces_around_brackets ? 2 : 1;
	comma_size = flags & f_spaces_after_commas ? 2 : 1;
	colon_size = flags & f_spaces_after_colons ? 2 : 1;

	while (value) {
		json_int_t integer;
		json_object_entry *entry;

		switch (value->type) {
			case json_array:

				if (((json_builder_value *)value)->length_iterated == 0) {
					if (value->u.array.length == 0) {
						total += 2; /* `[]` */
						break;
					}

					total += bracket_size; /* `[` */

					++depth;
					MEASURE_NEWLINE(); /* \n after [ */
				}

				if (((json_builder_value *)value)->length_iterated == value->u.array.length) {
					--depth;
					MEASURE_NEWLINE();
					total += bracket_size; /* `]` */

					((json_builder_value *)value)->length_iterated = 0;
					break;
				}

				if (((json_builder_value *)value)->length_iterated > 0) {
					total += comma_size; /* `, ` */

					MEASURE_NEWLINE();
				}

				((json_builder_value *)value)->length_iterated++;
				value = value->u.array.values[((json_builder_value *)value)->length_iterated - 1];
				continue;

			case json_object:

				if (((json_builder_value *)value)->length_iterated == 0) {
					if (value->u.object.length == 0) {
						total += 2; /* `{}` */
						break;
					}

					total += bracket_size; /* `{` */

					++depth;
					MEASURE_NEWLINE(); /* \n after { */
				}

				if (((json_builder_value *)value)->length_iterated == value->u.object.length) {
					--depth;
					MEASURE_NEWLINE();
					total += bracket_size; /* `}` */

					((json_builder_value *)value)->length_iterated = 0;
					break;
				}

				if (((json_builder_value *)value)->length_iterated > 0) {
					total += comma_size; /* `, ` */
					MEASURE_NEWLINE();
				}

				entry = value->u.object.values + (((json_builder_value *)value)->length_iterated++);

				total += 2 + colon_size; /* `"": ` */
				total += measure_string(entry->name_length, entry->name);

				value = entry->value;
				continue;
#ifdef CARYLL_USE_PRE_SERIALIZED
			case json_pre_serialized:

				total += value->u.string.length;
				break;
#endif
			case json_string:
				total += 2; /* `""` */
				total += measure_string(value->u.string.length, value->u.string.ptr);
				break;

			case json_integer:

				integer = value->u.integer;

				if (integer < 0) {
					total += 1; /* `-` */
					integer = -integer;
				}

				++total; /* first digit */

				while (integer >= 10) {
					++total; /* another digit */
					integer /= 10;
				}

				break;

			case json_double: {
				char buffer[256];
				emyg_dtoa(value->u.dbl, buffer);
				total += strlen(buffer);
				break;
			}

			case json_boolean:

				total += value->u.boolean ? 4 : /* `true` */
				             5;                 /* `false` */

				break;

			case json_null:

				total += 4; /* `null` */
				break;

			default:
				break;
		};

		value = value->parent;
	}

	if (opts.mode == json_serialize_mode_multiline) {
		total += newlines * (((opts.opts & json_serialize_opt_CRLF) ? 2 : 1) + opts.indent_size);
		total += indents * opts.indent_size;
	}

	return total;
}

void json_serialize(json_char *buf, json_value *value) {
	json_serialize_ex(buf, value, default_opts);
}

#define PRINT_NEWLINE()                                                                                                \
	do {                                                                                                               \
		if (opts.mode == json_serialize_mode_multiline) {                                                              \
			if (opts.opts & json_serialize_opt_CRLF) *buf++ = '\r';                                                    \
			*buf++ = '\n';                                                                                             \
			for (i = 0; i < indent; ++i)                                                                               \
				*buf++ = indent_char;                                                                                  \
		}                                                                                                              \
	} while (0);

#define PRINT_OPENING_BRACKET(c)                                                                                       \
	do {                                                                                                               \
		*buf++ = (c);                                                                                                  \
		if (flags & f_spaces_around_brackets) *buf++ = ' ';                                                            \
	} while (0);

#define PRINT_CLOSING_BRACKET(c)                                                                                       \
	do {                                                                                                               \
		if (flags & f_spaces_around_brackets) *buf++ = ' ';                                                            \
		*buf++ = (c);                                                                                                  \
	} while (0);

void json_serialize_ex(json_char *buf, json_value *value, json_serialize_opts opts) {
	json_int_t integer, orig_integer;
	json_object_entry *entry;
	json_char *ptr;
	int indent = 0;
	char indent_char;
	int i;
	int flags;

	flags = get_serialize_flags(opts);

	indent_char = flags & f_tabs ? '\t' : ' ';

	while (value) {
		switch (value->type) {
			case json_array:

				if (((json_builder_value *)value)->length_iterated == 0) {
					if (value->u.array.length == 0) {
						*buf++ = '[';
						*buf++ = ']';

						break;
					}

					PRINT_OPENING_BRACKET('[');

					indent += opts.indent_size;
					PRINT_NEWLINE();
				}

				if (((json_builder_value *)value)->length_iterated == value->u.array.length) {
					indent -= opts.indent_size;
					PRINT_NEWLINE();
					PRINT_CLOSING_BRACKET(']');

					((json_builder_value *)value)->length_iterated = 0;
					break;
				}

				if (((json_builder_value *)value)->length_iterated > 0) {
					*buf++ = ',';

					if (flags & f_spaces_after_commas) *buf++ = ' ';

					PRINT_NEWLINE();
				}

				((json_builder_value *)value)->length_iterated++;
				value = value->u.array.values[((json_builder_value *)value)->length_iterated - 1];
				continue;

			case json_object:

				if (((json_builder_value *)value)->length_iterated == 0) {
					if (value->u.object.length == 0) {
						*buf++ = '{';
						*buf++ = '}';

						break;
					}

					PRINT_OPENING_BRACKET('{');

					indent += opts.indent_size;
					PRINT_NEWLINE();
				}

				if (((json_builder_value *)value)->length_iterated == value->u.object.length) {
					indent -= opts.indent_size;
					PRINT_NEWLINE();
					PRINT_CLOSING_BRACKET('}');

					((json_builder_value *)value)->length_iterated = 0;
					break;
				}

				if (((json_builder_value *)value)->length_iterated > 0) {
					*buf++ = ',';

					if (flags & f_spaces_after_commas) *buf++ = ' ';

					PRINT_NEWLINE();
				}

				entry = value->u.object.values + (((json_builder_value *)value)->length_iterated++);

				*buf++ = '\"';
				buf += serialize_string(buf, entry->name_length, entry->name);
				*buf++ = '\"';
				*buf++ = ':';

				if (flags & f_spaces_after_colons) *buf++ = ' ';

				value = entry->value;
				continue;
#ifdef CARYLL_USE_PRE_SERIALIZED
			case json_pre_serialized:
				memcpy(buf, value->u.string.ptr, value->u.string.length);
				buf += value->u.string.length;
				break;
#endif
			case json_string:

				*buf++ = '\"';
				buf += serialize_string(buf, value->u.string.length, value->u.string.ptr);
				*buf++ = '\"';
				break;

			case json_integer:

				integer = value->u.integer;

				if (integer < 0) {
					*buf++ = '-';
					integer = -integer;
				}

				orig_integer = integer;

				++buf;

				while (integer >= 10) {
					++buf;
					integer /= 10;
				}

				integer = orig_integer;
				ptr = buf;

				do {
					*--ptr = "0123456789"[integer % 10];
				} while ((integer /= 10) > 0);

				break;

			case json_double: {
				char tmp[256];
				emyg_dtoa(value->u.dbl, tmp);
				memcpy(buf, tmp, strlen(tmp));
				buf += strlen(tmp);
				break;
			}

			case json_boolean:

				if (value->u.boolean) {
					memcpy(buf, "true", 4);
					buf += 4;
				} else {
					memcpy(buf, "false", 5);
					buf += 5;
				}

				break;

			case json_null:

				memcpy(buf, "null", 4);
				buf += 4;
				break;

			default:
				break;
		};

		value = value->parent;
	}

	*buf = 0;
}

void json_builder_free(json_value *value) {
	json_value *cur_value;

	if (!value) return;

	value->parent = 0;

	while (value) {
		switch (value->type) {
			case json_array:

				if (!value->u.array.length) {
					free(value->u.array.values);
					break;
				}

				value = value->u.array.values[--value->u.array.length];
				continue;

			case json_object:

				if (!value->u.object.length) {
					free(value->u.object.values);
					break;
				}

				--value->u.object.length;

				if (((json_builder_value *)value)->is_builder_value) {
					/* Names are allocated separately for builder values.  In parser
					 * values, they are part of the same allocation as the values array
					 * itself.
					 */
					free(value->u.object.values[value->u.object.length].name);
				}

				value = value->u.object.values[value->u.object.length].value;
				continue;

			case json_string:
#ifdef CARYLL_USE_PRE_SERIALIZED
			case json_pre_serialized:
#endif
				free(value->u.string.ptr);
				break;

			default:
				break;
		};

		cur_value = value;
		value = value->parent;
		free(cur_value);
	}
}
