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

#include "lib.h"
#include "array.h"
#include "mail-storage-private.h"
#include "mail-search.h"
#include "mailbox-search-result-private.h"

static void
mailbox_search_result_analyze_args(struct mail_search_result *result,
				   struct mail_search_arg *arg)
{
	for (; arg != NULL; arg = arg->next) {
		switch (arg->type) {
		case SEARCH_OR:
		case SEARCH_SUB:
			mailbox_search_result_analyze_args(result,
							   arg->value.subargs);
			break;
		case SEARCH_FLAGS:
			result->args_have_flags = TRUE;
			break;
		case SEARCH_KEYWORDS:
			result->args_have_keywords = TRUE;
			break;
		case SEARCH_MODSEQ:
			result->args_have_modseq = TRUE;
			break;
		default:
			break;
		}
	}
}

struct mail_search_result *
mailbox_search_result_alloc(struct mailbox *box, struct mail_search_args *args,
			    enum mailbox_search_result_flags flags)
{
	struct mail_search_result *result;

	result = i_new(struct mail_search_result, 1);
	result->box = box;
	result->flags = flags;
	i_array_init(&result->uids, 32);
	i_array_init(&result->never_uids, 128);

	if ((result->flags & MAILBOX_SEARCH_RESULT_FLAG_UPDATE) != 0) {
		result->search_args = args;
		mail_search_args_ref(result->search_args);
		mailbox_search_result_analyze_args(result, args->args);
	}

	array_push_back(&result->box->search_results, &result);
	return result;
}

void mailbox_search_result_free(struct mail_search_result **_result)
{
	struct mail_search_result *result = *_result;
	unsigned int i;

	*_result = NULL;

	if (!array_lsearch_ptr_idx(&result->box->search_results, result, &i))
		i_unreached();
	array_delete(&result->box->search_results, i, 1);

	if (result->search_args != NULL)
		mail_search_args_unref(&result->search_args);

	array_free(&result->uids);
	array_free(&result->never_uids);
	if (array_is_created(&result->removed_uids)) {
		array_free(&result->removed_uids);
		array_free(&result->added_uids);
	}
	i_free(result);
}

struct mail_search_result *
mailbox_search_result_save(struct mail_search_context *ctx,
			   enum mailbox_search_result_flags flags)
{
	struct mail_search_result *result;

	result = mailbox_search_result_alloc(ctx->transaction->box,
					     ctx->args, flags);
	array_push_back(&ctx->results, &result);
	return result;
}

void mailbox_search_result_initial_done(struct mail_search_result *result)
{
	if ((result->flags & MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC) != 0) {
		i_array_init(&result->removed_uids, 32);
		i_array_init(&result->added_uids, 32);
	}
	mail_search_args_seq2uid(result->search_args);
}

void mailbox_search_results_initial_done(struct mail_search_context *ctx)
{
	struct mail_search_result *const *results;
	unsigned int i, count;

	results = array_get(&ctx->results, &count);
	for (i = 0; i < count; i++)
		mailbox_search_result_initial_done(results[i]);
}

void mailbox_search_result_add(struct mail_search_result *result, uint32_t uid)
{
	i_assert(uid > 0);

	if (seq_range_exists(&result->uids, uid))
		return;

	seq_range_array_add(&result->uids, uid);
	if (array_is_created(&result->added_uids)) {
		seq_range_array_add(&result->added_uids, uid);
		seq_range_array_remove(&result->removed_uids, uid);
	}
}

void mailbox_search_result_remove(struct mail_search_result *result,
				  uint32_t uid)
{
	if (seq_range_array_remove(&result->uids, uid)) {
		if (array_is_created(&result->removed_uids)) {
			seq_range_array_add(&result->removed_uids, uid);
			seq_range_array_remove(&result->added_uids, uid);
		}
	}
}

void mailbox_search_results_add(struct mail_search_context *ctx, uint32_t uid)
{
	struct mail_search_result *const *results;
	unsigned int i, count;

	results = array_get(&ctx->results, &count);
	for (i = 0; i < count; i++)
		mailbox_search_result_add(results[i], uid);
}

void mailbox_search_results_remove(struct mailbox *box, uint32_t uid)
{
	struct mail_search_result *const *results;
	unsigned int i, count;

	results = array_get(&box->search_results, &count);
	for (i = 0; i < count; i++)
		mailbox_search_result_remove(results[i], uid);
}

void mailbox_search_result_never(struct mail_search_result *result,
				 uint32_t uid)
{
	seq_range_array_add(&result->never_uids, uid);
}

void mailbox_search_results_never(struct mail_search_context *ctx,
				  uint32_t uid)
{
	struct mail_search_result *const *results;
	unsigned int i, count;

	if (ctx->update_result != NULL)
		mailbox_search_result_never(ctx->update_result, uid);

	results = array_get(&ctx->results, &count);
	for (i = 0; i < count; i++)
		mailbox_search_result_never(results[i], uid);
}

const ARRAY_TYPE(seq_range) *
mailbox_search_result_get(struct mail_search_result *result)
{
	return &result->uids;
}

void mailbox_search_result_sync(struct mail_search_result *result,
				ARRAY_TYPE(seq_range) *removed_uids,
				ARRAY_TYPE(seq_range) *added_uids)
{
	array_clear(removed_uids);
	array_clear(added_uids);

	array_append_array(removed_uids, &result->removed_uids);
	array_append_array(added_uids, &result->added_uids);

	array_clear(&result->removed_uids);
	array_clear(&result->added_uids);
}
