#include <inttypes.h>
#include <stdlib.h>
#include <vector>
#include "../spq.h"
#include "../../../debug.h"
#include "gtest/gtest.h"

using namespace spq;

TEST(debug_tests, assert_minheap_test) {
	std::vector<uint64_t> v;
	v.push_back((uint64_t)-1);
	v.push_back(0);
	ASSERT_MINHEAP(v);
	v.push_back(3);
	ASSERT_MINHEAP(v);
	v.push_back(1);
	ASSERT_MINHEAP(v);
	v.push_back(3);
	ASSERT_MINHEAP(v);
	v.push_back(2);
	ASSERT_DEATH(ASSERT_MINHEAP(v), "Heap invariant violated") << "Heap validation should not have passed successfully.";
}

TEST(spq_tests, creation_test) {
	spq_t pq(100, 5);
	EXPECT_TRUE(pq.is_empty()) << "Invalid is-empty for an empty queue.";
	uint64_t dummy;
	ASSERT_DEATH(pq.delete_mins(dummy), "Cannot delete") << "Empty pq should throw on delete-min()";
}

TEST(spq_tests, insert_test) {
	spq_t pq(100, 100);
	pq.insert(1, 50);
	ASSERT_EQ(1, pq.nitems()) << "nitems is invalid.";
	pq.insert(2, 70);
	ASSERT_EQ(2, pq.nitems()) << "nitems is invalid.";
}

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

TEST(spq_tests, decrease_key_test) {
	spq_t pq(100, 250);
	pq.insert(5, 100);
	EXPECT_EQ(1, pq.nitems());
	EXPECT_TRUE(pq.contains(5));
	pq.decrease_key(5, 10);
	EXPECT_EQ(1, pq.nitems());
	EXPECT_TRUE(pq.contains(5));

	uint64_t dmin = -1;
	node_t* mins = pq.delete_mins(dmin);
	EXPECT_EQ(0, pq.nitems());
	EXPECT_TRUE(pq.is_empty());
	EXPECT_FALSE(pq.contains(5));
	EXPECT_EQ(NULL, mins->_pnext) << "Single node should have been returned.";
	EXPECT_EQ(5, mins->_name) << "Invalid node name.";
	EXPECT_EQ(10, dmin) << "Invalid dmin";
}

TEST(spq_tests, decrease_key_monotonicity_test) {
	spq_t pq(100, 200);
	pq.insert(5, 100);
	ASSERT_DEATH(pq.decrease_key(5, 101), "annot decrease")
		<< "Decrease-key can only be used with actually decreasing keys.";
}

TEST(spq_tests, repeat_decrease_key_test) {
	spq_t pq(100, 200);
	pq.insert(1, 100);
	pq.decrease_key(1, 50);
	pq.decrease_key(1, 10);
	pq.decrease_key(1, 1);
	pq.decrease_key(1, 0);
	EXPECT_FALSE(pq.is_empty());
	EXPECT_EQ(1, pq.nitems());
}

int insertion_sort2(int64_t *items1, int64_t *items2, const int n) {
	if(!items1 || !items2 || n < 0) return 1;

	for(int64_t i = 1; i < n; i++) {
		int64_t curr1 = items1[i];
		int64_t curr2 = items2[i];
		int64_t j;
		for(j = i - 1; j >= 0 && curr1 < items1[j]; j--) {
			items1[j + 1] = items1[j];
			items2[j + 1] = items2[j];
		}
		items1[j + 1] = curr1;
		items2[j + 1] = curr2;
	}
	return 0;
}

void shuffle2(int64_t *vals1, int64_t *vals2, const int64_t n) {
	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) {
	shuffle2(vals, vals, n);
}

TEST(spq_tests, delete_min_test) {
	// const int seed = 2128507;
	// srand(seed);

	uint64_t n = 111;
	uint64_t maxcost = 1000;
	int diver = 20;

	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] = round((1.0 / diver * (rand() % diver)) * maxcost);
	}
	shuffle(pdists, n);

	// std::cout << "Nodes (sorted by name):";
	// for(int64_t i = 0; i < n; ++i)
	//     std::cout << " (" << pnames[i] << ',' << pdists[i] << ')';
	// std::cout << '\n';

	insertion_sort2(pdists, pnames, n);

	// std::cout << "Nodes (sorted by dist):";
	// for(int64_t i = 0; i < n; ++i)
	// 	std::cout << " (" << pnames[i] << ',' << pdists[i] << ')';
	// std::cout << '\n';

	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);

	// std::cout << "Feeding nodes to priority queue.\n";
	spq_t pq(n, maxcost);
	for(int64_t i = 0; i < n; ++i)
		pq.insert(pnames_wc[i], pdists_wc[i]);
	// pq.dump();

	// std::cout << "Dequeueing:";
	int64_t nnodes_remaining = n;
	while(!pq.is_empty()) {
		uint64_t actual_dmin = -1;
		node_t *pnode = pq.delete_mins(actual_dmin);
		// pq.dump();
		int64_t ndel = 0; for(node_t *cursor = pnode; cursor; cursor = cursor->_pnext) ++ndel;
		nnodes_remaining = n - ndel;

		// for(node_t *cursor = pnode; cursor; cursor = cursor->_pnext)
		//	std::cout << " (" << cursor->_name << ',' << actual_dmin << ')';
		
		// ASSERT_EQ(nnodes_remaining, pq.nitems()) << "Invalid pq size after delete-min.";
		// int64_t expected_dmin = pdists[i];
		// int64_t expected_name = pnames[i];
		// ASSERT_EQ(expected_name, actual_name) << "Invalid node extracted on delete-min.";
		// ASSERT_EQ(expected_dmin, actual_dmin) << "Invalid d extracted on delete-min.";
	}
	std::cout << '\n';
	ASSERT_TRUE(pq.is_empty()) << "pq should be empty after all elements are deleted.";

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

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

