/*
 * Embedded Linux library
 * Copyright (C) 2023  Cruise LLC
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <stdio.h>
#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <alloca.h>

#include <ell/ell.h>
#include "ell/useful.h"

static inline void swap_int(void *l, void *r)
{
	int *li = l;
	int *ri = r;

	SWAP(*li, *ri);
}

static inline bool less(const void *l, const void *r)
{
	const int *li = l;
	const int *ri = r;

	return *li < *ri;
}

static const struct l_minheap_ops ops = {
	.less = less,
	.swap = swap_int,
	.elem_size = sizeof(int),
};

static const int test_values[] = {
	3, 1, 2, 4, INT_MAX, INT_MIN, -4, -2, -1, -3, 0, INT_MIN, INT_MAX
};

static void verify_pop(struct l_minheap *minheap)
{
	size_t size = minheap->used;
	int *values = minheap->data;
	int last;

	last = values[0];
	while (size) {
		assert(last <= values[0]);
		last = values[0];
		assert(l_minheap_pop(minheap, &ops, NULL));
		size -= 1;
	}
}

static void test_minheap_init(const void *data)
{
	struct l_minheap minheap;
	int *values = alloca(sizeof(test_values));

	memcpy(values, test_values, sizeof(test_values));

	l_minheap_init(&minheap, values, L_ARRAY_SIZE(test_values),
			L_ARRAY_SIZE(test_values), &ops);

	verify_pop(&minheap);
}

static void test_minheap_push(const void *data)
{
	struct l_minheap minheap;
	int *values = l_newa(int, L_ARRAY_SIZE(test_values));
	unsigned int i;

	l_minheap_init(&minheap, values, 0, L_ARRAY_SIZE(test_values), &ops);

	for (i = 0; i < L_ARRAY_SIZE(test_values); i++)
		assert(l_minheap_push(&minheap, &ops, &test_values[i]));

	verify_pop(&minheap);
}

static void test_minheap_push_random(const void *data)
{
	struct l_minheap minheap;
	unsigned int n_items = 1024 * 1024;
	int *values = l_malloc(sizeof(int) * n_items);
	unsigned int i;

	l_minheap_init(&minheap, values, 0, n_items, &ops);

	for (i = 0; i < n_items; i++) {
		unsigned int r = random();

		assert(l_minheap_push(&minheap, &ops, &r));
	}

	verify_pop(&minheap);
	l_free(values);
}

static void test_minheap_pop_push(const void *data)
{
	struct l_minheap minheap;
	int *values = alloca(sizeof(test_values));
	unsigned int i;
	int tmp;

	for (i = 0; i < L_ARRAY_SIZE(test_values); i++)
		values[i] = INT_MIN;

	l_minheap_init(&minheap, values, L_ARRAY_SIZE(test_values),
			L_ARRAY_SIZE(test_values), &ops);

	for (i = 0; i < L_ARRAY_SIZE(test_values); i++) {
		tmp = test_values[i];

		assert(l_minheap_pop_push(&minheap, &ops, &tmp));
		assert(tmp == INT_MIN);
	}

	verify_pop(&minheap);
}

static void test_minheap_delete(const void *data)
{
	struct l_minheap minheap;
	int *values = l_newa(int, L_ARRAY_SIZE(test_values));
	unsigned int i;

	for (i = 0; i < L_ARRAY_SIZE(test_values); i++) {
		l_minheap_init(&minheap, values, L_ARRAY_SIZE(test_values),
				L_ARRAY_SIZE(test_values), &ops);
		assert(l_minheap_delete(&minheap, i, &ops));
		verify_pop(&minheap);
	}
}

int main(int argc, char *argv[])
{
	l_test_init(&argc, &argv);

	l_test_add("minheap/init", test_minheap_init, NULL);
	l_test_add("minheap/push", test_minheap_push, NULL);
	l_test_add("minheap/push_random", test_minheap_push_random, NULL);
	l_test_add("minheap/pop_push", test_minheap_pop_push, NULL);
	l_test_add("minheap/delete", test_minheap_delete, NULL);

	return l_test_run();
}
