/* Copyright (c) Dovecot authors, see top-level COPYING file */

#include "lib.h"
#include "array.h"
#include "str.h"
#include "mail-search.h"
#include "mail-namespace.h"
#include "mailbox-list.h"
#include "doveadm-mail.h"
#include "doveadm-mailbox-list-iter.h"

struct doveadm_mailbox_list_iter {
	struct mail_user *user;
	struct doveadm_mail_cmd_context *ctx;
	struct mail_search_args *search_args;
	enum mailbox_list_iter_flags iter_flags;

	struct mailbox_list_iterate_context *iter;

	struct mailbox_info info;
	ARRAY_TYPE(const_string) patterns;
	unsigned int pattern_idx;

	bool only_selectable;
};

static bool
search_args_get_mailbox_patterns(const struct mail_search_arg *args,
				 ARRAY_TYPE(const_string) *patterns,
				 bool *have_guid, bool *have_wildcards)
{
	const struct mail_search_arg *subargs;

	for (; args != NULL; args = args->next) {
		switch (args->type) {
		case SEARCH_OR:
			/* we don't currently try to optimize OR. */
			break;
		case SEARCH_SUB:
		case SEARCH_INTHREAD:
			subargs = args->value.subargs;
			for (; subargs != NULL; subargs = subargs->next) {
				if (!search_args_get_mailbox_patterns(subargs,
							patterns, have_guid,
							have_wildcards))
					return FALSE;
			}
			break;
		case SEARCH_MAILBOX_GLOB:
			*have_wildcards = TRUE;
			/* fall through */
		case SEARCH_MAILBOX:
			if (args->match_not) {
				array_clear(patterns);
				return FALSE;
			}
			array_push_back(patterns, &args->value.str);
			break;
		case SEARCH_MAILBOX_GUID:
			*have_guid = TRUE;
			break;
		default:
			break;
		}
	}
	return TRUE;
}

static struct doveadm_mailbox_list_iter *
doveadm_mailbox_list_iter_init_nsmask(struct doveadm_mail_cmd_context *ctx,
				      struct mail_user *user,
				      struct mail_search_args *search_args,
				      enum mailbox_list_iter_flags iter_flags,
				      enum mail_namespace_type ns_mask)
{
	static const char *all_pattern = "*";
	struct doveadm_mailbox_list_iter *iter;
	bool have_guid = FALSE, have_wildcards = FALSE;

	iter = i_new(struct doveadm_mailbox_list_iter, 1);
	iter->ctx = ctx;
	iter->search_args = search_args;
	iter->user = user;
	i_array_init(&iter->patterns, 16);
	(void)search_args_get_mailbox_patterns(search_args->args,
					       &iter->patterns,
					       &have_guid, &have_wildcards);

	if (array_count(&iter->patterns) == 0) {
		iter_flags |= MAILBOX_LIST_ITER_SKIP_ALIASES;
		if (have_guid) {
			ns_mask |= MAIL_NAMESPACE_TYPE_SHARED |
				MAIL_NAMESPACE_TYPE_PUBLIC;
		}
		array_push_back(&iter->patterns, &all_pattern);
	} else if (have_wildcards) {
		iter_flags |= MAILBOX_LIST_ITER_STAR_WITHIN_NS;
		ns_mask |= MAIL_NAMESPACE_TYPE_SHARED |
			MAIL_NAMESPACE_TYPE_PUBLIC;
	} else {
		/* just return the listed mailboxes without actually
		   iterating through. this also allows accessing mailboxes
		   without lookup ACL right */
		return iter;
	}
	array_append_zero(&iter->patterns);

	iter->only_selectable = TRUE;
	iter->iter_flags = iter_flags;
	iter->iter = mailbox_list_iter_init_namespaces(user->namespaces,
						       array_front(&iter->patterns),
						       ns_mask, iter_flags);
	return iter;
}

struct doveadm_mailbox_list_iter *
doveadm_mailbox_list_iter_init(struct doveadm_mail_cmd_context *ctx,
			       struct mail_user *user,
			       struct mail_search_args *search_args,
			       enum mailbox_list_iter_flags iter_flags)
{
	enum mail_namespace_type ns_mask = MAIL_NAMESPACE_TYPE_PRIVATE;

	return doveadm_mailbox_list_iter_init_nsmask(ctx, user, search_args,
						     iter_flags, ns_mask);
}

struct doveadm_mailbox_list_iter *
doveadm_mailbox_list_iter_full_init(struct doveadm_mail_cmd_context *ctx,
				    struct mail_user *user,
				    struct mail_search_args *search_args,
				    enum mailbox_list_iter_flags iter_flags)
{
	enum mail_namespace_type ns_mask = MAIL_NAMESPACE_TYPE_MASK_ALL;
	struct doveadm_mailbox_list_iter *iter;

	iter = doveadm_mailbox_list_iter_init_nsmask(ctx, user, search_args,
						     iter_flags, ns_mask);
	iter->only_selectable = FALSE;
	return iter;
}

int doveadm_mailbox_list_iter_deinit(struct doveadm_mailbox_list_iter **_iter)
{
	struct doveadm_mailbox_list_iter *iter = *_iter;
	enum mail_error error;
	int ret;

	*_iter = NULL;

	if (iter->iter == NULL)
		ret = 0;
	else if ((ret = mailbox_list_iter_deinit(&iter->iter)) < 0) {
		const char *errtxt = mailbox_list_get_last_internal_error(
			iter->user->namespaces->list, &error);
		e_error(iter->ctx->cctx->event,
			"Listing mailboxes failed: %s", errtxt);
		doveadm_mail_failed_error(iter->ctx, error);
	}
	array_free(&iter->patterns);
	i_free(iter);
	return ret;
}

const struct mailbox_info *
doveadm_mailbox_list_iter_next(struct doveadm_mailbox_list_iter *iter)
{
	const struct mailbox_info *info;
	const char *const *patterns;
	unsigned int count;

	if (iter->iter == NULL) {
		patterns = array_get(&iter->patterns, &count);
		if (iter->pattern_idx == count)
			return NULL;

		iter->info.vname = patterns[iter->pattern_idx++];
		iter->info.ns = mail_namespace_find(iter->user->namespaces,
						    iter->info.vname);
		return &iter->info;
	}

	while ((info = mailbox_list_iter_next(iter->iter)) != NULL) {
		char sep = mail_namespace_get_sep(info->ns);

		if ((info->flags & (MAILBOX_NOSELECT |
				    MAILBOX_NONEXISTENT)) != 0) {
			if (iter->only_selectable)
				continue;
		}

		if (mail_search_args_match_mailbox(iter->search_args,
						   info->vname, sep))
			break;
	}
	return info;
}
