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

#include "lib.h"
#include "array.h"
#include "test-common.h"
#include "mail-index-private.h"
#include "mail-transaction-log-view-private.h"

static struct mail_transaction_log *log;
static struct mail_transaction_log_view *view;
static bool clean_refcount0_files = FALSE;

static void
test_transaction_log_file_add(uint32_t file_seq)
{
	struct mail_transaction_log_file **p, *file;

	file = i_new(struct mail_transaction_log_file, 1);
	file->hdr.file_seq = file_seq;
	file->hdr.hdr_size = file->sync_offset = sizeof(file->hdr);
	file->hdr.prev_file_seq = file_seq - 1;
	file->hdr.prev_file_offset = (uint32_t)-1;
	file->log = log;
	file->fd = -1;
	file->buffer = buffer_create_dynamic(default_pool, 256);
	file->buffer_offset = file->hdr.hdr_size;

	/* files must be sorted by file_seq */
	for (p = &log->files; *p != NULL; p = &(*p)->next) {
		if ((*p)->hdr.file_seq > file->hdr.file_seq) {
			file->next = *p;
			break;
		}
	}
	*p = file;
	log->head = file;
}

void mail_index_set_error(struct mail_index *index ATTR_UNUSED,
			  const char *fmt ATTR_UNUSED, ...)
{
}

void mail_transaction_log_file_set_corrupted(struct mail_transaction_log_file *file ATTR_UNUSED,
					     const char *fmt ATTR_UNUSED, ...)
{
}

void mail_transaction_logs_clean(struct mail_transaction_log *log ATTR_UNUSED)
{
}

int mail_transaction_log_find_file(struct mail_transaction_log *log,
				   uint32_t file_seq, bool nfs_flush ATTR_UNUSED,
				   struct mail_transaction_log_file **file_r,
				   const char **reason_r)
{
	struct mail_transaction_log_file *file, *next;

	for (file = log->files; file != NULL; file = next) {
		next = file->next;
		if (file->hdr.file_seq == file_seq) {
			*file_r = file;
			return 1;
		}
		/* refcount=0 files at the beginning of the list may be freed */
		if (file->refcount == 0 && file == log->files &&
		    clean_refcount0_files)
			log->files = next;
	}
	if (clean_refcount0_files && file_seq == 4) {
		/* "clean refcount=0 files" test autocreates this file */
		test_transaction_log_file_add(4);
		*file_r = log->head;
		return 1;
	}
	*reason_r = "not found";
	return 0;
}

int mail_transaction_log_file_map(struct mail_transaction_log_file *file ATTR_UNUSED,
				  uoff_t start_offset ATTR_UNUSED, uoff_t end_offset ATTR_UNUSED,
				  const char **reason_r ATTR_UNUSED)
{
	return 1;
}

int mail_transaction_log_file_get_highest_modseq_at(
		struct mail_transaction_log_file *file ATTR_UNUSED,
		uoff_t offset ATTR_UNUSED, uint64_t *highest_modseq_r,
		const char **error_r ATTR_UNUSED)
{
	*highest_modseq_r = 0;
	return 1;
}

void mail_transaction_update_modseq(const struct mail_transaction_header *hdr ATTR_UNUSED,
				    const void *data ATTR_UNUSED,
				    uint64_t *cur_modseq,
				    unsigned int version ATTR_UNUSED)
{
	*cur_modseq += 1;
}

static bool view_is_file_refed(uint32_t file_seq)
{
	struct mail_transaction_log_file *const *files;
	unsigned int i, count;
	bool ret = FALSE;

	files = array_get(&view->file_refs, &count);
	for (i = 0; i < count; i++) {
		if (files[i]->hdr.file_seq == file_seq) {
			i_assert(!ret); /* could be a test too.. */
			ret = TRUE;
		}
	}
	return ret;
}

static size_t
add_append_record(struct mail_transaction_log_file *file,
		  const struct mail_index_record *rec)
{
	struct mail_transaction_header hdr;
	size_t size;

	i_zero(&hdr);
	hdr.type = MAIL_TRANSACTION_APPEND | MAIL_TRANSACTION_EXTERNAL;
	hdr.size = mail_index_uint32_to_offset(sizeof(hdr) + sizeof(*rec));

	buffer_append(file->buffer, &hdr, sizeof(hdr));
	buffer_append(file->buffer, rec, sizeof(*rec));

	size = sizeof(hdr) + sizeof(*rec);
	file->sync_offset += size;
	return size;
}

