#include "../test.h"
#include "../test_common.h"

#include <stdlib.h>
#include <string.h>

#include <aerospike/as_arraylist.h>
#include <aerospike/as_arraylist_iterator.h>
#include <aerospike/as_integer.h>
#include <aerospike/as_hashmap.h>
#include <aerospike/as_list.h>
#include <aerospike/as_list_iterator.h>
#include <aerospike/as_map.h>
#include <aerospike/as_msgpack.h>
#include <aerospike/as_msgpack_ext.h>
#include <aerospike/as_serializer.h>
#include <aerospike/as_string.h>
#include <aerospike/as_stringmap.h>

#define MAX_BUF_SIZE	2000000

/******************************************************************************
 * STATIC FUNCTIONS
 *****************************************************************************/

static as_val *random_val(void);

static msgpack_compare_t
compare_vals(as_val *v1, as_val *v2)
{
	as_serializer ser;
	as_msgpack_init(&ser);

	uint32_t size1 = as_serializer_serialize_getsize(&ser, v1);
	uint32_t size2 = as_serializer_serialize_getsize(&ser, v2);

	uint8_t *buf1 = alloca(size1);
	uint8_t *buf2 = alloca(size2);

	as_serializer_serialize_presized(&ser, v1, buf1);
	as_serializer_serialize_presized(&ser, v2, buf2);

	as_serializer_destroy(&ser);

	as_unpacker pk1 = {
			.buffer = buf1,
			.offset = 0,
			.length = size1,
	};
	as_unpacker pk2 = {
			.buffer = buf2,
			.offset = 0,
			.length = size2,
	};

	msgpack_compare_t ret = as_unpack_compare(&pk1, &pk2);

	if (pk1.offset != pk1.length) {
		info("pk1.offset(%u) != pk1.length(%u)", pk1.offset, pk1.length)
		return MSGPACK_COMPARE_ERROR;
	}

	if (pk2.offset != pk2.length) {
		info("pk2.offset(%u) != pk2.length(%u)", pk2.offset, pk2.length)
		return MSGPACK_COMPARE_ERROR;
	}

	return ret;
}

static msgpack_compare_t
compare_bufs(uint8_t *buf1, uint32_t size1, uint8_t *buf2, uint32_t size2)
{
	as_unpacker pk1 = {
			.buffer = buf1,
			.offset = 0,
			.length = size1,
	};
	as_unpacker pk2 = {
			.buffer = buf2,
			.offset = 0,
			.length = size2,
	};

	msgpack_compare_t ret = as_unpack_compare(&pk1, &pk2);

	if (pk1.offset != pk1.length) {
		info("pk1.offset(%u) != pk1.length(%u)", pk1.offset, pk1.length)
		return MSGPACK_COMPARE_ERROR;
	}

	if (pk2.offset != pk2.length) {
		info("pk2.offset(%u) != pk2.length(%u)", pk2.offset, pk2.length)
		return MSGPACK_COMPARE_ERROR;
	}

	return ret;
}

static as_val *
random_nil(void)
{
	return (as_val *)&as_nil;
}

static as_val *
random_bool(void)
{
	return (as_val *)(rand() % 2 == 0 ? &as_false : &as_true);
}

static as_val *
random_integer(void)
{
	return (as_val *)as_integer_new(rand());
}

static as_val *
random_string(void)
{
	int len = rand() % 200;
	char* str = alloca(len + 1);

	for (int i = 0; i < len; i++) {
		str[i] = rand() % 256;
	}
	str[len] = '\0';

	return (as_val *)as_string_new_strdup(str);
}

static as_val *
random_list(void)
{
	int cap = rand() % 4 + 1;
	as_arraylist *list = as_arraylist_new(cap, cap);

	for (int i = 0; i < cap; i++) {
		as_arraylist_append(list, random_val());
	}

	return (as_val *)list;
}

static as_val *
random_map(void)
{
	int cap = rand() % 3 + 1;
	as_hashmap *val = as_hashmap_new(cap);

	for (int i = 0; i < cap; i++) {
		as_hashmap_set(val, random_val(), random_val());
	}

	return (as_val *)val;
}

typedef as_val *(*random_val_func)(void);

