#include "ntdb-source.h"
#include "tap-interface.h"
#include "logging.h"
#include "layout.h"

static ntdb_len_t free_record_length(struct ntdb_context *ntdb, ntdb_off_t off)
{
	struct ntdb_free_record f;
	enum NTDB_ERROR ecode;

	ecode = ntdb_read_convert(ntdb, off, &f, sizeof(f));
	if (ecode != NTDB_SUCCESS)
		return ecode;
	if (frec_magic(&f) != NTDB_FREE_MAGIC)
		return NTDB_ERR_CORRUPT;
	return frec_len(&f);
}

int main(int argc, char *argv[])
{
	ntdb_off_t b_off, test;
	struct ntdb_context *ntdb;
	struct ntdb_layout *layout;
	NTDB_DATA data, key;
	ntdb_len_t len;

	/* FIXME: Test NTDB_CONVERT */
	/* FIXME: Test lock order fail. */

	plan_tests(42);
	data = ntdb_mkdata("world", 5);
	key = ntdb_mkdata("hello", 5);

	/* No coalescing can be done due to EOF */
	layout = new_ntdb_layout();
	ntdb_layout_add_freetable(layout);
	len = 15560;
	ntdb_layout_add_free(layout, len, 0);
	ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
	/* NOMMAP is for lockcheck. */
	ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP|MAYBE_NOSYNC,
			 O_RDWR, 0, &tap_log_attr);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);
	ok1(free_record_length(ntdb, layout->elem[1].base.off) == len);

	/* Figure out which bucket free entry is. */
	b_off = bucket_off(ntdb->ftable_off, size_to_bucket(len));
	/* Lock and fail to coalesce. */
	ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
	test = layout->elem[1].base.off;
	ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, len, &test)
	    == 0);
	ntdb_unlock_free_bucket(ntdb, b_off);
	ok1(free_record_length(ntdb, layout->elem[1].base.off) == len);
	ok1(test == layout->elem[1].base.off);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);
	ntdb_close(ntdb);
	ntdb_layout_free(layout);

	/* No coalescing can be done due to used record */
	layout = new_ntdb_layout();
	ntdb_layout_add_freetable(layout);
	ntdb_layout_add_free(layout, 15528, 0);
	ntdb_layout_add_used(layout, key, data, 6);
	ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
	/* NOMMAP is for lockcheck. */
	ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP|MAYBE_NOSYNC,
			 O_RDWR, 0, &tap_log_attr);
	ok1(free_record_length(ntdb, layout->elem[1].base.off) == 15528);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);

	/* Figure out which bucket free entry is. */
	b_off = bucket_off(ntdb->ftable_off, size_to_bucket(15528));
	/* Lock and fail to coalesce. */
	ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
	test = layout->elem[1].base.off;
	ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, 15528, &test)
	    == 0);
	ntdb_unlock_free_bucket(ntdb, b_off);
	ok1(free_record_length(ntdb, layout->elem[1].base.off) == 15528);
	ok1(test == layout->elem[1].base.off);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);
	ntdb_close(ntdb);
	ntdb_layout_free(layout);

	/* Coalescing can be done due to two free records, then EOF */
	layout = new_ntdb_layout();
	ntdb_layout_add_freetable(layout);
	ntdb_layout_add_free(layout, 1024, 0);
	ntdb_layout_add_free(layout, 14520, 0);
	ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
	/* NOMMAP is for lockcheck. */
	ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP|MAYBE_NOSYNC,
			 O_RDWR, 0, &tap_log_attr);
	ok1(free_record_length(ntdb, layout->elem[1].base.off) == 1024);
	ok1(free_record_length(ntdb, layout->elem[2].base.off) == 14520);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);

	/* Figure out which bucket (first) free entry is. */
	b_off = bucket_off(ntdb->ftable_off, size_to_bucket(1024));
	/* Lock and coalesce. */
	ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
	test = layout->elem[2].base.off;
	ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, 1024, &test)
	    == 1024 + sizeof(struct ntdb_used_record) + 14520);
	/* Should tell us it's erased this one... */
	ok1(test == NTDB_ERR_NOEXIST);
	ok1(ntdb->file->allrecord_lock.count == 0 && ntdb->file->num_lockrecs == 0);
	ok1(free_record_length(ntdb, layout->elem[1].base.off)
	    == 1024 + sizeof(struct ntdb_used_record) + 14520);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);
	ntdb_close(ntdb);
	ntdb_layout_free(layout);

	/* Coalescing can be done due to two free records, then data */
	layout = new_ntdb_layout();
	ntdb_layout_add_freetable(layout);
	ntdb_layout_add_free(layout, 1024, 0);
	ntdb_layout_add_free(layout, 14488, 0);
	ntdb_layout_add_used(layout, key, data, 6);
	ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
	/* NOMMAP is for lockcheck. */
	ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP|MAYBE_NOSYNC,
			 O_RDWR, 0, &tap_log_attr);
	ok1(free_record_length(ntdb, layout->elem[1].base.off) == 1024);
	ok1(free_record_length(ntdb, layout->elem[2].base.off) == 14488);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);

	/* Figure out which bucket free entry is. */
	b_off = bucket_off(ntdb->ftable_off, size_to_bucket(1024));
	/* Lock and coalesce. */
	ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
	test = layout->elem[2].base.off;
	ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, 1024, &test)
	    == 1024 + sizeof(struct ntdb_used_record) + 14488);
	ok1(ntdb->file->allrecord_lock.count == 0 && ntdb->file->num_lockrecs == 0);
	ok1(free_record_length(ntdb, layout->elem[1].base.off)
	    == 1024 + sizeof(struct ntdb_used_record) + 14488);
	ok1(test == NTDB_ERR_NOEXIST);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);
	ntdb_close(ntdb);
	ntdb_layout_free(layout);

	/* Coalescing can be done due to three free records, then EOF */
	layout = new_ntdb_layout();
	ntdb_layout_add_freetable(layout);
	ntdb_layout_add_free(layout, 1024, 0);
	ntdb_layout_add_free(layout, 512, 0);
	ntdb_layout_add_free(layout, 13992, 0);
	ntdb_layout_write(layout, free, &tap_log_attr, "run-03-coalesce.ntdb");
	/* NOMMAP is for lockcheck. */
	ntdb = ntdb_open("run-03-coalesce.ntdb", NTDB_NOMMAP|MAYBE_NOSYNC,
			 O_RDWR, 0, &tap_log_attr);
	ok1(free_record_length(ntdb, layout->elem[1].base.off) == 1024);
	ok1(free_record_length(ntdb, layout->elem[2].base.off) == 512);
	ok1(free_record_length(ntdb, layout->elem[3].base.off) == 13992);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);

	/* Figure out which bucket free entry is. */
	b_off = bucket_off(ntdb->ftable_off, size_to_bucket(1024));
	/* Lock and coalesce. */
	ok1(ntdb_lock_free_bucket(ntdb, b_off, NTDB_LOCK_WAIT) == 0);
	test = layout->elem[2].base.off;
	ok1(coalesce(ntdb, layout->elem[1].base.off, b_off, 1024, &test)
	    == 1024 + sizeof(struct ntdb_used_record) + 512
	    + sizeof(struct ntdb_used_record) + 13992);
	ok1(ntdb->file->allrecord_lock.count == 0
	    && ntdb->file->num_lockrecs == 0);
	ok1(free_record_length(ntdb, layout->elem[1].base.off)
	    == 1024 + sizeof(struct ntdb_used_record) + 512
	    + sizeof(struct ntdb_used_record) + 13992);
	ok1(ntdb_check(ntdb, NULL, NULL) == 0);
	ntdb_close(ntdb);
	ntdb_layout_free(layout);

	ok1(tap_log_messages == 0);
	return exit_status();
}