/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "json-tree.h"

struct json_tree {
	pool_t pool;
	struct json_tree_node *root, *cur, *cur_child;
};

struct json_tree *json_tree_init(void)
{
	struct json_tree *tree;
	pool_t pool;

	pool = pool_alloconly_create("json tree", 1024);
	tree = p_new(pool, struct json_tree, 1);
	tree->pool = pool;
	tree->root = tree->cur = p_new(tree->pool, struct json_tree_node, 1);
	tree->cur->value_type = JSON_TYPE_OBJECT;
	return tree;
}

void json_tree_deinit(struct json_tree **_tree)
{
	struct json_tree *tree = *_tree;

	*_tree = NULL;
	pool_unref(&tree->pool);
}

static void
json_tree_append_child(struct json_tree *tree, enum json_type type,
		       const char *value)
{
	struct json_tree_node *node;

	node = p_new(tree->pool, struct json_tree_node, 1);
	node->parent = tree->cur;
	node->value_type = type;
	node->value.str = p_strdup(tree->pool, value);

	if (tree->cur_child == NULL)
		tree->cur->value.child = node;
	else
		tree->cur_child->next = node;
	tree->cur_child = node;
}

static void
json_tree_set_cur(struct json_tree *tree, struct json_tree_node *node)
{
	tree->cur = node;
	tree->cur_child = tree->cur->value.child;
	if (tree->cur_child != NULL) {
		while (tree->cur_child->next != NULL)
			tree->cur_child = tree->cur_child->next;
	}
}

static int
json_tree_append_value(struct json_tree *tree, enum json_type type,
		       const char *value)
{
	switch (tree->cur->value_type) {
	case JSON_TYPE_OBJECT_KEY:
		/* "key": value - we already added the node and set its key,
		   so now just set the value */
		tree->cur->value_type = type;
		tree->cur->value.str = p_strdup(tree->pool, value);
		json_tree_set_cur(tree, tree->cur->parent);
		break;
	case JSON_TYPE_ARRAY:
		/* element in array - add a new node */
		json_tree_append_child(tree, type, value);
		break;
	default:
		return -1;
	}
	return 0;
}

int json_tree_append(struct json_tree *tree, enum json_type type,
		     const char *value)
{
	switch (type) {
	case JSON_TYPE_OBJECT_KEY:
		if (tree->cur->value_type != JSON_TYPE_OBJECT)
			return -1;
		json_tree_append_child(tree, type, NULL);
		json_tree_set_cur(tree, tree->cur_child);
		tree->cur->key = p_strdup(tree->pool, value);
		break;
	case JSON_TYPE_ARRAY:
		if (json_tree_append_value(tree, type, NULL) < 0)
			return -1;
		json_tree_set_cur(tree, tree->cur_child);
		break;
	case JSON_TYPE_OBJECT:
		if (tree->cur->value_type == JSON_TYPE_OBJECT_KEY)
			tree->cur->value_type = JSON_TYPE_OBJECT;
		else if (tree->cur->value_type == JSON_TYPE_ARRAY) {
			json_tree_append_child(tree, type, NULL);
			json_tree_set_cur(tree, tree->cur_child);
		} else {
			return -1;
		}
		break;
	case JSON_TYPE_OBJECT_END:
		if (tree->cur->parent == NULL ||
		    tree->cur->value_type != JSON_TYPE_OBJECT)
			return -1;
		json_tree_set_cur(tree, tree->cur->parent);
		break;
	case JSON_TYPE_ARRAY_END:
		if (tree->cur->parent == NULL ||
		    tree->cur->value_type != JSON_TYPE_ARRAY)
			return -1;
		json_tree_set_cur(tree, tree->cur->parent);
		break;
	case JSON_TYPE_STRING:
	case JSON_TYPE_NUMBER:
	case JSON_TYPE_TRUE:
	case JSON_TYPE_FALSE:
	case JSON_TYPE_NULL:
		if (json_tree_append_value(tree, type, value) < 0)
			return -1;
		break;
	}
	return 0;
}

struct json_tree_node *json_tree_root(struct json_tree *tree)
{
	return tree->root;
}

struct json_tree_node *
json_tree_find_key(struct json_tree_node *node, const char *key)
{
	for (; node != NULL; node = node->next) {
		if (node->key != NULL && strcmp(node->key, key) == 0)
			return node;
	}
	return NULL;
}

struct json_tree_node *
json_tree_find_child_with(struct json_tree_node *node,
			  const char *key, const char *value)
{
	struct json_tree_node *child;

	i_assert(node->value_type == JSON_TYPE_OBJECT ||
		 node->value_type == JSON_TYPE_ARRAY);

	for (node = node->value.child; node != NULL; node = node->next) {
		if (node->value_type != JSON_TYPE_OBJECT)
			continue;

		child = json_tree_find_key(node->value.child, key);
		if (child != NULL && child->value.str != NULL &&
		    strcmp(child->value.str, value) == 0)
			return node;
	}
	return NULL;
}
