#include <stdarg.h>
#include <stddef.h>
#include <string.h>

#include "fiber.h"
#include "key_def.h"
#include "memory.h"
#include "msgpuck.h"
#include "small/region.h"
#include "trivia/util.h"
#include "tuple.h"
#include "mp_util.h"

#define UNIT_TAP_COMPATIBLE 1
#include "unit.h"

static char *
test_key_new_va(const char *format, va_list ap)
{
	struct region *region = &fiber()->gc;
	size_t region_svp = region_used(region);

	/* Format the MsgPack key definition. */
	const size_t mp_buf_size = 1024;
	char *mp_buf = region_alloc(region, mp_buf_size);
	fail_if(mp_buf == NULL);
	size_t mp_size = mp_vformat(mp_buf, mp_buf_size, format, ap);
	fail_if(mp_size > mp_buf_size);

	/* Create a key. */
	char *key = xmalloc(mp_size);
	memcpy(key, mp_buf, mp_size);

	region_truncate(region, region_svp);
	return key;
}

/** Creates a key from a MsgPack format (see mp_format). */
static char *
test_key_new(const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	char *key = test_key_new_va(format, ap);
	fail_unless(mp_typeof(*key) == MP_ARRAY);
	va_end(ap);
	return key;
}

static struct tuple *
test_tuple_new_va(const char *format, va_list ap)
{
	struct region *region = &fiber()->gc;
	size_t region_svp = region_used(region);

	/* Format the MsgPack key definition. */
	const size_t mp_buf_size = 1024;
	char *mp_buf = region_alloc(region, mp_buf_size);
	fail_if(mp_buf == NULL);
	size_t mp_size = mp_vformat(mp_buf, mp_buf_size, format, ap);
	fail_if(mp_size > mp_buf_size);

	/* Create a tuple. */
	struct tuple *tuple = tuple_new(tuple_format_runtime, mp_buf,
					mp_buf + mp_size);
	fail_if(tuple == NULL);

	region_truncate(region, region_svp);
	return tuple;
}

/** Creates a tuple from a MsgPack format (see mp_format). */
static struct tuple *
test_tuple_new(const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	struct tuple *tuple = test_tuple_new_va(format, ap);
	va_end(ap);
	return tuple;
}

static struct key_def *
test_key_def_new_va(const char *format, va_list ap, bool for_func_index)
{
	struct region *region = &fiber()->gc;
	size_t region_svp = region_used(region);

	/* Format the MsgPack key definition. */
	const size_t mp_buf_size = 1024;
	char *mp_buf = region_alloc(region, mp_buf_size);
	fail_if(mp_buf == NULL);
	fail_if(mp_vformat(mp_buf, mp_buf_size, format, ap) > mp_buf_size);

	/* Decode the key parts. */
	const char *parts = mp_buf;
	uint32_t part_count = mp_decode_array(&parts);
	size_t stub;
	struct key_part_def *part_def =
		(struct key_part_def *)region_alloc_array(region,
							  struct key_part_def,
							  part_count, &stub);
	fail_if(part_def == NULL);
	fail_if(key_def_decode_parts(part_def, part_count, &parts,
				     /*fields=*/NULL, /*field_count=*/0,
				     region) != 0);

	/* Create a key def. */
	struct key_def *def = key_def_new(part_def, part_count, for_func_index);
	fail_if(def == NULL);
	key_def_update_optionality(def, 0);

	region_truncate(region, region_svp);
	return def;
}

/** Creates a key_def from a MsgPack format (see mp_format). */
static struct key_def *
test_key_def_new(const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	struct key_def *def = test_key_def_new_va(format, ap,
						  /*for_func_index=*/false);
	va_end(ap);
	return def;
}

/** Creates a functional index key_def from a MsgPack format (see mp_format). */
static struct key_def *
test_key_def_new_func(const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	struct key_def *def = test_key_def_new_va(format, ap,
						  /*for_func_index=*/true);
	va_end(ap);
	return def;
}

/**
 * Checks that tuple_compare() -> func_index_compare() return value equals
 * `expected`.
 */
static void
test_check_tuple_compare_func(struct key_def *cmp_def,
			      struct tuple *tuple_a, struct tuple *func_key_a,
			      struct tuple *tuple_b, struct tuple *func_key_b,
			      int expected)
{
	int r = tuple_compare(tuple_a, (hint_t)func_key_a,
			      tuple_b, (hint_t)func_key_b, cmp_def);
	r = r > 0 ? 1 : r < 0 ? -1 : 0;
	is(r, expected, "func_index_compare(%s/%s, %s/%s) = %d, expected %d",
	   tuple_str(tuple_a), tuple_str(func_key_a),
	   tuple_str(tuple_b), tuple_str(func_key_b), r, expected);
}

