#include "ZUnitTest.hpp"

#include <ZSTL/ZArray.hpp>

#include <string.h>	//for memcmp

static const char* test_Constructors_Size_Capacity();
static const char* test_SliceConstructor();
static const char* test_At_Data();
static const char* test_Copy_Equals_Swap();
static const char* test_Allocator();
static const char* test_Reserve_Resize_Clear_Empty();
static const char* test_Push_Pop();
static const char* test_Insert_Erase();
static const char* test_Begin_End();
static const char* test_Find_Iterator();

//List of unit tests
ZUnitTest ZArrayUnitTests[] =
{
	{ "ZArray: Constructors, Size, Capacity",	test_Constructors_Size_Capacity },
	{ "ZArray: Slice Constructor",				test_SliceConstructor },
	{ "ZArray: At, Data",						test_At_Data },
	{ "ZArray: Copy, Equals, Swap",				test_Copy_Equals_Swap },
	{ "ZArray: Allocator",						test_Allocator },
	{ "ZArray: Reserve, Resize, Clear, Empty",	test_Reserve_Resize_Clear_Empty }, 
	{ "ZArray: Push, Pop",						test_Push_Pop },
	{ "ZArray: Insert, Erase",					test_Insert_Erase },
	{ "ZArray: Begin, End",						test_Begin_End },
	{ "ZArray: Iterator",						test_Find_Iterator }
};

DECLARE_ZTESTBLOCK(ZArray);

/*************************************************************************/
namespace ZArrayTestObjects
{
	struct TestObject
	{
		//Constructor
		TestObject() { TestObject::Instances++; }

		//Destructor
		~TestObject() { TestObject::Instances--; }

		//Static Instance Count
		static size_t Instances;
	};

	//Defaults to zero
	size_t TestObject::Instances = 0;

	//We use this allocator to ensure that allocator-agnostic functions work correctly
	class IntArrayAllocator
	{
	public:
		int* Allocate(size_t _size)
		{
			return new (std::nothrow) int[_size];
		}

		void Deallocate(int* _ptr, size_t _size) 
		{
			(void)_size;
			delete[] _ptr;
		}
	};
}

