/*
 * Copyright (c) 2019-2021 Joris Vink <joris@coders.se>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>

#include <float.h>
#include <inttypes.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

#include "kore.h"

static int	json_guess_type(u_int8_t, u_int32_t *);
static int	json_next(struct kore_json *, u_int8_t *);
static int	json_peek(struct kore_json *, u_int8_t *);

static int	json_consume_whitespace(struct kore_json *);
static int	json_next_byte(struct kore_json *, u_int8_t *, int);

static char	*json_get_string(struct kore_json *);

static int	json_parse_array(struct kore_json *, struct kore_json_item *);
static int	json_parse_object(struct kore_json *, struct kore_json_item *);
static int	json_parse_string(struct kore_json *, struct kore_json_item *);
static int	json_parse_number(struct kore_json *, struct kore_json_item *);
static int	json_parse_literal(struct kore_json *, struct kore_json_item *);

static struct kore_json_item	*json_item_alloc(int, const char *,
				    struct kore_json_item *);
static struct kore_json_item	*json_find_item(struct kore_json_item *,
				    char **, u_int32_t, int);

static u_int8_t		json_null_literal[] = { 'n', 'u', 'l', 'l' };
static u_int8_t		json_true_literal[] = { 't', 'r', 'u', 'e' };
static u_int8_t		json_false_literal[] = { 'f', 'a', 'l', 's', 'e' };

static const char *json_errtab[] = {
	"no error",
	"invalid JSON object",
	"invalid JSON array",
	"invalid JSON string",
	"invalid JSON number",
	"invalid JSON literal",
	"too many nested items",
	"end of stream while parsing JSON",
	"invalid JSON",
	"invalid search query specified",
	"item not found",
	"item found, but not expected value"
};

void
kore_json_init(struct kore_json *json, const void *data, size_t len)
{
	memset(json, 0, sizeof(*json));

	json->data = data;
	json->length = len;

	kore_buf_init(&json->tmpbuf, 1024);
}

int
kore_json_parse(struct kore_json *json)
{
	u_int8_t	ch;
	u_int32_t	type;

	if (json->root)
		return (KORE_RESULT_OK);

	if (json_consume_whitespace(json) == -1) {
		json->error = KORE_JSON_ERR_INVALID_JSON;
		return (KORE_RESULT_ERROR);
	}

	if (!json_peek(json, &ch))
		return (KORE_RESULT_ERROR);

	if (!json_guess_type(ch, &type)) {
		json->error = KORE_JSON_ERR_INVALID_JSON;
		return (KORE_RESULT_ERROR);
	}

	json->root = json_item_alloc(type, NULL, NULL);

	if (!json->root->parse(json, json->root)) {
		if (json->error == 0)
			json->error = KORE_JSON_ERR_INVALID_JSON;
		return (KORE_RESULT_ERROR);
	}

	/* Don't allow garbage at the end. */
	(void)json_consume_whitespace(json);
	if (json->offset != json->length) {
		json->error = KORE_JSON_ERR_INVALID_JSON;
		return (KORE_RESULT_ERROR);
	}

	return (KORE_RESULT_OK);
}

struct kore_json_item *
kore_json_find(struct kore_json_item *root, const char *path, u_int32_t type)
{
	struct kore_json_item	*item;
	char			*copy;
	char			*tokens[KORE_JSON_DEPTH_MAX + 1];

	copy = kore_strdup(path);

	if (!kore_split_string(copy, "/", tokens, KORE_JSON_DEPTH_MAX)) {
		kore_free(copy);
		return (NULL);
	}

	item = json_find_item(root, tokens, type, 0);
	kore_free(copy);

	return (item);
}

void
kore_json_cleanup(struct kore_json *json)
{
	if (json == NULL)
		return;

	kore_buf_cleanup(&json->tmpbuf);
	kore_json_item_free(json->root);
}

const char *
kore_json_strerror(struct kore_json *json)
{
	if (json->error >= 0 && json->error <= KORE_JSON_ERR_LAST)
		return (json_errtab[json->error]);

	return ("unknown JSON error");
}

