#include "ZUnitTest.hpp"

//Hijack the assert
#define ZSTL_ASSERT(condition, message) SST_OS_RuntimeAssert(condition, message)

#include <ZSTL/ZBasicString.hpp>
#include <SST/SST_Hash.h>

static const char* test_Constructors();
static const char* test_AbsoluteIndex();
static const char* test_Allocator();
static const char* test_Array();
static const char* test_At();
static const char* test_Back();
static const char* test_Capacity();
static const char* test_Clear();
static const char* test_Clear_With_Capacity();
static const char* test_Compare_To_Array();
static const char* test_Compare_To_ZString();
static const char* test_Copy();
static const char* test_Data();
static const char* test_Empty();
static const char* test_Equals_To_Array();
static const char* test_Equals_To_ZString();
static const char* test_Erase();
static const char* test_Erase_Substring();
static const char* test_Front();
static const char* test_Hash();
static const char* test_Insert();
static const char* test_Length();
static const char* test_OperatorPlus();
static const char* test_PopBack();
static const char* test_PopFront();
static const char* test_PushBack();
static const char* test_PushFront();
static const char* test_Reserve();
static const char* test_Resize();
static const char* test_Resize_With_Fill();
static const char* test_Swap();

ZUnitTest ZBasicStringUnitTests[] =
{
	{ "ZBasicString: Constructors",							test_Constructors },
	{ "ZBasicString: AbsoluteIndex",						test_AbsoluteIndex },
	{ "ZBasicString: Allocator",							test_Allocator },
	{ "ZBasicString: Array",								test_Array },
	{ "ZBasicString: At",									test_At },
	{ "ZBasicString: Back",									test_Back },
	{ "ZBasicString: Capacity",								test_Capacity },
	{ "ZBasicString: Clear",								test_Clear },
	{ "ZBasicString: Clear with capacity",					test_Clear_With_Capacity },
	{ "ZBasicString: Compare to array",						test_Compare_To_Array },
	{ "ZBasicString: Compare to zstring",					test_Compare_To_ZString },
	{ "ZBasicString: Copy",									test_Copy },
	{ "ZBasicString: Data",									test_Data },
	{ "ZBasicString: Empty",								test_Empty },
	{ "ZBasicString: Equals to array",						test_Equals_To_Array },
	{ "ZBasicString: Equals to zstring",					test_Equals_To_ZString },
	{ "ZBasicString: Erase",								test_Erase },
	{ "ZBasicString: Erase substring",						test_Erase_Substring },
	{ "ZBasicString: Front",								test_Front },
	{ "ZBasicString: Hash",									test_Hash },
	{ "ZBasicString: Insert",								test_Insert },
	{ "ZBasicString: Length",								test_Length },
	{ "ZBasicString: OperatorPlus",							test_OperatorPlus },
	{ "ZBasicString: PopBack",								test_PopBack },
	{ "ZBasicString: PopFront",								test_PopFront },
	{ "ZBasicString: PushBack",								test_PushBack },
	{ "ZBasicString: PushFront",							test_PushFront },
	{ "ZBasicString: Reserve",								test_Reserve },
	{ "ZBasicString: Resize",								test_Resize },
	{ "ZBasicString: Resize with fill",						test_Resize_With_Fill },
	{ "ZBasicString: Swap",									test_Swap }
};

DECLARE_ZTESTBLOCK(ZBasicString);

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

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

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

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