static const char* test_Constructors_Size_Capacity()
{
	int testData[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	//First we test our basic constructors with default allocator
	ZArray<int> arr1;
	ZArray<int> arr2(5);
	ZArray<int> arr3(testData, 6, 10);

	TASSERT(arr1.Capacity() == ZARRAY_DEFAULT_CAPACITY, "Capacity() returns wrong value after default constructor!");
	TASSERT(arr1.Size() == 0, "Size() returns wrong value after default constructor!");

	TASSERT(arr2.Capacity() == 5, "Capacity() returns wrong value after constructing with explicit capacity");
	TASSERT(arr2.Size() == 0, "Size() returns wrong value after constructing with explicit capacity!");

	TASSERT(arr3.Capacity() == 10, "Capacity() returns wrong value after data constructor!");
	TASSERT(arr3.Size() == 6, "Size() returns wrong value after data constructor!");

	//Test our copy constructor with typename A == typename B
	ZArray<int> arr4(arr3);

	TASSERT(arr3.Capacity() == 10, "arr3 modified after copy constructor! (Capacity)");
	TASSERT(arr3.Size() == 6, "arr3 modified after copy constructor! (Size)");

	TASSERT(arr4.Capacity() == 10, "Capacity() returns wrong value after copy constructor!");
	TASSERT(arr4.Size() == 6, "Size() returns wrong value after copy constructor!");

	//Test our basic constructors with provided allocator
	ZArray<int, ZArrayTestObjects::IntArrayAllocator> arr5;
	ZArray<int, ZArrayTestObjects::IntArrayAllocator> arr6(5);
	ZArray<int, ZArrayTestObjects::IntArrayAllocator> arr7(testData, 6, 10);

	TASSERT(arr5.Capacity() == ZARRAY_DEFAULT_CAPACITY, "Capacity() returns wrong value after default constructor! (IntArrayAllocator)");
	TASSERT(arr5.Size() == 0, "Size() returns wrong value after default constructor! (IntArrayAllocator)");

	TASSERT(arr6.Capacity() == 5, "Capacity() returns wrong value after constructing with explicit capacity! (IntArrayAllocator)");
	TASSERT(arr6.Size() == 0, "Size() returns wrong value after constructing with explicit capacity! (IntArrayAllocator)");

	TASSERT(arr7.Capacity() == 10, "Capacity() returns wrong value after data constructor! (IntArrayAllocator)");
	TASSERT(arr7.Size() == 6, "Size() returns wrong value after data constructor! (IntArrayAllocator)");

	//Test our copy constructor with typename A != typename B
	ZArray<int> arr8(arr7);

	TASSERT(arr7.Capacity() == 10, "arr7 modified after copy constructor! (Capacity)");
	TASSERT(arr7.Size() == 6, "arr7 modified after copy constructor! (Size)");

	TASSERT(arr8.Capacity() == 10, "Capacity() returns wrong value after copy constructor! (IntArrayAllocator)");
	TASSERT(arr8.Size() == 6, "Size() returns wrong value after copy constructor! (IntArrayAllocator)");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_SliceConstructor()
{
	ZArray<int> list;

	list.PushBack(1);
	list.PushBack(2);
	list.PushBack(3);
	list.PushBack(4);
	list.PushBack(5);
	list.PushBack(6);

	ZArray<int> slice(list, 2, 5);

	TASSERT(slice.Size() == 3, "Slice() created slice of incorrect size!");
	TASSERT(slice.PopFront() == 3, "Slice() created incorrect slice!");
	TASSERT(slice.PopFront() == 4, "Slice() created incorrect slice!");
	TASSERT(slice.PopFront() == 5, "Slice() created incorrect slice!");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_At_Data()
{
	int testData[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	ZArray<int> arr1;
	ZArray<int> arr2(5);
	ZArray<int> arr3(testData, 6, 10);

	//Ensure that 'At' returns proper data
	TASSERT(arr3.At(0) == testData[0], "arr3[0] != testData[0]");
	TASSERT(arr3.At(1) == testData[1], "arr3[1] != testData[1]");
	TASSERT(arr3.At(2) == testData[2], "arr3[2] != testData[2]");
	TASSERT(arr3.At(3) == testData[3], "arr3[3] != testData[3]");
	TASSERT(arr3.At(4) == testData[4], "arr3[4] != testData[4]");
	TASSERT(arr3.At(5) == testData[5], "arr3[5] != testData[5]");

	//Ensure that Data() != testData (this would be bad)
	TASSERT(arr3.Data() != testData, "Data constructor failed to copy data, instead took ownership of array!");

	//Ensure that 'At(i)' returns the same as 'Data()[i]'
	TASSERT(arr3.At(0) == arr3.Data()[0], "arr3[0] != arr3.Data()[0]");
	TASSERT(arr3.At(1) == arr3.Data()[1], "arr3[1] != arr3.Data()[1]");
	TASSERT(arr3.At(2) == arr3.Data()[2], "arr3[2] != arr3.Data()[2]");
	TASSERT(arr3.At(3) == arr3.Data()[3], "arr3[3] != arr3.Data()[3]");
	TASSERT(arr3.At(4) == arr3.Data()[4], "arr3[4] != arr3.Data()[4]");
	TASSERT(arr3.At(5) == arr3.Data()[5], "arr3[5] != arr3.Data()[5]");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_Copy_Equals_Swap()
{
	int testData[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	ZArray<int> arr1;
	ZArray<int> arr2(5);
	ZArray<int> arr3(testData, 6, 10);

	arr2.Copy(arr3);

	TASSERT(arr3.At(0) == arr2.At(0), "arr3.At(0) != arr2.At(0) after Copy()!");
	TASSERT(arr3.At(1) == arr2.At(1), "arr3.At(1) != arr2.At(1) after Copy()!");
	TASSERT(arr3.At(2) == arr2.At(2), "arr3.At(2) != arr2.At(2) after Copy()!");
	TASSERT(arr3.At(3) == arr2.At(3), "arr3.At(3) != arr2.At(3) after Copy()!");
	TASSERT(arr3.At(4) == arr2.At(4), "arr3.At(4) != arr2.At(4) after Copy()!");
	TASSERT(arr3.At(5) == arr2.At(5), "arr3.At(5) != arr2.At(5) after Copy()!");

	TASSERT(arr2.Equals(arr3), "arr2.Equals(arr3) returns false after Copy()!")
	TASSERT(arr3.Equals(arr2), "arr3.Equals(arr2) returns false after Copy()!")

	arr1.Swap(arr3);

	TASSERT(arr2.Equals(arr1), "arr2.Equals(arr2) returns false after Swap()!");
	TASSERT(!arr3.Equals(arr1), "arr3.Equals(arr1) returns true after Swap()!");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

class PooledIntArrayAllocator
{
public:
	static size_t GlobalArray[256];
	static size_t GlobalTracker;

	size_t CurrentSize;

	size_t* Allocate(size_t _size)
	{
		GlobalTracker = GlobalTracker + _size;
		CurrentSize = _size;
		return &GlobalArray[GlobalTracker - _size];
	}
	
	void Deallocate(size_t* _ptr, size_t _size) 
	{
		(void)_ptr;
		(void)_size;
	}
};

size_t PooledIntArrayAllocator::GlobalTracker = 0;
size_t PooledIntArrayAllocator::GlobalArray[256];

static const char* test_Allocator()
{
	size_t testData[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	ZArray<size_t> arr1;
	ZArray<size_t> arr2(5);
	ZArray<size_t> arr3(testData, 6, 10);

	ZArray<size_t, PooledIntArrayAllocator> arr4(testData, 10, 10);
	ZArray<size_t, PooledIntArrayAllocator> arr5(testData, 5, 5);

	TASSERT(arr4.Allocator().CurrentSize != arr5.Allocator().CurrentSize, 
		"Allocator returns invalid allocator instances!");

	for (int i = 0; i < 10; i++)
	{
		TASSERT(memcmp(arr4.Allocator().GlobalArray, arr5.Allocator().GlobalArray, 256 * sizeof(size_t)) == 0, 
				"arr4 and arr5 do not share static global data with pooled allocator!");
	}
	
	return ZTEST_SUCCESS;
}

/*************************************************************************/

using namespace ZArrayTestObjects;

static const char* test_Reserve_Resize_Clear_Empty()
{
	int testData[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	ZArray<int> arr1;
	ZArray<int> arr2(5);
	ZArray<int> arr3(testData, 6, 10);

	arr3.Reserve(arr3.Size());

	TASSERT(arr3.Capacity() != arr3.Size(), "Reserve(Size()) improperly reduced capacity!");

	arr3.Reserve(arr3.Size(), true);

	TASSERT(arr3.Capacity() == arr3.Size(), "Reserve(Size(), true) did not reduce capacity to proper value!");

	arr3.Reserve(10);

	TASSERT(arr3.Capacity() == 10, "Reserve(10) did not reserve enough capacity!");
	TASSERT(arr3.Size() == 6, "Reserve(10) modified the size!");
	
	for (size_t i = 0; i < arr3.Size(); i++)
	{
		TASSERT(arr3[i] == (int)i, "Reserve(10) modified elements!");
	}

	arr3.Resize(arr3.Capacity());

	TASSERT(arr3.Capacity() == 10, "Resize(Capacity()) improperly modified capacity!");
	TASSERT(arr3.Size() == 10, "Resize(Capacity() did not grow arr3 to the correct size!");

	for (int i = 0; i < 6; i++)
	{
		TASSERT(arr3[i] == i, "Resize(Capacity() improperly modifed existing elements!");
	}

	arr3.Resize(6);

	TASSERT(arr3.Capacity() == 10, "Resize(6) with _size < Capacity() improperly modified capacity!");
	TASSERT(arr3.Size() == 6, "Resize(6) did not shrink arr3 to the correct size!");

	for (size_t i = 0; i < arr3.Size(); i++)
	{
		TASSERT(arr3[i] == (int)i, "Resize(6) improperly modified existing elements!");
	}

	arr3.Resize(arr3.Capacity(), -1);

	TASSERT(arr3.Capacity() == 10, "Resize(Capacity(), -1) improperly modified capacity!");
	TASSERT(arr3.Size() == 10, "Resize(Capacity(), -1) did not grow arr3 to the correct size!");

	for (int i = 0; i < 6; i++)
	{
		TASSERT(arr3[i] == i, "Resize(Capacity(), -1) improperly modified existing elements!");
	}

	for (size_t i = 6; i < arr3.Size(); i++)
	{
		TASSERT(arr3[i] == -1, "Resize(Capacity(), -1) improperly set new values!");
	}

	arr3.Resize(32, -1);

	TASSERT(arr3.Capacity() == 32, "Resize(32, -1) did not grow capacity!");
	TASSERT(arr3.Size() == 32, "Resize(32, -1) did not grow arr3 to the correct size!");
	TASSERT(!arr3.Empty(), "Empty() did not return false when Size() == 32!");

	arr3.Clear();

	TASSERT(arr3.Capacity() == 32, "Clear() improperly reduced capacity!");
	TASSERT(arr3.Size() == 0, "Clear() did not correctly reduce size!");
	TASSERT(arr3.Empty(), "Empty() did not return true when Size() == 0!");

	arr3.Clear(arr3.Capacity());

	TASSERT(arr3.Capacity() == 32, "Clear(Capacity()) improperly reduced capacity!");
	TASSERT(arr3.Empty(), "Empty() did not return true when Size() == 0!");

	arr3.Clear(256);

	TASSERT(arr3.Capacity() == 256, "Clear(256) did not correctly grow capacity!");
	TASSERT(arr3.Empty(), "Empty() did not return true when Size() == 0!");

	/* Testing memory allocator guarantees */
	{
		ZArray<TestObject> arr4;

		TASSERT(TestObject::Instances == ZARRAY_DEFAULT_ALLOCATOR_STORAGE, "TestObject array constructed invalid number of instances with default constructor!");
	}
		
	TASSERT(TestObject::Instances == 0, "TestObject array failed to deallocate instances when out of scope!");	

	ZArray<TestObject, ZArrayAllocator<ZArrayTestObjects::TestObject, 0> > arr5(0);
	
	TASSERT(TestObject::Instances == 0, "TestObject array constructed instances with zero capacity constructor!");

	arr5.Reserve(5);

	TASSERT(TestObject::Instances == 5, "TestObject array constructed invalid number of instances after Reserve(5)!");

	arr5.Reserve(2);

	TASSERT(TestObject::Instances == 5, "TestObject array destructed instances when _reallocate was false!");

	arr5.Reserve(2, true);

	TASSERT(TestObject::Instances == 2, "TestObject array failed to deallocate instances when _reallocate was true!");

	arr5.Reserve(0);

	TASSERT(TestObject::Instances == 0, "TestObject array failed to deallocate all instances when Reserve(0) was called!");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_Push_Pop()
{
	int testData[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	ZArray<int> arr1;
	ZArray<int> arr2(5);
	ZArray<int> arr3(testData, 6, 10);

	int val;

	val = arr3.PopBack();

	TASSERT(val == 5, "Pop() did not return correct value!");
	TASSERT(arr3.Size() == 5, "Pop() did not correctly reduce size!");
	TASSERT(arr3.Capacity() == 10, "Pop() incorrectly modified capacity!");

	arr3.PushBack(10);

	TASSERT(arr3[arr3.Size() - 1] == 10, "PushBack(10) did not push correct value!");
	TASSERT(arr3[arr3.Size() - 2] == 4, "PushBack(10) incorrectly modified earlier elements!");
	TASSERT(arr3.Size() == 6, "PushBack(10) did not correctly increase size!");
	TASSERT(arr3.Capacity() == 10, "Pop() incorrectly modified capacity!");

	arr3.PushBack(10);
	arr3.PushBack(10);
	arr3.PushBack(10);
	arr3.PushBack(10);

	arr3.PushBack(15); //This one should trigger a capacity increase

	TASSERT(arr3.Size() == 11, "PushBack(...) did not correctly increase size!");
	TASSERT(arr3.Capacity() > 11, "PushBack(...) with Size() == Capacity() did not increase capacity!");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_Insert_Erase()
{
	int testData[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	ZArray<int> arr1;
	ZArray<int> arr2(5);
	ZArray<int> arr3(testData, 6, 10);

	//a1 = [1,2,3,4]
	arr1.PushBack(1);
	arr1.PushBack(2);
	arr1.PushBack(3);
	arr1.PushBack(4);

	//a1 = [0xFF, 1, 2, 3, 4]
	arr1.Insert(0, 0xFF);

	TASSERT(arr1[0] == 0xFF, "Insert(0, 0xFF) did not set element [0] to 0xFF");
	TASSERT(arr1.Size() == 5, "Insert(0, 0xFF) did not set the size correctly");

	for(size_t i = 1; i < arr1.Size(); i++)
	{
		//Elements [1] .. [Size()-1] should be unmodified.
		TASSERT(arr1[i] == (int)i, "Insert(0, 0xFF) did not preserve remaining elements' values!");
	}

	int test = arr1.Erase(0);

	TASSERT(test == 0xFF, "Erase(0) returned the wrong value!");
	TASSERT(arr1.Size() == 4, "Erase(0) did not correctly set the size!");

	for(size_t i = 0; i < arr1.Size(); i++)
	{
		//Now elements [0..3] should be the values {1, 2, 3, 4} again.
		TASSERT(arr1[i] == (int)(i+1), "Erase(0) did not preserve remaining elements' values");
	}

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_Begin_End()
{
	int testData[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	ZArray<int> arr1;
	ZArray<int> arr2(5);
	ZArray<int> arr3(testData, 6, 10);

	ZArray<int>::Iterator itr1_1 = arr1.Begin();
	ZArray<int>::Iterator itr1_2 = arr1.End();

	TASSERT(itr1_1 == itr1_2, "Empty array does not return Begin() == End()!");

	ZArray<int>::Iterator itr3_1 = arr3.Begin();
	ZArray<int>::Iterator itr3_2 = arr3.End();

	TASSERT(itr3_1 != itr3_2, "Non-empty array returns Begin() == End()!");

	return ZTEST_SUCCESS;
}

/*************************************************************************/

static const char* test_Find_Iterator()
{
	int testData[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	ZArray<int> arr1;
	ZArray<int> arr2(5);
	ZArray<int> arr3(testData, 6, 10);

	ZArray<int>::Iterator itr1_1 = arr1.Begin();

	ZArray<int>::Iterator itr3_1 = arr3.Begin();
	ZArray<int>::Iterator itr3_2 = arr3.End();

	TASSERT(itr1_1.HasNext() == false, "Iterator HasNext() returns true on empty array iterator!");
	TASSERT(itr1_1.HasPrev() == false, "Iterator HasPrev() returns true on empty array iterator!");
	TASSERT(itr1_1.HasCurrent() == false, "Iterator HasCurrent() returns false on empty array iterator!");

	TASSERT(itr3_1.HasNext() == true, "Iterator HasNext() returns false when elements remain!");
	TASSERT(itr3_1.HasPrev() == false, "Iterator HasPrev() returns true on Begin() Iterator!");
	TASSERT(itr3_1.HasCurrent() == true, "Iterator HasCurrent() returns false on Begin() Iterator!");

	TASSERT(itr1_1 >= arr1.Begin(), "Iterator >= did not return true when equal to Begin()!");
	TASSERT(itr1_1 <= arr1.Begin(), "Iterator <= did not return true when equal to Begin()!");
	TASSERT(itr3_1 <= arr3.End(), "Iterator <= did not return true when less than End()!");

	size_t i = 0;

	TASSERT(itr3_1.HasCurrent() == true, "Iterator HasCurrent() does not return true when elements exist!");
	TASSERT(itr3_1.Get() == arr3[i], "Iterator Get() does not return value at beginning of array with Begin() Iterator!");
	TASSERT(*itr3_1 == arr3[i], "Iterator operator * does not return value at beginning of array with Begin() Iterator!");
	TASSERT(itr3_1 + 1 == (size_t)arr3[i + 1], "Iterator + operator does not return correct iterator!");

	itr3_1 += 1;

	TASSERT(*itr3_1 == arr3[i + 1], "Iterator += operator did not correctly update iterator!");
	TASSERT((size_t)*itr3_1 > arr3.Begin(), "Iterator > operator did not return true when compared to Begin()!");

	for (i = 1; itr3_1 != itr3_2 && i < arr3.Size(); ++i, ++itr3_1)
	{
		TASSERT(itr3_1 != itr3_2, "Iterator != returns true when iterators not equal!");
		TASSERT(itr3_1 == (size_t)i, "Iterator does not cast correctly to size_t!");
		TASSERT(itr3_1.HasCurrent() == true, "Iterator HasCurrent() does not return true when elements exist!");
		TASSERT(itr3_1.HasPrev() == true, "Iterator HasPrev() returns false when previous elements exist!");
		TASSERT(itr3_1.Get() == arr3[i], "Iterator Get() does not return correct value!");
		TASSERT(*itr3_1 == arr3[i], "Iterator operator * does not return correct value!");
		TASSERT(itr3_1 - 1 == (size_t)arr3[i - 1], "Iterator - operator does not return correct iterator!");
	}

	TASSERT(itr3_1 == itr3_2, "Iterator did not iterate to end!");

	itr3_1 -= 1;

	TASSERT(itr3_1 != itr3_2, "Iterator operator -= did not correctly update iterator!");

	for (i = itr3_1; itr3_1 != arr3.Begin() && i < arr3.Size(); --i, --itr3_1)
	{
		TASSERT(itr3_1 != itr3_2, "Iterator != returns true when iterators not equal!");
	}

	TASSERT(itr3_1 == arr3.Begin(), "Iterator did not iterate to beginning!");

	itr3_1 = itr3_2;

	TASSERT(itr3_1 == itr3_2, "Iterator operator = did not correctly assign iterator!");

	return ZTEST_SUCCESS;
}