static as_val *
random_val()
{
	static const random_val_func list[] = {
			random_nil,
			random_bool,
			random_integer,
			random_string,
			random_list,
			random_map,
	};
	const size_t size = sizeof(list) / sizeof(random_val_func);
	int r = rand() % (int)size;
	int i = 2;
	while (i-- > 0) {
		// reroll to reduce chance for map/list
		if (r > 3) {
			r = rand() % (int)size;
		}
	}
	return list[r]();
}

/******************************************************************************
 * TEST CASES
 *****************************************************************************/

TEST( msgpack_size, "size deep list" )
{
	// uint8_t buf[MAX_BUF_SIZE] will cause stack overflow on windows.
	// Use malloc instead.
	uint8_t* buf = malloc(MAX_BUF_SIZE);

	memset(buf, 0x91, MAX_BUF_SIZE - 1);
	buf[MAX_BUF_SIZE - 1] = 0x90;

	as_unpacker pk = {
			.length = MAX_BUF_SIZE,
			.buffer = buf,
			.offset = 0
	};

	int64_t size = as_unpack_size(&pk);
	free(buf);

	assert( size == MAX_BUF_SIZE );
}

TEST( msgpack_compare, "compare deep list" )
{
	// uint8_t buf[MAX_BUF_SIZE] will cause stack overflow on windows.
	// Use malloc instead.
	uint8_t* buf = malloc(MAX_BUF_SIZE);

	memset(buf, 0x91, MAX_BUF_SIZE - 1);
	buf[MAX_BUF_SIZE - 1] = 0x90;

	msgpack_compare_t ret = as_unpack_buf_compare(buf, MAX_BUF_SIZE, buf, MAX_BUF_SIZE);
	free(buf);

	assert( ret == MSGPACK_COMPARE_EQUAL );
}

TEST( msgpack_compare_utf8, "compare utf8" )
{
	as_string s1;
	as_string s2;

	as_string_init(&s1, "hi", false);
	as_string_init(&s2, "hi", false);

	assert( compare_vals((as_val *)&s1, (as_val *)&s2) == MSGPACK_COMPARE_EQUAL );

	as_string_destroy(&s1);
	as_string_destroy(&s2);

	// smiley face emoji is U+1F603
	as_string_init(&s1, "hi😃😁😂😄😅😆", false);
	// neutral face emoji is U+1F610
	as_string_init(&s2, "hi😐😁😂😄😅😆", false);

	assert( compare_vals((as_val *)&s1, (as_val *)&s2) == MSGPACK_COMPARE_LESS );

	as_string_destroy(&s1);
	as_string_destroy(&s2);

	as_string_init(&s1, "hi😃😐😁😂😄😅😆", false);
	as_string_init(&s2, "hi😃😐😁😂😄😅😆", false);

	assert( compare_vals((as_val *)&s1, (as_val *)&s2) == MSGPACK_COMPARE_EQUAL );

	as_string_destroy(&s1);
	as_string_destroy(&s2);

	as_string_init(&s1, "hi😃😐😁😂😄😅😆hello", false);
	as_string_init(&s2, "hi😃😐😁😂😄😅😆", false);

	assert( compare_vals((as_val *)&s1, (as_val *)&s2) == MSGPACK_COMPARE_GREATER );

	as_string_destroy(&s1);
	as_string_destroy(&s2);
}

TEST( msgpack_compare_mixed, "compare mixed" )
{
	as_arraylist list1;
	as_arraylist list2;

	as_arraylist_init(&list1, 10, 10);
	as_arraylist_init(&list2, 10, 10);

	for (int i = 0; i < 9; i++) {
		as_val *val = random_val();
		as_val_reserve(val);

		as_arraylist_append(&list1, val);
		as_arraylist_append(&list2, val);
	}

	assert( compare_vals((as_val *)&list1, (as_val *)&list2) == MSGPACK_COMPARE_EQUAL );

	as_arraylist_append(&list1, (as_val *)as_string_new_strdup("hi😃😐😁😂😄😅😆hello"));
	as_arraylist_append(&list2, (as_val *)as_string_new_strdup("hi😃😐😁😂😄😅😆"));

	assert( compare_vals((as_val *)&list1, (as_val *)&list2) == MSGPACK_COMPARE_GREATER );

	as_arraylist_destroy(&list1);
	as_arraylist_destroy(&list2);
}