struct kore_json_item *
kore_json_create_item(struct kore_json_item *parent, const char *name,
    u_int32_t type, ...)
{
	const char			*p;
	va_list				args;
	struct kore_json_item		*item;

	item = kore_calloc(1, sizeof(*item));
	item->type = type;

	va_start(args, type);

	switch (item->type) {
	case KORE_JSON_TYPE_OBJECT:
		TAILQ_INIT(&item->data.items);
		break;
	case KORE_JSON_TYPE_ARRAY:
		TAILQ_INIT(&item->data.items);
		break;
	case KORE_JSON_TYPE_STRING:
		p = va_arg(args, const char *);
		item->data.string = kore_strdup(p);
		break;
	case KORE_JSON_TYPE_NUMBER:
		item->data.number = va_arg(args, double);
		break;
	case KORE_JSON_TYPE_INTEGER:
		item->data.s64 = va_arg(args, int64_t);
		break;
	case KORE_JSON_TYPE_INTEGER_U64:
		item->data.u64 = va_arg(args, u_int64_t);
		break;
	case KORE_JSON_TYPE_LITERAL:
		item->data.literal = va_arg(args, int);
		break;
	default:
		fatal("%s: unknown type %d", __func__, item->type);
	}

	if (name)
		item->name = kore_strdup(name);

	if (parent) {
		if (parent->type != KORE_JSON_TYPE_OBJECT &&
		    parent->type != KORE_JSON_TYPE_ARRAY) {
			fatal("%s: invalid parent type (%d)",
			    __func__, parent->type);
		}

		TAILQ_INSERT_TAIL(&parent->data.items, item, list);
	}

	va_end(args);

	return (item);
}

void
kore_json_item_tobuf(struct kore_json_item *item, struct kore_buf *buf)
{
	struct kore_json_item	*nitem;

	if (item->name)
		kore_buf_appendf(buf, "\"%s\":", item->name);

	switch (item->type) {
	case KORE_JSON_TYPE_OBJECT:
		kore_buf_appendf(buf, "{");
		TAILQ_FOREACH(nitem, &item->data.items, list) {
			kore_json_item_tobuf(nitem, buf);

			if (TAILQ_NEXT(nitem, list))
				kore_buf_appendf(buf, ",");
		}
		kore_buf_appendf(buf, "}");
		break;
	case KORE_JSON_TYPE_ARRAY:
		kore_buf_appendf(buf, "[");
		TAILQ_FOREACH(nitem, &item->data.items, list) {
			kore_json_item_tobuf(nitem, buf);

			if (TAILQ_NEXT(nitem, list))
				kore_buf_appendf(buf, ",");
		}
		kore_buf_appendf(buf, "]");
		break;
	case KORE_JSON_TYPE_STRING:
		kore_buf_appendf(buf, "\"%s\"", item->data.string);
		break;
	case KORE_JSON_TYPE_NUMBER:
		kore_buf_appendf(buf, "%f", item->data.number);
		break;
	case KORE_JSON_TYPE_INTEGER:
		kore_buf_appendf(buf, "%" PRId64, item->data.s64);
		break;
	case KORE_JSON_TYPE_INTEGER_U64:
		kore_buf_appendf(buf, "%" PRIu64, item->data.u64);
		break;
	case KORE_JSON_TYPE_LITERAL:
		switch (item->data.literal) {
		case KORE_JSON_TRUE:
			kore_buf_append(buf,
			    json_true_literal, sizeof(json_true_literal));
			break;
		case KORE_JSON_FALSE:
			kore_buf_append(buf,
			    json_false_literal, sizeof(json_false_literal));
			break;
		case KORE_JSON_NULL:
			kore_buf_append(buf,
			    json_null_literal, sizeof(json_null_literal));
			break;
		default:
			fatal("%s: unknown literal %d", __func__,
			    item->data.literal);
		}
		break;
	default:
		fatal("%s: unknown type %d", __func__, item->type);
	}
}

static struct kore_json_item *
json_find_item(struct kore_json_item *object, char **tokens,
    u_int32_t type, int pos)
{
	char			*p, *str;
	struct kore_json_item	*item, *nitem;
	int			err, idx, spot;

	if (tokens[pos] == NULL)
		return (NULL);

	if (object->type != KORE_JSON_TYPE_OBJECT &&
	    object->type != KORE_JSON_TYPE_ARRAY)
		return (NULL);

	if ((str = strchr(tokens[pos], '[')) != NULL) {
		*(str)++ = '\0';

		if ((p = strchr(str, ']')) == NULL)
			return (NULL);

		*p = '\0';

		spot = kore_strtonum(str, 10, 0, USHRT_MAX, &err);
		if (err != KORE_RESULT_OK)
			return (NULL);
	} else {
		spot = -1;
	}

	item = NULL;

	TAILQ_FOREACH(item, &object->data.items, list) {
		if (item->name && strcmp(item->name, tokens[pos]))
			continue;

		if (item->type == KORE_JSON_TYPE_ARRAY && spot != -1) {
			idx = 0;
			nitem = NULL;
			TAILQ_FOREACH(nitem, &item->data.items, list) {
				if (idx++ == spot)
					break;
			}

			if (nitem == NULL)
				return (NULL);

			item = nitem;
		}

		if (tokens[pos + 1] == NULL) {
			if (item->type == type)
				return (item);
			return (NULL);
		}

		if (item->type == KORE_JSON_TYPE_OBJECT ||
		    item->type == KORE_JSON_TYPE_ARRAY) {
			item = json_find_item(item, tokens, type, pos + 1);
		} else {
			item = NULL;
		}

		break;
	}

	return (item);
}