static void test_mail_transaction_log_view(void)
{
	const struct mail_transaction_header *hdr;
	const struct mail_index_record *rec;
	struct mail_index_record append_rec;
	const void *data;
	void *oldfile;
	uint32_t seq;
	uoff_t offset, last_log_size;
	const char *reason;
	bool reset;

	test_begin("init");
	log = i_new(struct mail_transaction_log, 1);
	log->index = i_new(struct mail_index, 1);
	log->index->log = log;
	log->index->log_sync_locked = TRUE;
	test_transaction_log_file_add(1);
	test_transaction_log_file_add(2);
	test_transaction_log_file_add(3);

	/* add an append record to the 3rd log file */
	i_zero(&append_rec);
	append_rec.uid = 1;

	last_log_size = sizeof(struct mail_transaction_log_header) +
		add_append_record(log->head, &append_rec);

	view = mail_transaction_log_view_open(log);
	i_assert(view != NULL);
	test_assert(log->views == view &&
		    !view_is_file_refed(1) && !view_is_file_refed(2) &&
		    view_is_file_refed(3));
	test_end();

	/* we have files 1-3 opened */
	test_begin("set all");
	test_assert(mail_transaction_log_view_set(view, 0, 0, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 1 &&
		    reset && view_is_file_refed(1) && view_is_file_refed(2) &&
		    view_is_file_refed(3) &&
		    !mail_transaction_log_view_is_corrupted(view));
	mail_transaction_log_view_get_prev_pos(view, &seq, &offset);
	test_assert(seq == 1 && offset == sizeof(struct mail_transaction_log_header));
	test_assert(mail_transaction_log_view_next(view, &hdr, &data) == 1);
	test_assert(hdr->type == (MAIL_TRANSACTION_APPEND | MAIL_TRANSACTION_EXTERNAL));
	rec = data;
	test_assert(memcmp(rec, &append_rec, sizeof(*rec)) == 0);
	test_assert(mail_transaction_log_view_next(view, &hdr, &data) == 0);
	test_assert(mail_transaction_log_view_is_last(view));
	mail_transaction_log_view_get_prev_pos(view, &seq, &offset);
	test_assert(seq == 3 && offset == last_log_size);
	test_end();

	test_begin("set first");
	test_assert(mail_transaction_log_view_set(view, 0, 0, 0, 0, &reset, &reason) == 1);
	mail_transaction_log_view_get_prev_pos(view, &seq, &offset);
	test_assert(seq == 1 && offset == sizeof(struct mail_transaction_log_header));
	test_assert(mail_transaction_log_view_next(view, &hdr, &data) == 0);
	mail_transaction_log_view_get_prev_pos(view, &seq, &offset);
	test_assert(seq == 1 && offset == sizeof(struct mail_transaction_log_header));
	test_end();

	test_begin("set end");
	test_assert(mail_transaction_log_view_set(view, 3, last_log_size, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 1);
	mail_transaction_log_view_get_prev_pos(view, &seq, &offset);
	test_assert(seq == 3 && offset == last_log_size);
	test_assert(mail_transaction_log_view_next(view, &hdr, &data) == 0);
	mail_transaction_log_view_get_prev_pos(view, &seq, &offset);
	test_assert(seq == 3 && offset == last_log_size);
	test_end();

	test_begin("log clear");
	mail_transaction_log_view_clear(view, 2);
	test_assert(!view_is_file_refed(1) && view_is_file_refed(2) &&
		    view_is_file_refed(3));
	oldfile = log->files;
	buffer_free(&log->files->buffer);
	log->files = log->files->next;
	i_free(oldfile);
	test_assert(log->files->hdr.file_seq == 2);
	test_end();

	/* --- first file has been removed --- */

	test_begin("set 2-3");
	test_assert(mail_transaction_log_view_set(view, 2, 0, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 1);
	test_end();

	test_begin("missing log handing");
	test_assert(mail_transaction_log_view_set(view, 0, 0, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 0);
	test_end();

	test_begin("closed log handling");
	view->log = NULL;
	test_assert(mail_transaction_log_view_set(view, 0, 0, (uint32_t)-1, UOFF_T_MAX, &reset, &reason) == 0);
	view->log = log;
	test_end();

	test_begin("clean refcount=0 files");
	oldfile = log->files;
	/* clear all references */
	mail_transaction_log_view_clear(view, 0);
	clean_refcount0_files = TRUE;
	/* create a new file during mail_transaction_log_view_set(), which
	   triggers freeing any unreferenced files. */
	test_assert(mail_transaction_log_view_set(view, 2, 0, 4, UOFF_T_MAX, &reset, &reason) == 1);
	clean_refcount0_files = FALSE;
	log->files = oldfile;
	test_end();

	mail_transaction_log_view_close(&view);
	i_free(log->index);
	while (log->files != NULL) {
		oldfile = log->files;
		buffer_free(&log->files->buffer);
		log->files = log->files->next;
		i_free(oldfile);
	}
	i_free(log);
}

int main(void)
{
	static void (*const test_functions[])(void) = {
		test_mail_transaction_log_view,
		NULL
	};
	return test_run(test_functions);
}