TEST( msgpack_compare_mixed_level2, "compare mixed level 2" )
{
	as_arraylist list1;
	as_arraylist list2;

	as_arraylist_init(&list1, 10, 10);
	as_arraylist_init(&list2, 10, 10);

	for (int i = 0; i < 9; i++) {
		as_val *val = random_val();
		as_val_reserve(val);

		as_arraylist_append(&list1, val);
		as_arraylist_append(&list2, val);
	}

	as_arraylist list1_1;
	as_arraylist list2_1;

	as_arraylist_init(&list1_1, 10, 10);
	as_arraylist_init(&list2_1, 10, 10);

	as_arraylist_insert(&list1, 7, (as_val *)&list1_1);
	as_arraylist_insert(&list2, 7, (as_val *)&list2_1);

	for (int i = 0; i < 9; i++) {
		as_val *val = random_val();
		as_val_reserve(val);

		as_arraylist_append(&list1_1, val);
		as_arraylist_append(&list2_1, val);
	}

	assert( compare_vals((as_val *)&list1, (as_val *)&list2) == MSGPACK_COMPARE_EQUAL );

	as_arraylist_insert(&list1_1, 7, (as_val *)as_string_new_strdup("hi😃😐😁😂😄😅😆hello"));
	as_arraylist_insert(&list2_1, 7, (as_val *)as_string_new_strdup("hi😃😐😁😂😄😅😆"));

	assert( compare_vals((as_val *)&list1, (as_val *)&list2) == MSGPACK_COMPARE_GREATER );

	as_arraylist_append(&list1, (as_val *)&as_nil);
	as_arraylist_append(&list1, (as_val *)&as_nil);
	as_arraylist_append(&list2_1, (as_val *)&as_nil);
	as_arraylist_append(&list2_1, (as_val *)&as_nil);

	assert( compare_vals((as_val *)&list1, (as_val *)&list2) == MSGPACK_COMPARE_GREATER );

	as_arraylist_destroy(&list1);
	as_arraylist_destroy(&list2);
}

TEST( msgpack_compare_lists, "compare lists" )
{
	as_arraylist list0;
	int lens[] = {2, 5, 3, 4};
	const char *items[] = {"item1", "item2"};

	as_arraylist_init(&list0, 10, 10);

	for (int i = 0; i < 10; i++) {
		as_arraylist *listp = as_arraylist_new(5, 5);

		as_arraylist_append_str(listp, items[i % 2]);

		for (int j = 1; j < lens[i % 4]; j++) {
			as_arraylist_append_int64(listp, j);
		}

		{
			char *s = as_val_tostring((as_val *)listp);
			info("list[%d]: %s", i, s);
			free(s);
		}

		as_arraylist_append(&list0, (as_val *)listp);
	}


	as_arraylist cmp1;
	as_arraylist_init(&cmp1, 10, 10);
	as_arraylist_append_str(&cmp1, "item1");
	as_arraylist_append(&cmp1, (as_val *)&as_cmp_wildcard);

	{
		char *s = as_val_tostring((as_val *)&cmp1);
		info("cmp1: %s", s);
		free(s);
	}

	uint32_t eq_count = 0;
	for (int i = 0; i < 10; i++) {
		as_list *cmp0 = as_arraylist_get_list(&list0, i);
		msgpack_compare_t cmp = compare_vals((as_val *)cmp0, (as_val *)&cmp1);

		assert_false(cmp == MSGPACK_COMPARE_ERROR);

		if (cmp == MSGPACK_COMPARE_EQUAL) {
			eq_count++;
			msgpack_compare_t cmp2 = compare_vals((as_val *)&cmp1, (as_val *)cmp0);
			assert_true(cmp2 == MSGPACK_COMPARE_EQUAL);
		}
	}
	assert_int_eq(eq_count, 5);

	as_arraylist_destroy(&cmp1);

	as_arraylist list1;
	as_arraylist_init(&list1, 10, 10);
	as_arraylist_concat(&list1, &list0);

	assert_int_eq(compare_vals((as_val *)&list0, (as_val *)&list1), MSGPACK_COMPARE_EQUAL);

	as_arraylist_append_int64(&list1, 1);

	assert_int_eq(compare_vals((as_val *)&list0, (as_val *)&list1), MSGPACK_COMPARE_LESS);

	as_arraylist_remove(&list1, as_arraylist_size(&list1) - 1);

	as_arraylist_destroy(&list0);
	as_arraylist_destroy(&list1);
}

