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

#include "lib.h"
#include "ostream.h"
#include "mail-storage.h"
#include "mail-namespace.h"
#include "mail-search.h"
#include "doveadm-print.h"
#include "doveadm-mail.h"
#include "doveadm-mail-iter.h"

struct doveadm_mail_iter {
	struct doveadm_mail_cmd_context *ctx;
	struct mail_search_args *search_args;
	enum doveadm_mail_iter_flags flags;

	struct mailbox *box;
	struct mailbox_transaction_context *t;
	struct mail_search_context *search_ctx;
	bool killed;
};

int doveadm_mail_iter_init(struct doveadm_mail_cmd_context *ctx,
			   const struct mailbox_info *info,
			   struct mail_search_args *search_args,
			   enum mail_fetch_field wanted_fields,
			   const char *const *wanted_headers,
			   enum doveadm_mail_iter_flags flags,
			   struct doveadm_mail_iter **iter_r)
{
	struct doveadm_mail_iter *iter;
	struct mailbox_header_lookup_ctx *headers_ctx;
	const char *errstr;
	enum mail_error error;

	enum mailbox_flags readonly_flag =
		(flags & DOVEADM_MAIL_ITER_FLAG_READONLY) != 0 ?
		MAILBOX_FLAG_READONLY : 0;

	iter = i_new(struct doveadm_mail_iter, 1);
	iter->ctx = ctx;
	iter->flags = flags;
	iter->box = mailbox_alloc(info->ns->list, info->vname,
				  MAILBOX_FLAG_IGNORE_ACLS | readonly_flag);
	iter->search_args = search_args;

	if (mailbox_sync(iter->box, MAILBOX_SYNC_FLAG_FULL_READ) < 0) {
		*iter_r = NULL;
		errstr = mailbox_get_last_internal_error(iter->box, &error);
		if (error != MAIL_ERROR_NOTFOUND) {
			e_error(ctx->cctx->event, "Syncing mailbox %s failed: %s",
				info->vname, errstr);
			doveadm_mail_failed_mailbox(ctx, iter->box);
		}
		mailbox_free(&iter->box);
		i_free(iter);
		return error == MAIL_ERROR_NOTFOUND ? 0 : -1;
	}

	headers_ctx = wanted_headers == NULL || wanted_headers[0] == NULL ?
		NULL : mailbox_header_lookup_init(iter->box, wanted_headers);

	mail_search_args_init(search_args, iter->box, FALSE, NULL);
	iter->t = mailbox_transaction_begin(iter->box, ctx->transaction_flags,
					    ctx->cmd->name);
	iter->search_ctx = mailbox_search_init(iter->t, search_args, NULL,
					       wanted_fields, headers_ctx);
	mailbox_header_lookup_unref(&headers_ctx);
	*iter_r = iter;
	return 1;
}

static int
doveadm_mail_iter_deinit_transaction(struct doveadm_mail_iter *iter,
				     bool commit)
{
	int ret = 0;

	if (iter->search_ctx != NULL) {
		if (mailbox_search_deinit(&iter->search_ctx) < 0) {
			e_error(iter->ctx->cctx->event,
				"Searching mailbox %s failed: %s",
				mailbox_get_vname(iter->box),
				mailbox_get_last_internal_error(iter->box, NULL));
			ret = -1;
		}
	}
	if (iter->t == NULL)
		;
	else if (commit) {
		if (mailbox_transaction_commit(&iter->t) < 0) {
			e_error(iter->ctx->cctx->event,
				"Committing mailbox %s failed: %s",
				mailbox_get_vname(iter->box),
				mailbox_get_last_internal_error(iter->box, NULL));
			ret = -1;
		}
	} else {
		mailbox_transaction_rollback(&iter->t);
	}
	mail_search_args_deinit(iter->search_args);
	return ret;
}

static int
doveadm_mail_iter_deinit_full(struct doveadm_mail_iter **_iter,
			      bool sync, bool commit, bool keep_box)
{
	struct doveadm_mail_iter *iter = *_iter;
	int ret;

	*_iter = NULL;

	ret = doveadm_mail_iter_deinit_transaction(iter, commit);
	if (ret == 0 && sync) {
		ret = mailbox_sync(iter->box, 0);
		if (ret < 0) {
			e_error(iter->ctx->cctx->event,
				"Mailbox %s: Mailbox sync failed: %s",
				mailbox_get_vname(iter->box),
				mailbox_get_last_internal_error(iter->box, NULL));
		}
	}
	if (ret < 0)
		doveadm_mail_failed_mailbox(iter->ctx, iter->box);
	else if (iter->killed) {
		iter->ctx->exit_code = EX_TEMPFAIL;
		ret = -1;
	}
	if (!keep_box)
		mailbox_free(&iter->box);
	i_free(iter);
	return ret;
}

int doveadm_mail_iter_deinit(struct doveadm_mail_iter **_iter)
{
	return doveadm_mail_iter_deinit_full(_iter, FALSE, TRUE, FALSE);
}

int doveadm_mail_iter_deinit_sync(struct doveadm_mail_iter **_iter)
{
	return doveadm_mail_iter_deinit_full(_iter, TRUE, TRUE, FALSE);
}

int doveadm_mail_iter_deinit_keep_box(struct doveadm_mail_iter **iter,
				      struct mailbox **box_r)
{
	*box_r = (*iter)->box;
	return doveadm_mail_iter_deinit_full(iter, FALSE, TRUE, TRUE);
}

void doveadm_mail_iter_deinit_rollback(struct doveadm_mail_iter **_iter)
{
	(void)doveadm_mail_iter_deinit_full(_iter, FALSE, FALSE, FALSE);
}

bool doveadm_mail_iter_next(struct doveadm_mail_iter *iter,
			    struct mail **mail_r)
{
	if (iter->search_ctx == NULL)
		return FALSE;
	if (doveadm_is_killed()) {
		iter->killed = TRUE;
		return FALSE;
	}
	if ((iter->flags & DOVEADM_MAIL_ITER_FLAG_STOP_WITH_CLIENT) != 0 &&
	    doveadm_print_ostream->stream_errno != 0) {
		iter->killed = TRUE;
		return FALSE;
	}
	return mailbox_search_next(iter->search_ctx, mail_r);
}

struct mailbox *doveadm_mail_iter_get_mailbox(struct doveadm_mail_iter *iter)
{
	return iter->box;
}
