/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "str.h"
#include "mailbox-tree.h"

struct mailbox_tree_context {
	pool_t pool;
	char separator;
	bool parents_nonexistent;
	bool sorted;
	unsigned int node_size;

	struct mailbox_node *nodes;
};

struct mailbox_tree_iterate_context {
	struct mailbox_node *root, *next_node;
	unsigned int flags_mask;

	char separator;

	ARRAY(struct mailbox_node *) node_path;
	string_t *path_str;
	size_t parent_pos;

	bool first_child:1;
};

struct mailbox_tree_context *mailbox_tree_init(char separator)
{
	return mailbox_tree_init_size(separator, sizeof(struct mailbox_node));
}

struct mailbox_tree_context *
mailbox_tree_init_size(char separator, unsigned int mailbox_node_size)
{
	struct mailbox_tree_context *tree;

	i_assert(mailbox_node_size >= sizeof(struct mailbox_node));

	tree = i_new(struct mailbox_tree_context, 1);
	tree->pool = pool_alloconly_create(MEMPOOL_GROWING"mailbox_tree", 10240);
	tree->separator = separator;
	tree->node_size = mailbox_node_size;
	return tree;
}

void mailbox_tree_deinit(struct mailbox_tree_context **_tree)
{
	struct mailbox_tree_context *tree = *_tree;

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

void mailbox_tree_set_separator(struct mailbox_tree_context *tree,
				char separator)
{
	tree->separator = separator;
}

void mailbox_tree_set_parents_nonexistent(struct mailbox_tree_context *tree)
{
	tree->parents_nonexistent = TRUE;
}

void mailbox_tree_clear(struct mailbox_tree_context *tree)
{
	p_clear(tree->pool);
	tree->nodes = NULL;
}

pool_t mailbox_tree_get_pool(struct mailbox_tree_context *tree)
{
	return tree->pool;
}

struct mailbox_node *mailbox_tree_get_root(struct mailbox_tree_context *tree)
{
	return tree->nodes;
}

static struct mailbox_node * ATTR_NULL(2)
mailbox_tree_traverse(struct mailbox_tree_context *tree, const char *path,
		      bool create, bool *created_r)
{
	struct mailbox_node **node, *parent;
	const char *name, *suffix;
	string_t *str;

	*created_r = FALSE;

	if (path == NULL)
		return tree->nodes;

	if (str_begins_icase(path, "INBOX", &suffix) &&
	    (suffix[0] == '\0' || suffix[0] == tree->separator))
		path = t_strdup_printf("INBOX%s", suffix);

	parent = NULL;
	node = &tree->nodes;

	str = t_str_new(strlen(path)+1);
	for (name = path;; path++) {
		if (*path != tree->separator && *path != '\0')
			continue;

		str_truncate(str, 0);
		str_append_data(str, name, (size_t) (path - name));
		name = str_c(str);

		/* find the node */
		while (*node != NULL) {
			if (strcmp((*node)->name, name) == 0)
				break;

			node = &(*node)->next;
		}

		if (*node == NULL) {
			/* not found, create it */
			if (!create)
				break;

			*node = p_malloc(tree->pool, tree->node_size);
			(*node)->parent = parent;
			(*node)->name = p_strdup(tree->pool, name);
			if (tree->parents_nonexistent)
				(*node)->flags = MAILBOX_NONEXISTENT;
			tree->sorted = FALSE;
			*created_r = TRUE;
		}

		if (*path == '\0')
			break;

		name = path+1;

		parent = *node;
		node = &(*node)->children;
	}

	return *node;
}

struct mailbox_node *
mailbox_tree_get(struct mailbox_tree_context *tree, const char *path,
		 bool *created_r)
{
	struct mailbox_node *node;
	bool created;

	T_BEGIN {
		node = mailbox_tree_traverse(tree, path, TRUE, &created);
	} T_END;
	if (created && tree->parents_nonexistent)
		node->flags = 0;

	*created_r = created;
	return node;
}

struct mailbox_node *
mailbox_tree_lookup(struct mailbox_tree_context *tree, const char *path)
{
	struct mailbox_node *node;
	bool created;

	i_assert(tree != NULL);

	T_BEGIN {
		node = mailbox_tree_traverse(tree, path, FALSE, &created);
	} T_END;
	return node;
}

struct mailbox_tree_iterate_context *
mailbox_tree_iterate_init(struct mailbox_tree_context *tree,
			  struct mailbox_node *root, unsigned int flags_mask)
{
	struct mailbox_tree_iterate_context *ctx;

	ctx = i_new(struct mailbox_tree_iterate_context, 1);
	ctx->separator = tree->separator;
	ctx->root = root != NULL ? root : tree->nodes;
	ctx->flags_mask = flags_mask;
	ctx->path_str = str_new(default_pool, 256);
	i_array_init(&ctx->node_path, 16);

	ctx->next_node = ctx->root;
	return ctx;
}

static void
mailbox_tree_iterate_set_next_node(struct mailbox_tree_iterate_context *ctx)
{
	struct mailbox_node *node = ctx->next_node;
	struct mailbox_node *const *nodes;
	unsigned int i, count;

	if (node->children != NULL) {
		array_push_back(&ctx->node_path, &node);
		ctx->parent_pos = str_len(ctx->path_str);
		node = node->children;
		ctx->first_child = TRUE;
	} else if (node->next != NULL) {
		node = node->next;
	} else {
		nodes = array_get(&ctx->node_path, &count);
		node = NULL;
		for (i = count; i != 0; i--) {
			size_t len = strlen(nodes[i-1]->name) + 1;

			i_assert(len <= ctx->parent_pos);
			ctx->parent_pos -= len;
			if (nodes[i-1]->next != NULL) {
				node = nodes[i-1]->next;
				ctx->first_child = TRUE;
				i--;

				if (ctx->parent_pos != 0)
					ctx->parent_pos--;
				break;
			}
		}
		array_delete(&ctx->node_path, i, count - i);
	}

	ctx->next_node = node;
}

struct mailbox_node *
mailbox_tree_iterate_next(struct mailbox_tree_iterate_context *ctx,
			  const char **path_r)
{
	struct mailbox_node *node;

	do {
		node = ctx->next_node;
		if (node == NULL)
			return NULL;

		str_truncate(ctx->path_str, ctx->parent_pos);
		if (ctx->first_child) {
			ctx->first_child = FALSE;
			if (node->parent != NULL) {
				str_append_c(ctx->path_str, ctx->separator);
				ctx->parent_pos++;
			}
		}
		str_append(ctx->path_str, node->name);

		mailbox_tree_iterate_set_next_node(ctx);
	} while ((node->flags & ctx->flags_mask) != ctx->flags_mask);

	*path_r = str_c(ctx->path_str);
	return node;
}

void mailbox_tree_iterate_deinit(struct mailbox_tree_iterate_context **_ctx)
{
	struct mailbox_tree_iterate_context *ctx = *_ctx;

	*_ctx = NULL;
	if (ctx == NULL)
		return;
	str_free(&ctx->path_str);
	array_free(&ctx->node_path);
	i_free(ctx);
}

static struct mailbox_node * ATTR_NULL(1, 2)
mailbox_tree_dup_branch(struct mailbox_tree_context *dest_tree,
			struct mailbox_node *dest_parent,
			const struct mailbox_node *src)
{
	struct mailbox_node *node, *dest_nodes = NULL, **dest = &dest_nodes;

	for (; src != NULL; src = src->next) {
		*dest = node = p_malloc(dest_tree->pool, dest_tree->node_size);
		node->name = p_strdup(dest_tree->pool, src->name);
		node->flags = src->flags;

		node->parent = dest_parent;
		node->children = mailbox_tree_dup_branch(dest_tree, node,
							 src->children);
		dest = &node->next;
	}
	return dest_nodes;
}

struct mailbox_tree_context *mailbox_tree_dup(struct mailbox_tree_context *src)
{
	struct mailbox_tree_context *dest;

	/* for now we don't need to support extra data */
	i_assert(src->node_size == sizeof(struct mailbox_node));

	dest = mailbox_tree_init_size(src->separator, src->node_size);
	dest->nodes = mailbox_tree_dup_branch(dest, NULL, src->nodes);
	return dest;
}

static int mailbox_node_name_cmp(struct mailbox_node *const *node1,
				 struct mailbox_node *const *node2)
{
	return strcmp((*node1)->name, (*node2)->name);
}

static void mailbox_tree_sort_branch(struct mailbox_node **nodes,
				     ARRAY_TYPE(mailbox_node) *tmparr)
{
	struct mailbox_node *node, **dest;

	if (*nodes == NULL)
		return;

	/* first put the nodes into an array and sort it */
	array_clear(tmparr);
	for (node = *nodes; node != NULL; node = node->next)
		array_push_back(tmparr, &node);
	array_sort(tmparr, mailbox_node_name_cmp);

	/* update the node pointers */
	dest = nodes;
	array_foreach_elem(tmparr, node) {
		*dest = node;
		dest = &(*dest)->next;
	}
	*dest = NULL;

	/* sort the children */
	for (node = *nodes; node != NULL; node = node->next)
		mailbox_tree_sort_branch(&node->children, tmparr);
}

void mailbox_tree_sort(struct mailbox_tree_context *tree)
{
	if (tree->sorted)
		return;
	tree->sorted = TRUE;

	T_BEGIN {
		ARRAY_TYPE(mailbox_node) tmparr;

		t_array_init(&tmparr, 32);
		mailbox_tree_sort_branch(&tree->nodes, &tmparr);
	} T_END;
}