static void
test_func_compare(void)
{
	plan(6);
	header();

	struct key_def *func_def = test_key_def_new_func(
		"[{%s%u%s%s%s%b}{%s%u%s%s%s%b}]",
		"field", 0, "type", "string", "is_nullable", 1,
		"field", 1, "type", "string", "is_nullable", 1);

	struct key_def *pk_def = test_key_def_new(
		"[{%s%u%s%s}]",
		"field", 1, "type", "unsigned");

	struct key_def *cmp_def = key_def_merge(func_def, pk_def);
	/* Just like when `opts->is_unique == true`, see index_def_new(). */
	cmp_def->unique_part_count = func_def->part_count;

	struct testcase {
		int expected_result;
		struct tuple *tuple_a;
		struct tuple *tuple_b;
		struct tuple *func_key_a;
		struct tuple *func_key_b;
	};

	struct testcase testcases[] = {
		{
			-1, /* func_key_a < func_key_b */
			test_tuple_new("[%s%u%s]", "--", 0, "--"),
			test_tuple_new("[%s%u%s]", "--", 0, "--"),
			test_tuple_new("[%sNIL]", "aa"),
			test_tuple_new("[%s%s]", "aa", "bb"),
		}, {
			1, /* func_key_a > func_key_b */
			test_tuple_new("[%s%u%s]", "--", 0, "--"),
			test_tuple_new("[%s%u%s]", "--", 0, "--"),
			test_tuple_new("[%s%s]", "aa", "bb"),
			test_tuple_new("[%sNIL]", "aa"),
		}, {
			0, /* func_key_a == func_key_b, pk not compared */
			test_tuple_new("[%s%u%s]", "--", 10, "--"),
			test_tuple_new("[%s%u%s]", "--", 20, "--"),
			test_tuple_new("[%s%s]", "aa", "bb"),
			test_tuple_new("[%s%s]", "aa", "bb"),
		}, {
			-1, /* func_key_a == func_key_b, pk_a < pk_b */
			test_tuple_new("[%s%u%s]", "--", 30, "--"),
			test_tuple_new("[%s%u%s]", "--", 40, "--"),
			test_tuple_new("[%sNIL]", "aa"),
			test_tuple_new("[%sNIL]", "aa"),
		}, {
			1, /* func_key_a == func_key_b, pk_a > pk_b */
			test_tuple_new("[%s%u%s]", "--", 60, "--"),
			test_tuple_new("[%s%u%s]", "--", 50, "--"),
			test_tuple_new("[%sNIL]", "aa"),
			test_tuple_new("[%sNIL]", "aa"),
		}, {
			0, /* func_key_a == func_key_b, pk_a == pk_b */
			test_tuple_new("[%s%u%s]", "--", 70, "--"),
			test_tuple_new("[%s%u%s]", "--", 70, "--"),
			test_tuple_new("[%sNIL]", "aa"),
			test_tuple_new("[%sNIL]", "aa"),
		}
	};

	for (size_t i = 0; i < lengthof(testcases); i++) {
		struct testcase *t = &testcases[i];
		test_check_tuple_compare_func(cmp_def,
					      t->tuple_a, t->func_key_a,
					      t->tuple_b, t->func_key_b,
					      t->expected_result);
	}

	for (size_t i = 0; i < lengthof(testcases); i++) {
		struct testcase *t = &testcases[i];
		tuple_delete(t->tuple_a);
		tuple_delete(t->tuple_b);
		tuple_delete(t->func_key_a);
		tuple_delete(t->func_key_b);
	}
	key_def_delete(func_def);
	key_def_delete(pk_def);
	key_def_delete(cmp_def);

	footer();
	check_plan();
}

/**
 * Checks that tuple_compare_with_key with cmp_def of functional index
 * returns the same result as comparison of concatenated func and primary keys.
 */
static void
test_check_tuple_compare_with_key_func(
		struct key_def *cmp_def, struct tuple *tuple,
		struct tuple *func_key, struct key_def *model_def,
		struct tuple *model, const char *key)
{
	fail_unless(cmp_def->for_func_index);
	fail_if(model_def->for_func_index);
	const char *key_parts = key;
	uint32_t part_count = mp_decode_array(&key_parts);
	int a = tuple_compare_with_key(tuple, (hint_t)func_key, key_parts,
				       part_count, HINT_NONE, cmp_def);
	int b = tuple_compare_with_key(model, HINT_NONE, key_parts,
				       part_count, HINT_NONE, model_def);
	a = a > 0 ? 1 : a < 0 ? -1 : 0;
	b = b > 0 ? 1 : b < 0 ? -1 : 0;
	is(a, b, "tuple_compare_with_key_func(%s/%s, %s) = %d, expected %d",
	   tuple_str(tuple), tuple_str(func_key), mp_str(key), a, b);
}

