#include <inttypes.h>
#include <stdlib.h>
#include "../rheap.h"
#include "gtest/gtest.h"

using namespace rheap;

TEST(rheap_tests, creation_test) {
	rheap_t heap(100, 5);
	EXPECT_EQ(0, heap.get_nitems()) << "Invalid nitems.";
	EXPECT_EQ(11, heap.get_nbuckets()) << "Invalid nbuckets.";
	EXPECT_TRUE(heap.is_empty()) << "Heap should initially be empty.";
	ASSERT_DEATH(heap.delete_min(), "Cannot delete") << "Empty heap should throw on delete-min()";
}

TEST(rheap_tests, insert_test) {
	rheap_t heap(100, 100);

	heap.insert(1, 50);
	ASSERT_EQ(1, heap.get_nitems()) << "nitems is invalid.";
	ASSERT_EQ(50, heap.get_buckets()[7].pnodes->pnext->d) << "Invalid inserted item location.";

	heap.insert(2, 70);
	ASSERT_EQ(2, heap.get_nitems()) << "nitems is invalid.";
	ASSERT_EQ(70, heap.get_buckets()[8].pnodes->pnext->d) << "Invalid inserted item location.";
}

TEST(rheap_tests, reinsert_test) {
	rheap_t heap(100, 333);
	heap.insert(7, 123);
	ASSERT_DEATH(heap.insert(7, 321), "Cannot reinsert") << "Reinsertion should be prohibited.";
}

TEST(rheap_tests, decrease_key_test) {
	rheap_t heap(100, 250);
	heap.insert(5, 100);
	EXPECT_EQ(1, heap.get_nitems());
	EXPECT_EQ(100, heap.get_buckets()[8].pnodes->pnext->d);
	heap.decrease_key(5, 10);
	EXPECT_EQ(1, heap.get_nitems());
	EXPECT_TRUE(heap.get_buckets()[8].is_empty());
	EXPECT_EQ(10, heap.get_buckets()[5].pnodes->pnext->d);
}

TEST(rheap_tests, decrease_key_monotonicity_test) {
	rheap_t heap(100, 200);
	heap.insert(5, 100);
	ASSERT_DEATH(heap.decrease_key(5, 101), "cannot decrease")
		<< "Decrease-key can only be used with actually decreasing keys.";
}

TEST(rheap_tests, repeat_decrease_key_test) {
	rheap_t heap(100, 200);
	heap.insert(1, 100);
	heap.decrease_key(1, 50);
	heap.decrease_key(1, 10);
	heap.decrease_key(1, 1);
	heap.decrease_key(1, 0);
	for(int i = 2; i < heap.get_nbuckets(); ++i)
		EXPECT_TRUE(heap.get_buckets()[i].is_empty())
			<< i << "'th bucket is not empty.";
	EXPECT_EQ(1, heap.get_buckets()[1].pnodes->pnext->name);
}

void shuffle2(int64_t *vals1, int64_t *vals2, const int64_t n, const int64_t seed) {
	srand(seed);
	for(int64_t i = 0; i < n; i++) {
		int64_t j = rand() % n;

		int64_t temp = vals1[i];
		vals1[i] = vals1[j];
		vals1[j] = temp;

		if(vals1 != vals2) {
			temp = vals2[i];
			vals2[i] = vals2[j];
			vals2[j] = temp;
		}
	}
}

void shuffle(int64_t *vals, const int64_t n, const int64_t seed) {
	shuffle2(vals, vals, n, seed);
}

TEST(rheap_tests, delete_min_test) {
	int seed = 2128506;
	uint64_t n = 10;
	uint64_t maxcost = 1000;

	int64_t *pnames = new int64_t[n];
	int64_t *pdists = new int64_t[n];

	for(int64_t i = 0; i < n; ++i) {
		pnames[i] = i;
		pdists[i] = i;
	}
	shuffle(pnames, n, seed);

	int64_t *pnames_wc = new int64_t[n];
	int64_t *pdists_wc = new int64_t[n];
	for(int64_t i = 0; i < n; ++i) {
		pnames_wc[i] = pnames[i];
		pdists_wc[i] = pdists[i];
	}
	shuffle2(pnames_wc, pdists_wc, n, seed);

	rheap_t heap(n, maxcost);
	for(int64_t i = 0; i < n; ++i)
		heap.insert(pnames_wc[i], pdists_wc[i]);

	for(int64_t i = 0; i < n; ++i) {
		int64_t actual = heap.delete_min();
		ASSERT_EQ(n - i - 1, heap.get_nitems()) << "Invalid heap size after delete-min.";
		int64_t expected = pnames[i];
		ASSERT_EQ(expected, actual) << "Invalid node extracted on delete-min.";
	}
	ASSERT_TRUE(heap.is_empty()) << "Heap should be empty after all elements are deleted.";

	delete [] pnames;
	delete [] pnames_wc;
	delete [] pdists;
	delete [] pdists_wc;
}

/*
TEST(rheap_tests, ahuja_test) {
	rheap_t heap(6, 20);
	heap.insert(1, 13);
	heap.insert(2,  0);
	heap.insert(3, 15);
	heap.insert(4, 20);
	heap.dump();

	EXPECT_EQ(2, heap.delete_min());
	heap.decrease_key(4, 9);
	heap.dump();
	
	EXPECT_EQ(4, heap.delete_min());
	heap.dump();
}
*/

int main(int argc, char **argv) {
	::testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}