TEST( msgpack_int_direct, "signed int direct" )
{
	const int64_t range = 1000000;
	uint8_t buf[(sizeof(uint64_t) + 1) * 10];

	for (int64_t i = -range; i < range; i += 10) {
		as_packer pk = {
			.buffer = buf,
			.capacity = (uint32_t)sizeof(buf)
		};

		for (int j = 0; j < 10; j++) {
			as_pack_int64(&pk, i + j);
		}

		as_unpacker upk = {
			.buffer = buf,
			.length = pk.offset
		};

		for (int j = 0; j < 10; j++) {
			int64_t result = 0;

			as_unpack_int64(&upk, &result);
			assert( i + j == result );
		}
	}
}

TEST( msgpack_deep, "deep list/map" )
{
	// uint8_t buf[MAX_BUF_SIZE] will cause stack overflow on windows.
	// Use malloc instead.
	uint8_t* buf0 = malloc(MAX_BUF_SIZE);
	uint8_t* buf1 = malloc(MAX_BUF_SIZE);

	as_packer pk0 = {
			.buffer = buf0,
			.capacity = MAX_BUF_SIZE
	};

	as_packer pk1 = {
			.buffer = buf1,
			.capacity = MAX_BUF_SIZE
	};

	for (int i = 0; i < 300; i++) {
		as_pack_list_header(&pk0, 1);
		as_pack_list_header(&pk1, 1);
	}

	as_packer save0 = pk0;
	as_packer save1 = pk1;
	msgpack_compare_t cmp;
	const uint32_t shift = 299;

	as_pack_int64(&pk0, 0);
	as_pack_int64(&pk1, 1);

	cmp = compare_bufs(pk0.buffer, pk0.offset, pk1.buffer, pk1.offset);
	assert_int_eq(cmp, MSGPACK_COMPARE_LESS);
	cmp = compare_bufs(pk0.buffer + shift, pk0.offset - shift,
			pk1.buffer + shift, pk1.offset - shift);
	assert_int_eq(cmp, MSGPACK_COMPARE_LESS);

	pk0 = save0;
	pk1 = save1;

	as_pack_int64(&pk0, 1);
	as_pack_int64(&pk1, 1);

	cmp = compare_bufs(pk0.buffer, pk0.offset, pk1.buffer, pk1.offset);
	assert_int_eq(cmp, MSGPACK_COMPARE_EQUAL);
	cmp = compare_bufs(pk0.buffer + shift, pk0.offset - shift,
			pk1.buffer + shift, pk1.offset - shift);
	assert_int_eq(cmp, MSGPACK_COMPARE_EQUAL);

	pk0 = save0;
	pk1 = save1;

	as_pack_list_header(&pk0, 2);
	as_pack_list_header(&pk1, 2);

	as_pack_list_header(&pk0, 2);
	as_pack_list_header(&pk1, 2);

	save0 = pk0;
	save1 = pk1;

	as_pack_int64(&pk0, 0);
	as_pack_int64(&pk1, 1);

	as_pack_int64(&pk0, 2);
	as_pack_int64(&pk1, 2);

	as_pack_int64(&pk0, 3);
	as_pack_int64(&pk1, 3);

	cmp = compare_bufs(pk0.buffer, pk0.offset, pk1.buffer, pk1.offset);
	assert_int_eq(cmp, MSGPACK_COMPARE_LESS);
	cmp = compare_bufs(pk0.buffer + shift, pk0.offset - shift,
			pk1.buffer + shift, pk1.offset - shift);
	assert_int_eq(cmp, MSGPACK_COMPARE_LESS);

	pk0 = save0;
	pk1 = save1;

	as_pack_int64(&pk0, 0);
	as_pack_int64(&pk1, 0);

	as_pack_int64(&pk0, 2);
	as_pack_int64(&pk1, 2);

	as_pack_int64(&pk0, 3);
	as_pack_int64(&pk1, 3);

	cmp = compare_bufs(pk0.buffer, pk0.offset, pk1.buffer, pk1.offset);
	assert_int_eq(cmp, MSGPACK_COMPARE_EQUAL);
	cmp = compare_bufs(pk0.buffer + shift, pk0.offset - shift,
			pk1.buffer + shift, pk1.offset - shift);
	assert_int_eq(cmp, MSGPACK_COMPARE_EQUAL);

	pk0 = save0;
	pk1 = save1;

	as_pack_int64(&pk0, 0);
	as_pack_cmp_wildcard(&pk1);

	as_pack_int64(&pk0, 0);
	as_pack_int64(&pk1, 2);

	as_pack_int64(&pk0, 3);
	as_pack_int64(&pk1, 3);

	cmp = compare_bufs(pk0.buffer, pk0.offset, pk1.buffer, pk1.offset);
	assert_int_eq(cmp, MSGPACK_COMPARE_EQUAL);
	cmp = compare_bufs(pk0.buffer + shift, pk0.offset - shift,
			pk1.buffer + shift, pk1.offset - shift);
	assert_int_eq(cmp, MSGPACK_COMPARE_EQUAL);

	pk0 = save0;
	pk1 = save1;

	as_pack_list_header(&pk0, 2);
	as_pack_list_header(&pk1, 3);

	save0 = pk0;
	save1 = pk1;

	as_pack_int64(&pk0, 0);
	as_pack_int64(&pk0, 1);
	as_pack_int64(&pk1, 0);
	as_pack_int64(&pk1, 1);
	as_pack_int64(&pk1, 1);

	as_pack_int64(&pk0, 0);
	as_pack_int64(&pk1, 0);

	as_pack_int64(&pk0, 3);
	as_pack_int64(&pk1, 3);

	cmp = compare_bufs(pk0.buffer, pk0.offset, pk1.buffer, pk1.offset);
	assert_int_eq(cmp, MSGPACK_COMPARE_LESS);
	cmp = compare_bufs(pk0.buffer + shift, pk0.offset - shift,
			pk1.buffer + shift, pk1.offset - shift);
	assert_int_eq(cmp, MSGPACK_COMPARE_LESS);

	pk0 = save0;
	pk1 = save1;

	as_pack_cmp_wildcard(&pk0);
	as_pack_int64(&pk0, 1);
	as_pack_int64(&pk1, 0);
	as_pack_int64(&pk1, 1);
	as_pack_int64(&pk1, 1);

	as_pack_int64(&pk0, 0);
	as_pack_int64(&pk1, 0);

	as_pack_int64(&pk0, 3);
	as_pack_int64(&pk1, 3);

	cmp = compare_bufs(pk0.buffer, pk0.offset, pk1.buffer, pk1.offset);
	assert_int_eq(cmp, MSGPACK_COMPARE_EQUAL);
	cmp = compare_bufs(pk0.buffer + shift, pk0.offset - shift,
			pk1.buffer + shift, pk1.offset - shift);
	assert_int_eq(cmp, MSGPACK_COMPARE_EQUAL);

	pk0 = save0;
	pk1 = save1;

	as_pack_cmp_wildcard(&pk0);
	as_pack_int64(&pk0, 1);
	as_pack_int64(&pk1, 0);
	as_pack_int64(&pk1, 1);
	as_pack_int64(&pk1, 1);

	as_pack_int64(&pk0, 0);
	as_pack_int64(&pk1, 0);

	as_pack_int64(&pk0, 3);
	as_pack_int64(&pk1, 2);

	cmp = compare_bufs(pk0.buffer, pk0.offset, pk1.buffer, pk1.offset);
	assert_int_eq(cmp, MSGPACK_COMPARE_GREATER);
	cmp = compare_bufs(pk0.buffer + shift, pk0.offset - shift,
			pk1.buffer + shift, pk1.offset - shift);
	assert_int_eq(cmp, MSGPACK_COMPARE_GREATER);

	free(buf0);
	free(buf1);
}

/******************************************************************************
 * TEST SUITE
 *****************************************************************************/

SUITE( msgpack_direct, "as_msgpack direct size/compare" )
{
	suite_add( msgpack_size );
	suite_add( msgpack_compare );
	suite_add( msgpack_compare_utf8 );
	suite_add( msgpack_compare_mixed );
	suite_add( msgpack_compare_mixed_level2 );
	suite_add( msgpack_compare_lists );
	suite_add( msgpack_int_direct );
	suite_add( msgpack_deep );
}