static void
test_func_compare_with_key(void)
{
	plan(14);
	header();

	struct key_def *def = test_key_def_new_func(
		"[{%s%u%s%s}{%s%u%s%s}]",
		"field", 0, "type", "unsigned",
		"field", 1, "type", "string");
	/* Skip first field to check if func comparator can handle this. */
	struct key_def *pk_def = test_key_def_new(
		"[{%s%u%s%s}{%s%u%s%s}]",
		"field", 1, "type", "unsigned",
		"field", 2, "type", "string");
	struct key_def *cmp_def = key_def_merge(def, pk_def);
	/*
	 * Model def is a copy of cmp_def, but not for_func_index, and hence
	 * it has general implementation of tuple_compare_with_key method.
	 */
	struct key_def *model_def = test_key_def_new(
		"[{%s%u%s%s}{%s%u%s%s}{%s%u%s%s}{%s%u%s%s}]",
		"field", 0, "type", "unsigned",
		"field", 1, "type", "string",
		"field", 3, "type", "unsigned",
		"field", 4, "type", "string");
	struct tuple *func_key = test_tuple_new("[%u%s]", 20, "foo");
	struct tuple *tuple = test_tuple_new("[%u%u%s]", 200, 10, "cpp");
	/*
	 * Model tuple is concatenated func_key and tuple's primary key.
	 * Note that the 3rd field does not take part in comparison, so it
	 * is intentionally different from the first field of tuple, which is
	 * not compared too.
	 */
	struct tuple *model =
		test_tuple_new("[%u%s%u%u%s]", 20, "foo", 100, 10, "cpp");
	char *keys[] = {
		test_key_new("[]"),
		test_key_new("[%u]", 10),
		test_key_new("[%u]", 20),
		test_key_new("[%u]", 30),
		test_key_new("[%u%s]", 10, "foo"),
		test_key_new("[%u%s]", 20, "foo"),
		test_key_new("[%u%s]", 20, "bar"),
		test_key_new("[%u%s]", 30, "foo"),
		test_key_new("[%u%s%u]", 20, "foo", 5),
		test_key_new("[%u%s%u]", 20, "foo", 10),
		test_key_new("[%u%s%u]", 20, "foo", 15),
		test_key_new("[%u%s%u%s]", 20, "foo", 10, "bar"),
		test_key_new("[%u%s%u%s]", 20, "foo", 10, "cpp"),
		test_key_new("[%u%s%u%s]", 20, "foo", 10, "foo"),
	};
	for (unsigned int i = 0; i < lengthof(keys); i++) {
		test_check_tuple_compare_with_key_func(
			cmp_def, tuple, func_key, model_def, model, keys[i]);
	}
	for (unsigned int i = 0; i < lengthof(keys); i++)
		free(keys[i]);
	tuple_delete(func_key);
	tuple_delete(tuple);
	tuple_delete(model);
	key_def_delete(def);
	key_def_delete(pk_def);
	key_def_delete(cmp_def);

	footer();
	check_plan();
}

static void
test_check_tuple_extract_key_raw(struct key_def *key_def, struct tuple *tuple,
				 const char *key)
{
	uint32_t tuple_size;
	const char *tuple_data = tuple_data_range(tuple, &tuple_size);
	const char *tuple_key =
		tuple_extract_key_raw(tuple_data, tuple_data + tuple_size,
				      key_def, MULTIKEY_NONE, NULL);
	/*
	 * Set zeroes next to extracted key to check if it has not gone
	 * beyond the bounds of its memory.
	 */
	void *alloc = region_alloc(&fiber()->gc, 10);
	memset(alloc, 0, 10);
	const char *key_a = tuple_key;
	uint32_t part_count_a = mp_decode_array(&key_a);
	const char *key_b = key;
	uint32_t part_count_b = mp_decode_array(&key_b);
	ok(key_compare(key_a, part_count_a, HINT_NONE,
		       key_b, part_count_b, HINT_NONE, key_def) == 0 &&
	   part_count_a == part_count_b,
	   "Extracted key of tuple %s is %s, expected %s",
	   tuple_str(tuple), mp_str(tuple_key), mp_str(key));
}