void
kore_json_item_free(struct kore_json_item *item)
{
	struct kore_json_item	*node;

	if (item == NULL)
		return;

	switch (item->type) {
	case KORE_JSON_TYPE_OBJECT:
	case KORE_JSON_TYPE_ARRAY:
		while ((node = TAILQ_FIRST(&item->data.items)) != NULL) {
			TAILQ_REMOVE(&item->data.items, node, list);
			kore_json_item_free(node);
		}
		break;
	case KORE_JSON_TYPE_STRING:
		kore_free(item->data.string);
		break;
	case KORE_JSON_TYPE_NUMBER:
	case KORE_JSON_TYPE_LITERAL:
	case KORE_JSON_TYPE_INTEGER:
	case KORE_JSON_TYPE_INTEGER_U64:
		break;
	default:
		fatal("%s: unknown type %d", __func__, item->type);
	}

	kore_free(item->name);
	kore_free(item);
}

static struct kore_json_item *
json_item_alloc(int type, const char *name, struct kore_json_item *parent)
{
	struct kore_json_item	*item;

	item = kore_calloc(1, sizeof(*item));
	item->type = type;
	item->parent = parent;

	switch (item->type) {
	case KORE_JSON_TYPE_OBJECT:
		TAILQ_INIT(&item->data.items);
		item->parse = json_parse_object;
		break;
	case KORE_JSON_TYPE_ARRAY:
		TAILQ_INIT(&item->data.items);
		item->parse = json_parse_array;
		break;
	case KORE_JSON_TYPE_STRING:
		item->parse = json_parse_string;
		break;
	case KORE_JSON_TYPE_NUMBER:
	case KORE_JSON_TYPE_INTEGER:
	case KORE_JSON_TYPE_INTEGER_U64:
		item->parse = json_parse_number;
		break;
	case KORE_JSON_TYPE_LITERAL:
		item->parse = json_parse_literal;
		break;
	default:
		fatal("%s: unknown type %d", __func__, item->type);
	}

	if (name)
		item->name = kore_strdup(name);

	if (parent) {
		if (parent->type != KORE_JSON_TYPE_OBJECT &&
		    parent->type != KORE_JSON_TYPE_ARRAY) {
			fatal("%s: invalid parent type (%d)",
			    __func__, parent->type);
		}

		TAILQ_INSERT_TAIL(&parent->data.items, item, list);
	}

	return (item);
}

static int
json_peek(struct kore_json *json, u_int8_t *ch)
{
	return (json_next_byte(json, ch, 1));
}

static int
json_next(struct kore_json *json, u_int8_t *ch)
{
	return (json_next_byte(json, ch, 0));
}

static int
json_next_byte(struct kore_json *json, u_int8_t *ch, int peek)
{
	if (json->offset >= json->length) {
		json->error = KORE_JSON_ERR_EOF;
		return (KORE_RESULT_ERROR);
	}

	*ch = json->data[json->offset];

	if (peek == 0)
		json->offset++;

	return (KORE_RESULT_OK);
}

static int
json_consume_whitespace(struct kore_json *json)
{
	u_int8_t	ch;

	for (;;) {
		if (!json_peek(json, &ch))
			return (KORE_RESULT_ERROR);

		if (ch != ' ' && ch != '\n' && ch != '\r' && ch != '\t')
			break;

		json->offset++;
	}

	return (KORE_RESULT_OK);
}

static int
json_guess_type(u_int8_t ch, u_int32_t *type)
{
	if (ch == '-' || (ch >= '0' && ch <= '9')) {
		*type = KORE_JSON_TYPE_NUMBER;
		return (KORE_RESULT_OK);
	}

	switch (ch) {
	case '{':
		*type = KORE_JSON_TYPE_OBJECT;
		break;
	case '"':
		*type = KORE_JSON_TYPE_STRING;
		break;
	case '[':
		*type = KORE_JSON_TYPE_ARRAY;
		break;
	case 'f':
	case 'n':
	case 't':
		*type = KORE_JSON_TYPE_LITERAL;
		break;
	default:
		return (KORE_RESULT_ERROR);
	}

	return (KORE_RESULT_OK);
}

