// SPDX-License-Identifier: LicenseRef-AGPL-3.0-only-OpenSSL

#include <munit.h>

#include <chiaki/reorderqueue.h>

#define DROP_RECORD_MAX 16

typedef struct drop_record_t
{
	uint64_t count[DROP_RECORD_MAX];
	uint64_t seq_num[DROP_RECORD_MAX];
	bool failed;
} DropRecord;

static void drop(uint64_t seq_num, void *elem_user, void *cb_user)
{
	DropRecord *record = cb_user;
	uint64_t v = (uint64_t)(size_t)elem_user;
	if(v > DROP_RECORD_MAX)
	{
		record->failed = true;
		return;
	}
	record->count[v]++;
	record->seq_num[v] = seq_num;
}

static MunitResult test_reorder_queue_16(const MunitParameter params[], void *test_user)
{
	ChiakiReorderQueue queue;
	ChiakiErrorCode err = chiaki_reorder_queue_init_16(&queue, 2, 42);
	munit_assert_int(err, ==, CHIAKI_ERR_SUCCESS);
	munit_assert_size(chiaki_reorder_queue_size(&queue), ==, 4);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 0);

	chiaki_reorder_queue_set_drop_strategy(&queue, CHIAKI_REORDER_QUEUE_DROP_STRATEGY_END);

	DropRecord drop_record = { 0 };
	chiaki_reorder_queue_set_drop_cb(&queue, drop, &drop_record);

	uint64_t seq_num = 0;
	void *user = NULL;

	// pull from empty
	bool pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(!pulled);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 0);
	munit_assert(!drop_record.failed);

	// push one
	chiaki_reorder_queue_push(&queue, 42, (void *)0);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 1);

	// pull one
	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(pulled);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 0);
	munit_assert(!drop_record.failed);
	munit_assert_uint64(drop_record.count[0], ==, 0);
	munit_assert_uint64((uint64_t)(size_t)user, ==, 0);
	munit_assert_uint64(seq_num, ==, 42);

	// push outdated
	chiaki_reorder_queue_push(&queue, 42, (void *)0);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 0);
	munit_assert(!drop_record.failed);
	munit_assert_uint64(drop_record.count[0], ==, 1);
	munit_assert_uint64(drop_record.seq_num[0], ==, 42);
	memset(&drop_record, 0, sizeof(drop_record));

	// push until full out of order and try to pull in between
	chiaki_reorder_queue_push(&queue, 46, (void *)1);
	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(!pulled);
	chiaki_reorder_queue_push(&queue, 45, (void *)2);
	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(!pulled);
	chiaki_reorder_queue_push(&queue, 44, (void *)3);
	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(!pulled);
	chiaki_reorder_queue_push(&queue, 43, (void *)4);
	munit_assert(!drop_record.failed);
	for(size_t i=0; i<DROP_RECORD_MAX; i++)
		munit_assert_uint64(drop_record.count[i], ==, 0);

	// push more, because of CHIAKI_REORDER_QUEUE_DROP_STRATEGY_END this should be dropped
	chiaki_reorder_queue_push(&queue, 47, (void *)5);
	munit_assert(!drop_record.failed);
	for(size_t i=0; i<DROP_RECORD_MAX; i++)
		munit_assert_uint64(drop_record.count[i], ==, i == 5 ? 1 : 0);
	munit_assert_uint64(drop_record.seq_num[5], ==, 47);
	memset(&drop_record, 0, sizeof(drop_record));

	// push more with CHIAKI_REORDER_QUEUE_DROP_STRATEGY_BEGIN, so older elements should be dropped
	chiaki_reorder_queue_set_drop_strategy(&queue, CHIAKI_REORDER_QUEUE_DROP_STRATEGY_BEGIN);
	chiaki_reorder_queue_push(&queue, 47, (void *)5);
	munit_assert(!drop_record.failed);
	for(size_t i=0; i<DROP_RECORD_MAX; i++)
		munit_assert_uint64(drop_record.count[i], ==, i == 4 ? 1 : 0);
	munit_assert_uint64(drop_record.seq_num[4], ==, 43);
	memset(&drop_record, 0, sizeof(drop_record));
	
	// pull all, elements should arrive in order
	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(pulled);
	munit_assert_uint64(seq_num, ==, 44);
	munit_assert_uint64((uint64_t)(size_t)user, ==, 3);

	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(pulled);
	munit_assert_uint64(seq_num, ==, 45);
	munit_assert_uint64((uint64_t)(size_t)user, ==, 2);

	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(pulled);
	munit_assert_uint64(seq_num, ==, 46);
	munit_assert_uint64((uint64_t)(size_t)user, ==, 1);

	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(pulled);
	munit_assert_uint64(seq_num, ==, 47);
	munit_assert_uint64((uint64_t)(size_t)user, ==, 5);

	// should be empty now again
	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(!pulled);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 0);

	munit_assert(!drop_record.failed);
	for(size_t i=0; i<DROP_RECORD_MAX; i++)
		munit_assert_uint64(drop_record.count[i], ==, 0);

	// now push something much higher, because of CHIAKI_REORDER_QUEUE_DROP_STRATEGY_BEGIN, the queue should be relocated
	chiaki_reorder_queue_push(&queue, 1337, (void *)6);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 1);
	munit_assert(!drop_record.failed);
	for(size_t i=0; i<DROP_RECORD_MAX; i++)
		munit_assert_uint64(drop_record.count[i], ==, 0);

	// and pull again
	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(pulled);
	munit_assert_uint64(seq_num, ==, 1337);
	munit_assert_uint64((uint64_t)(size_t)user, ==, 6);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 0);

	// same as before, but with an element in the queue that will be dropped
	chiaki_reorder_queue_push(&queue, 1338, (void *)7);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 1);
	munit_assert(!drop_record.failed);
	for(size_t i=0; i<DROP_RECORD_MAX; i++)
		munit_assert_uint64(drop_record.count[i], ==, 0);

	chiaki_reorder_queue_push(&queue, 2000, (void *)8);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 1);
	munit_assert(!drop_record.failed);
	for(size_t i=0; i<DROP_RECORD_MAX; i++)
		munit_assert_uint64(drop_record.count[i], ==, i == 7 ? 1 : 0);
	munit_assert_uint64(drop_record.seq_num[7], ==, 1338);

	// pull again
	pulled = chiaki_reorder_queue_pull(&queue, &seq_num, &user);
	munit_assert(pulled);
	munit_assert_uint64(seq_num, ==, 2000);
	munit_assert_uint64((uint64_t)(size_t)user, ==, 8);
	munit_assert_uint64(chiaki_reorder_queue_count(&queue), ==, 0);

	chiaki_reorder_queue_fini(&queue);

	return MUNIT_OK;
}


MunitTest tests_reorder_queue[] = {
	{
		"/reorder_queue_16",
		test_reorder_queue_16,
		NULL,
		NULL,
		MUNIT_TEST_OPTION_NONE,
		NULL
	},
	{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }
};