static void
test_tuple_extract_key_raw_slowpath_nullable(void)
{
	plan(2);
	header();

	/* Create non-sequential key_defs to use slowpath implementation. */
	struct key_def *def_nullable_end = test_key_def_new(
		"[{%s%u%s%s}{%s%u%s%s%s%b}{%s%u%s%s%s%b}]",
		"field", 0, "type", "unsigned",
		"field", 2, "type", "unsigned", "is_nullable", 1,
		"field", 5, "type", "unsigned", "is_nullable", 1);
	struct key_def *def_nullable_begin = test_key_def_new(
		"[{%s%u%s%s%s%b}{%s%u%s%s%s%b}{%s%u%s%s}]",
		"field", 2, "type", "unsigned", "is_nullable", 1,
		"field", 5, "type", "unsigned", "is_nullable", 1,
		"field", 0, "type", "unsigned");
	fail_if(def_nullable_end == NULL || def_nullable_begin == NULL);
	struct tuple *tuple = test_tuple_new("[%u]", 10);
	fail_if(tuple == NULL);
	char *key_null_end = test_key_new("[%uNILNIL]", 10);
	char *key_null_begin = test_key_new("[NILNIL%u]", 10);
	fail_if(key_null_end == NULL || key_null_begin == NULL);

	size_t region_svp = region_used(&fiber()->gc);
	test_check_tuple_extract_key_raw(def_nullable_end, tuple,
					 key_null_end);
	test_check_tuple_extract_key_raw(def_nullable_begin, tuple,
					 key_null_begin);

	key_def_delete(def_nullable_end);
	key_def_delete(def_nullable_begin);
	tuple_delete(tuple);
	free(key_null_end);
	free(key_null_begin);
	region_truncate(&fiber()->gc, region_svp);

	footer();
	check_plan();
}

static void
test_tuple_validate_key_parts_raw(void)
{
	plan(7);
	header();

	struct key_def *def = test_key_def_new(
		"[{%s%u%s%s}{%s%u%s%s%s%b}]",
		"field", 0, "type", "unsigned",
		"field", 2, "type", "unsigned", "is_nullable", 1);
	fail_if(def == NULL);
	struct tuple *invalid_tuples[3] = {
		test_tuple_new("[%s]", "abc"),
		test_tuple_new("[%u%u%s]", 1, 20, "abc"),
		test_tuple_new("[%s%u%u]", "abc", 5, 10),
	};
	struct tuple *valid_tuples[4] = {
		test_tuple_new("[%u]", 10),
		test_tuple_new("[%u%u]", 10, 20),
		test_tuple_new("[%u%u%u]", 1, 5, 10),
		test_tuple_new("[%u%s%u%u]", 1, "dce", 5, 10),
	};
	for (size_t i = 0; i < lengthof(invalid_tuples); ++i)
		fail_if(invalid_tuples[i] == NULL);
	for (size_t i = 0; i < lengthof(valid_tuples); ++i)
		fail_if(valid_tuples[i] == NULL);

	for (size_t i = 0; i < lengthof(invalid_tuples); ++i)
		is(tuple_validate_key_parts_raw(def,
						tuple_data(invalid_tuples[i])),
		   -1, "tuple %zu must be invalid", i);
	for (size_t i = 0; i < lengthof(valid_tuples); ++i)
		is(tuple_validate_key_parts_raw(def,
						tuple_data(valid_tuples[i])),
		   0, "tuple %zu must be valid", i);

	key_def_delete(def);
	for (size_t i = 0; i < lengthof(invalid_tuples); ++i)
		tuple_delete(invalid_tuples[i]);
	for (size_t i = 0; i < lengthof(valid_tuples); ++i)
		tuple_delete(valid_tuples[i]);

	footer();
	check_plan();
}

static void
test_key_def_find_by_fieldno(void)
{
	plan(3);
	header();

	const struct key_part *key_part;
	struct key_def *key_def = test_key_def_new(
		"[{%s%u%s%s}{%s%u%s%s}{%s%u%s%s%s%s}]",
		"field", 1, "type", "unsigned",
		"field", 2, "type", "string",
		"field", 3, "type", "string", "path", "foo");

	key_part = key_def_find_by_fieldno(key_def, 2);
	isnt(key_part, NULL, "field 2 (no path) found");

	key_part = key_def_find_by_fieldno(key_def, 3);
	isnt(key_part, NULL, "field 3 (with path) found");

	key_part = key_def_find_by_fieldno(key_def, 100);
	is(key_part, NULL, "field 100 not found");

	key_def_delete(key_def);

	footer();
	check_plan();
}

static int
test_main(void)
{
	plan(5);
	header();

	test_func_compare();
	test_func_compare_with_key();
	test_tuple_extract_key_raw_slowpath_nullable();
	test_tuple_validate_key_parts_raw();
	test_key_def_find_by_fieldno();

	footer();
	return check_plan();
}

static uint32_t
test_field_name_hash(const char *str, uint32_t len)
{
	return str[0] + len;
}

int
main(void)
{
	memory_init();
	fiber_init(fiber_c_invoke);
	tuple_init(test_field_name_hash);

	int rc = test_main();

	tuple_free();
	fiber_free();
	memory_free();
	return rc;
}