static int
json_parse_object(struct kore_json *json, struct kore_json_item *object)
{
	u_int8_t		ch;
	u_int32_t		type;
	char			*key;
	struct kore_json_item	*item;
	int			ret, hasnext;

	if (json->depth++ >= KORE_JSON_DEPTH_MAX) {
		json->error = KORE_JSON_ERR_DEPTH;
		return (KORE_RESULT_ERROR);
	}

	key = NULL;
	hasnext = 0;
	ret = KORE_RESULT_ERROR;

	if (!json_next(json, &ch))
		goto cleanup;

	if (ch != '{')
		goto cleanup;

	for (;;) {
		if (!json_consume_whitespace(json))
			goto cleanup;

		if (!json_peek(json, &ch))
			goto cleanup;

		switch (ch) {
		case '}':
			if (hasnext) {
				json->error = KORE_JSON_ERR_INVALID_JSON;
				goto cleanup;
			}
			json->offset++;
			ret = KORE_RESULT_OK;
			goto cleanup;
		case '"':
			if ((key = json_get_string(json)) == NULL)
				goto cleanup;
			break;
		default:
			goto cleanup;
		}

		if (!json_consume_whitespace(json))
			goto cleanup;

		if (!json_next(json, &ch))
			goto cleanup;

		if (ch != ':')
			goto cleanup;

		if (!json_consume_whitespace(json))
			goto cleanup;

		if (!json_peek(json, &ch))
			goto cleanup;

		if (!json_guess_type(ch, &type))
			goto cleanup;

		item = json_item_alloc(type, key, object);

		if (!item->parse(json, item))
			goto cleanup;

		key = NULL;

		if (!json_consume_whitespace(json))
			goto cleanup;

		if (!json_next(json, &ch))
			goto cleanup;

		if (ch == ',') {
			hasnext = 1;
			continue;
		}

		if (ch == '}') {
			ret = KORE_RESULT_OK;
			break;
		}

		break;
	}

cleanup:
	if (ret == KORE_RESULT_ERROR && json->error == 0)
		json->error = KORE_JSON_ERR_INVALID_OBJECT;

	json->depth--;

	return (ret);
}

static int
json_parse_array(struct kore_json *json, struct kore_json_item *array)
{
	u_int8_t		ch;
	u_int32_t		type;
	char			*key;
	struct kore_json_item	*item;
	int			ret, hasnext;

	if (json->depth++ >= KORE_JSON_DEPTH_MAX) {
		json->error = KORE_JSON_ERR_DEPTH;
		return (KORE_RESULT_ERROR);
	}

	key = NULL;
	hasnext = 0;
	ret = KORE_RESULT_ERROR;

	if (!json_next(json, &ch))
		goto cleanup;

	if (ch != '[')
		goto cleanup;

	for (;;) {
		if (!json_consume_whitespace(json))
			goto cleanup;

		if (!json_peek(json, &ch))
			goto cleanup;

		if (ch == ']') {
			if (hasnext) {
				json->error = KORE_JSON_ERR_INVALID_JSON;
				goto cleanup;
			}
			json->offset++;
			ret = KORE_RESULT_OK;
			goto cleanup;
		}

		if (!json_guess_type(ch, &type))
			goto cleanup;

		item = json_item_alloc(type, key, array);

		if (!item->parse(json, item))
			goto cleanup;

		key = NULL;

		if (!json_consume_whitespace(json))
			goto cleanup;

		if (!json_next(json, &ch))
			goto cleanup;

		if (ch == ',') {
			hasnext = 1;
			continue;
		}

		if (ch == ']') {
			ret = KORE_RESULT_OK;
			break;
		}

		break;
	}

cleanup:
	if (ret == KORE_RESULT_ERROR && json->error == 0)
		json->error = KORE_JSON_ERR_INVALID_ARRAY;

	json->depth--;

	return (ret);
}

static int
json_parse_string(struct kore_json *json, struct kore_json_item *string)
{
	char		*value;

	if ((value = json_get_string(json)) == NULL)
		return (KORE_RESULT_ERROR);

	string->type = KORE_JSON_TYPE_STRING;
	string->data.string = kore_strdup(value);

	return (KORE_RESULT_OK);
}