static const char* test_Constructors()
{
	const char* testData1 = "SuperAwesomeString";
	const char* testData2 = "Super. Awesome. String.";

	ZBasicString<> string1;
	ZBasicString<> string2(testData1);
	ZBasicString<> string3(&testData1[5], 7);
	ZBasicString<> string4(string2);

	ZBasicString<CharArrayAllocator> string5(testData2);
	ZBasicString<> string6(string5);
	
	TASSERT( strcmp(string1.Data(), "") == 0 &&
			 string1.Empty() == true &&
			 string1.Length() == 0,
			 "Default string initialized with incorrect string!\n");

	TASSERT( strcmp(string2.Data(), testData1) == 0 &&
			 string2.Empty() == false &&
			 string2.Length() == 18,
			 "String initialized with incorrect string!\n");

	TASSERT( strcmp(string3.Data(), "Awesome") == 0 &&
			 string3.Empty() == false &&
			 string3.Length() == 7,
			 "Copy substring constructor initialized with incorrect string!\n");

	TASSERT( strcmp(string4.Data(), testData1) == 0 &&
			 string4.Empty() == false &&
			 string4.Length() == 18,
			 "Copy string constructor initialized with incorrect string!\n");

	TASSERT( strcmp(string5.Data(), testData2) == 0 &&
			 string5.Empty() == false &&
			 string5.Length() == 23,
			 "String constructor with custom allocator initialized with incorrect string!\n");

	TASSERT( strcmp(string6.Data(), testData2) == 0 &&
			 string6.Empty() == false &&
			 string6.Length() == 23,
			 "Copy string constructor with custom allocator initialized with incorrect string!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_AbsoluteIndex()
{
	ZBasicString<> string("1235");

	TASSERT( string.BoundsCheck(0, string.Length()) == 0, "AbsoluteIndex() mismaps 0!\n");
	TASSERT( string.BoundsCheck(1, string.Length()) == 1, "AbsoluteIndex() mismaps 1!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Allocator()
{
	// This is a pass-through for ZArray::Allocator(), so we don't need to do anything
	return ZTEST_SUCCESS;
}

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

static const char* test_Array()
{
	ZBasicString<> string("12345");
	
	TASSERT( string.Array().Empty() == false, "Array() gave back empty backing array!\n");
	TASSERT( string.Array().Back() == ZBASICSTRING_NULL_TERMINATOR, "Array() is not null terminated!\n");
	TASSERT( string.Array().Size() == 6, "Array() gave back missized array!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_At()
{
	ZBasicString<> string("12345");

	TASSERT( string.At(0) == '1', "At() returned incorrect character!\n");
	TASSERT( string.At(1) == '2', "At() returned incorrect character!\n");
	TASSERT( string.At(2) == '3', "At() returned incorrect character!\n");
	TASSERT( string.At(3) == '4', "At() returned incorrect character!\n");
	TASSERT( string.At(4) == '5', "At() returned incorrect character!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Back()
{
	ZBasicString<> string = ZBasicString<>("abcde");
	ZBasicString<> string2 = ZBasicString<>("1");
	ZBasicString<> string3 = ZBasicString<>();

	TASSERT(string.Back() == 'e', "Back() returned wrong front character for string length > 2");
	string.Back() = '!';
	TASSERT(string.Back() == '!', "Back() returned the correct reference");

	TASSERT(string2.Back() == '1', "Back() returned the wrong character for a string of length 1.");

//  Uncomment to test if Back() raises an assert on an empty string.
//	TASSERT(string3.Back() == '\0', "This should have asserted.");
	
	return ZTEST_SUCCESS;
}

/*************************************************************************/
static const char* test_Capacity()
{
	ZBasicString<ZArrayAllocator<char, 32> > string("12435");

	//NOTE: We can do this because (at the moment) constructing from a const char array
	//		and that will init the capacity to just enough to hold it.
	TASSERT( string.Capacity() == 5, "Capacity() returned incorrect capacity!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Clear()
{
	ZBasicString<> string("12435");

	TASSERT( string.Length() == 5, "String initialized with incorrect size!\n");
	string.Clear();
	TASSERT( string.Length() == 0, "Clear() did not set string length to 0!\n");
	TASSERT( string.Equals("") == true, "Clear() did not set string to empty string!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Clear_With_Capacity()
{
	ZBasicString<> string("12435");

	TASSERT( string.Length() == 5, "String initialized with incorrect size!\n");
	string.Clear(16);
	TASSERT( string.Length() == 0, "Clear() did not set string length to 0!\n");
	TASSERT( string.Capacity() == 16, "Clear() did not properly set new capacity of string!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Compare_To_Array()
{
	const char* str1 = "Hallo!";
	const char* str2 = "Hello!";
	const char* str3 = "Hullo!";
	const char* str4 = "Goodbye!";
	ZBasicString<> string("Hello!");

	TASSERT( string.Compare(str3) < 0, "Compare() misclassified lexigraphically larger string!\n");
	TASSERT( string.Compare(str2) == 0, "Compare() misclassified lexigraphically equal string!\n");
	TASSERT( string.Compare(str1) > 0, "Compare() misclassified lexigraphically smaller string!\n");
	TASSERT( string.Compare(str4) != 0, "Compare() misclassified different length different string!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Compare_To_ZString()
{
	ZBasicString<> str1("Hallo!");
	ZBasicString<> str2("Hello!");
	ZBasicString<> str3("Hullo!");
	ZBasicString<> str4("Goodbye!");
	ZBasicString<> str5("HELLO!");
	ZBasicString<> string("Hello!");

	TASSERT( string.Compare(str3) < 0, "Compare() misclassified lexigraphically larger string!");
	TASSERT( string.Compare(str2) == 0, "Compare() misclassified lexigraphically equal string!");
	TASSERT( string.Compare(str1) > 0, "Compare() misclassified lexigraphically smaller string!");
	TASSERT( string.Compare(str4) != 0, "Compare() misclassified different length different string!");
	TASSERT( string.Compare(str5) != 0, "Compare() is case-insensitive!");

	return ZTEST_SUCCESS;
}

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

static const char* test_Data()
{
	ZBasicString<> string("Hello!");

	TASSERT ( string.Data() != NULL, "Data() returned null pointer for valid string!\n");
	TASSERT ( strcmp(string.Data(),"Hello!") == 0, "Data() returned null pointer for valid string!\n");
	
	return ZTEST_SUCCESS;
}

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

static const char* test_Equals_To_Array()
{
	ZBasicString<> string("yes");
	const char* yes = "yes";
	const char* no = "no";

	TASSERT( string.Equals(no) == false, "Equals() misclassified different char array string!\n");
	TASSERT( string.Equals(yes) == true, "Equals() misclassified identical char array string!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Equals_To_ZString()
{
	ZBasicString<> str1("Hallo!");
	ZBasicString<> str2("Hello!");
	ZBasicString<> str3("Hello!");

	TASSERT( str3.Equals(str1) == false, "Equals() miscompared two different ZBasicStrings!\n");
	TASSERT( str3.Equals(str2) == true, "Equals() miscompared two equivalent ZBasicStrings!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Copy()
{
	ZBasicString<> emptyString("");
	ZBasicString<> notEmptyString("notEmpty");

	emptyString.Copy(notEmptyString);

	TASSERT( emptyString.Equals(notEmptyString), "Copy() failed to move data!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Empty()
{
	ZBasicString<> emptyString;
	ZBasicString<> emptyString2("");
	ZBasicString<> notEmptyString("notEmpty");

	TASSERT( emptyString.Empty() == true, "Empty() returned false on empty string from default constructor!\n");
	TASSERT( emptyString2.Empty() == true, "Empty() returned false on empty string from empty constructor!\n");
	TASSERT( notEmptyString.Empty() == false, "Empty() returned true on nonempty string!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Erase()
{
	ZBasicString<> string("abc");
	ZBasicString<> string2("abc");
	ZBasicString<> string3("abc");
	ZBasicString<> string4("abc");

	string.Erase(0);
	TASSERT(string.Equals("bc"), "Erase() failed to remove first character!\n");

	string2.Erase(2);
	TASSERT(string2.Equals("ab"), "Erase() failed to remove last character!\n");

	string3.Erase(1);
	TASSERT(string3.Equals("ac"), "Erase() failed to remove middle character!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Erase_Substring()
{
	ZBasicString<> string("abcde");
	ZBasicString<> string2("abcde");
	ZBasicString<> string3("abcde");
	ZBasicString<> string4("abcde");

	string.Erase(0,3);
	TASSERT(string.Equals("de") == true, "Erase() failed to remove first 3 characters!\n");
	
	string2.Erase(2,5);
	TASSERT(string2.Equals("ab") == true, "Erase() failed to remove last 3 characters!\n");

	string3.Erase(1,4);
	TASSERT(string3.Equals("ae") == true, "Erase() failed to remove middle 3 characters!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Front()
{
	ZBasicString<> string = ZBasicString<>("abcde");
	ZBasicString<> string2 = ZBasicString<>("1");
	ZBasicString<> string3 = ZBasicString<>();

	TASSERT(string.Front() == 'a', "Front() returned wrong front character for string length > 2");
	string.Front() = '!';
	TASSERT(string.Front() == '!', "Front() returned the correct reference)");

	TASSERT(string2.Front() == '1', "Front() returned the wrong character for a string of length 1.");
	//Uncomment to see if Front() asserts as advertised.
	//TASSERT(string3.Front() == '\0', "This should have asserted.");
	return ZTEST_SUCCESS;
}

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

static const char* test_Hash()
{
	ZBasicString<> string("hashMe");
	ZBasicString<> string2("hashMeToo");

	TASSERT( string.Hash() == SST_Crypto_HashJ6("hashMe",6), "Hash() returned wrong hash on string!\n");
	TASSERT( string.Hash() != string2.Hash(), "Hash() returned same hash on different strings!\n")

	return ZTEST_SUCCESS;
}

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

static const char* test_Insert()
{
	ZBasicString<> string1("hi");
	ZBasicString<> string2("hi");
	ZBasicString<> string3("hi");
	ZBasicString<> string4("hi");
	ZBasicString<> add("there");

	string1.Insert(0,add);
	TASSERT( string1.Equals("therehi"), "Insert() returned wrong string on insert front\n");

	string2.Insert(2,add);
	TASSERT( string2.Equals("hithere"), "Insert() returned wrong string on insert back\n");

	string4.Insert(1,add);
	TASSERT( string4.Equals("htherei"), "Insert() returned wrong string on insert middle\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Length()
{
	ZBasicString<> emptyString1("");
	ZBasicString<> emptyString2;
	ZBasicString<> mystring("12345");

	TASSERT( emptyString1.Length() == 0, "Length() reports nonempty on empty string!\n");
	TASSERT( emptyString2.Length() == 0, "Length() reports nonempty on default string!\n");
	TASSERT( mystring.Length() == 5, "Length() reports wrong length on string!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_OperatorPlus()
{
	/*
		ZBasicString<A> operator + (const char *_lhs, const ZBasicString<A>& _rhs)
		ZBasicString<A> operator + (const ZBasicString<A>& _lhs, const char *_rhs)
	*/
	ZBasicString<> string = ZBasicString<>("abcde");
	ZBasicString<> emptyString = ZBasicString<>();
	const char cString[5] = "1234";
	const char emptyCString[1] = "";

	TASSERT(string + cString == "abcde1234", "Right add of a c-string failed.");
	TASSERT(cString + string == "1234abcde", "Left add of a c-string failed");
	TASSERT(cString + emptyString == "1234", "Right add of empty ZBasicString failed");
	TASSERT(emptyString + cString == "1234", "Left add of empty ZBasicString failed");
	TASSERT(string + emptyCString == "abcde", "Right add of empty c-string failed.");
	TASSERT(emptyCString + string == "abcde", "Left add of empty c-string failed.");

	return ZTEST_SUCCESS;
}

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

static const char* test_PopBack()
{
	ZBasicString<> string("apple");
	char back;

	back = string.PopBack();
	TASSERT ( (back == 'e') && string.Equals("appl"), "PopBack() returned incorrect character!\n");

	back = string.PopBack();
	TASSERT ( (back == 'l') && string.Equals("app"), "PopBack() returned incorrect character!\n");

	back = string.PopBack();
	TASSERT ( (back == 'p') && string.Equals("ap"), "PopBack() returned incorrect character!\n");

	back = string.PopBack();
	TASSERT ( (back == 'p') && string.Equals("a"), "PopBack() returned incorrect character!\n");

	back = string.PopBack();
	TASSERT ( (back == 'a') && string.Empty(), "PopBack() returned incorrect character!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_PopFront()
{
	ZBasicString<> string("apple");
	char front;

	front = string.PopFront();
	TASSERT ( (front == 'a') && string.Equals("pple"), "PopFront() returned incorrect character!\n");

	front = string.PopFront();
	TASSERT ( (front == 'p') && string.Equals("ple"), "PopFront() returned incorrect character!\n");

	front = string.PopFront();
	TASSERT ( (front == 'p') && string.Equals("le"), "PopFront() returned incorrect character!\n");

	front = string.PopFront();
	TASSERT ( (front == 'l') && string.Equals("e"), "PopFront() returned incorrect character!\n");

	front = string.PopFront();
	TASSERT ( (front == 'e') && string.Empty(), "PopFront() returned incorrect character!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_PushBack()
{
	ZBasicString<> string("");

	string.PushBack('a');
	TASSERT ( string.Equals("a"), "PushBack() returned broken string!\n");

	string.PushBack('p');
	TASSERT ( string.Equals("ap"), "PushBack() returned broken string!\n");

	string.PushBack('p');
	TASSERT ( string.Equals("app"), "PushBack() returned broken string!\n");

	string.PushBack('l');
	TASSERT ( string.Equals("appl"), "PushBack() returned broken string!\n");

	string.PushBack('e');
	TASSERT ( string.Equals("apple"), "PushBack() returned broken string!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_PushFront()
{
	ZBasicString<> string("");
	
	string.PushFront('e');
	TASSERT ( string.Equals("e"), "PushFront() returned broken string!\n");
	
	string.PushFront('l');
	TASSERT ( string.Equals("le"), "PushFront() returned broken string!\n");

	string.PushFront('p');
	TASSERT ( string.Equals("ple"), "PushFront() returned broken string!\n");
	
	string.PushFront('p');
	TASSERT ( string.Equals("pple"), "PushFront() returned broken string!\n");
	
	string.PushFront('a');
	TASSERT ( string.Equals("apple"), "PushFront() returned broken string!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Reserve()
{
	ZBasicString<> str("wat");

	str.Reserve(34);
	TASSERT( str.Capacity() == 34, "Reserve() failed to set capacity to correct value!\n");

	str.Reserve(43);
	TASSERT( str.Capacity() == 43, "Reserve() failed to set capacity to correct value!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Resize()
{
	ZBasicString<> str("wat");

	TASSERT( str.Length() == 3, "Size() of string returned wrong size!\n");

	str.Resize(2);

	TASSERT( str.Length() == 2, "Size(2) of string returned wrong size!\n");

	str.Resize(4);

	TASSERT( str.Length() == 4, "Size(4) of string returned wrong size!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Resize_With_Fill()
{
	ZBasicString<> str("");

	str.Resize(4,'a');

	TASSERT( str.Equals("aaaa"), "Resize() did not fill new string correctly!\n");

	str.Resize(2,'b');
	TASSERT( str.Equals("aa"), "Resize() did resize the string correctly!\n");

	return ZTEST_SUCCESS;
}

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

static const char* test_Swap()
{
	ZBasicString<> str("hi");
	ZBasicString<> str2("there");

	str.Swap(str2);

	TASSERT( str.Equals("there") == true, "Swap did not replace first string!\n");
	TASSERT( str2.Equals("hi") == true, "Swap did not replace second string!\n");

	str.Swap(str2);

	TASSERT( str.Equals("hi") == true, "Swap did not put back first string!\n");
	TASSERT( str2.Equals("there") == true, "Swap did not put back second string!\n");

	return ZTEST_SUCCESS;
}

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