static int
json_parse_number(struct kore_json *json, struct kore_json_item *number)
{
	u_int8_t	ch;
	int		ret;
	char		*str;
	u_int32_t	type;

	str = NULL;
	ret = KORE_RESULT_ERROR;
	kore_buf_reset(&json->tmpbuf);

	type = KORE_JSON_TYPE_NUMBER | KORE_JSON_TYPE_INTEGER |
	    KORE_JSON_TYPE_INTEGER_U64;

	for (;;) {
		if (!json_peek(json, &ch))
			break;

		switch (ch) {
		case 'e':
		case 'E':
		case '.':
			type = KORE_JSON_TYPE_NUMBER;
			kore_buf_append(&json->tmpbuf, &ch, sizeof(ch));
			json->offset++;
			continue;
		case '-':
			if (json->tmpbuf.offset != 0)
				goto cleanup;
			type &= ~KORE_JSON_TYPE_INTEGER_U64;
			/* FALLTHROUGH */
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '+':
			kore_buf_append(&json->tmpbuf, &ch, sizeof(ch));
			json->offset++;
			continue;
		}

		break;
	}

	if (type & KORE_JSON_TYPE_INTEGER_U64)
		type = KORE_JSON_TYPE_INTEGER_U64;

	if (type & KORE_JSON_TYPE_INTEGER)
		type = KORE_JSON_TYPE_INTEGER;

	str = kore_buf_stringify(&json->tmpbuf, NULL);

	switch (type) {
	case KORE_JSON_TYPE_NUMBER:
		number->data.number =
		    kore_strtodouble(str, -DBL_MAX, DBL_MAX, &ret);
		break;
	case KORE_JSON_TYPE_INTEGER:
		number->data.s64 = (int64_t)kore_strtonum64(str, 1, &ret);
		break;
	case KORE_JSON_TYPE_INTEGER_U64:
		number->data.s64 = kore_strtonum64(str, 0, &ret);
		break;
	default:
		goto cleanup;
	}

	number->type = type;

cleanup:
	if (ret == KORE_RESULT_ERROR && json->error == 0)
		json->error = KORE_JSON_ERR_INVALID_NUMBER;

	return (ret);
}

static int
json_parse_literal(struct kore_json *json, struct kore_json_item *literal)
{
	size_t		len, idx;
	int		ret, val;
	u_int8_t	ch, *tmpl;

	ret = KORE_RESULT_ERROR;

	if (!json_next(json, &ch))
		goto cleanup;

	switch (ch) {
	case 'f':
		val = KORE_JSON_FALSE;
		tmpl = json_false_literal;
		len = sizeof(json_false_literal) - 1;
		break;
	case 'n':
		val = KORE_JSON_NULL;
		tmpl = json_null_literal;
		len = sizeof(json_null_literal) - 1;
		break;
	case 't':
		val = KORE_JSON_TRUE;
		tmpl = json_true_literal;
		len = sizeof(json_true_literal) - 1;
		break;
	default:
		goto cleanup;
	}

	for (idx = 0; idx < len; idx++) {
		if (!json_next(json, &ch))
			goto cleanup;

		if (ch != tmpl[idx + 1])
			goto cleanup;
	}

	literal->data.literal = val;
	literal->type = KORE_JSON_TYPE_LITERAL;

	ret = KORE_RESULT_OK;

cleanup:
	if (ret == KORE_RESULT_ERROR && json->error == 0)
		json->error = KORE_JSON_ERR_INVALID_LITERAL;

	return (ret);
}

static char *
json_get_string(struct kore_json *json)
{
	u_int8_t	ch;
	char		*res;

	res = NULL;

	if (!json_next(json, &ch))
		goto cleanup;

	if (ch != '"')
		goto cleanup;

	kore_buf_reset(&json->tmpbuf);

	for (;;) {
		if (!json_next(json, &ch))
			goto cleanup;

		if (ch == '"')
			break;

		if (ch <= 0x1f)
			goto cleanup;

		if (ch == '\\') {
			if (!json_next(json, &ch))
				goto cleanup;

			switch (ch) {
			case '\"':
			case '\\':
			case '/':
				break;
			case 'b':
				ch = '\b';
				break;
			case 'f':
				ch = '\f';
				break;
			case 'n':
				ch = '\n';
				break;
			case 'r':
				ch = '\r';
				break;
			case 't':
				ch = '\t';
				break;
			case 'u':
			default:
				/* XXX - not supported. */
				goto cleanup;
			}
		}

		kore_buf_append(&json->tmpbuf, &ch, sizeof(ch));
	}

	res = kore_buf_stringify(&json->tmpbuf, NULL);

cleanup:
	if (res == NULL && json->error == 0)
		json->error = KORE_JSON_ERR_INVALID_STRING;

	return (res);
}
