#include "ZUnitTest.hpp"

#include <stdlib.h>			//for NULL
#include <string.h>			//strcmp
#include <stdio.h>			//for printf

#include <ZUtil/ZAlloc.hpp>	//for ZENGINE_MEM_DUMP_LEAKS
#include <SST/SST_OS.h>
extern ZUnitTestBlock SST_SafeArithmeticTests;
extern ZUnitTestBlock SST_AtomicTests;
extern ZUnitTestBlock SST_EndianTests;
extern ZUnitTestBlock SST_FileSysTests;

extern ZUnitTestBlock ZArrayTests;
extern ZUnitTestBlock ZArrayAlgoTests;
extern ZUnitTestBlock ZListTests;
extern ZUnitTestBlock ZListAlgoTests;
extern ZUnitTestBlock ZRingBufferTests;
extern ZUnitTestBlock ZBasicStringTests;
extern ZUnitTestBlock ZBasicStringAlgoTests;
extern ZUnitTestBlock ZHashMapTests;

extern ZUnitTestBlock ZAssertTests;
extern ZUnitTestBlock ZAllocTests;
extern ZUnitTestBlock ZConcurrencyTests;

extern ZUnitTestBlock ZNameTests;
extern ZUnitTestBlock ZReferenceCounterTests;
extern ZUnitTestBlock ZSmartPointerTests;
extern ZUnitTestBlock ZWeakPointerTests;
extern ZUnitTestBlock ZSlabAllocatorTests;

extern ZUnitTestBlock ZRandomGeneratorTests;
extern ZUnitTestBlock ZSimplexNoiseTests;
extern ZUnitTestBlock ZTaskStreamTests;

extern ZUnitTestBlock ZKVTreeTests;
extern ZUnitTestBlock ZIniReaderTests;
extern ZUnitTestBlock ZIniWriterTests;
extern ZUnitTestBlock ZJSONReaderTests;
extern ZUnitTestBlock ZJSONWriterTests;
extern ZUnitTestBlock ZXMLReaderTests;
extern ZUnitTestBlock ZXMLWriterTests;
extern ZUnitTestBlock ZRegistryTests;
extern ZUnitTestBlock ZAllocWindowTests;

extern ZUnitTestBlock SST_Math_Mat22fTests;
extern ZUnitTestBlock SST_Math_Mat22dTests;
extern ZUnitTestBlock SST_Math_Mat22iTests;
extern ZUnitTestBlock SST_Math_Mat22uTests;
extern ZUnitTestBlock SST_Math_Mat33fTests;
extern ZUnitTestBlock SST_Math_Mat33dTests;
extern ZUnitTestBlock SST_Math_Mat33iTests;
extern ZUnitTestBlock SST_Math_Mat33uTests;
extern ZUnitTestBlock SST_Math_Mat44fTests;
extern ZUnitTestBlock SST_Math_Mat44dTests;
extern ZUnitTestBlock SST_Math_Mat44iTests;
extern ZUnitTestBlock SST_Math_Mat44uTests;

extern ZUnitTestBlock SST_Math_Vec2fTests;
extern ZUnitTestBlock SST_Math_Vec2dTests;
extern ZUnitTestBlock SST_Math_Vec2iTests;
extern ZUnitTestBlock SST_Math_Vec2uTests;
extern ZUnitTestBlock SST_Math_Vec3fTests;
extern ZUnitTestBlock SST_Math_Vec3dTests;
extern ZUnitTestBlock SST_Math_Vec3iTests;
extern ZUnitTestBlock SST_Math_Vec3uTests;
extern ZUnitTestBlock SST_Math_Vec4fTests;
extern ZUnitTestBlock SST_Math_Vec4dTests;
extern ZUnitTestBlock SST_Math_Vec4iTests;
extern ZUnitTestBlock SST_Math_Vec4uTests;
extern ZUnitTestBlock SST_Math_TransformTests;
extern ZUnitTestBlock SST_Math_GeoTests;

static ZUnitTestBlock* g_tests [] =
{
	&SST_SafeArithmeticTests,
	&SST_AtomicTests,
	&SST_EndianTests,
	&SST_FileSysTests,

	&SST_Math_Mat22fTests,
	&SST_Math_Mat22dTests,
	&SST_Math_Mat22iTests,
	&SST_Math_Mat22uTests,
	&SST_Math_Mat33fTests,
	&SST_Math_Mat33dTests,
	&SST_Math_Mat33iTests,
	&SST_Math_Mat33uTests,
	&SST_Math_Mat44fTests,
	&SST_Math_Mat44dTests,
	&SST_Math_Mat44iTests,
	&SST_Math_Mat44uTests,
	
	&SST_Math_Vec2fTests,
	&SST_Math_Vec2dTests,
	&SST_Math_Vec2iTests,
	&SST_Math_Vec2uTests,
	&SST_Math_Vec3fTests,
	&SST_Math_Vec3dTests,
	&SST_Math_Vec3iTests,
	&SST_Math_Vec3uTests,
	&SST_Math_Vec4fTests,
	&SST_Math_Vec4dTests,
	&SST_Math_Vec4iTests,
	&SST_Math_Vec4uTests,
	&SST_Math_TransformTests,
	&SST_Math_GeoTests,

	&ZArrayTests,
	&ZArrayAlgoTests,
	&ZListTests,
	&ZListAlgoTests,
	&ZRingBufferTests,
	&ZBasicStringTests,
	&ZBasicStringAlgoTests,
	&ZHashMapTests,

	&ZAssertTests,
	&ZAllocTests,
	&ZConcurrencyTests,

	&ZNameTests,
	&ZReferenceCounterTests,
	&ZSmartPointerTests,
	&ZWeakPointerTests,
	&ZSlabAllocatorTests,

	&ZRandomGeneratorTests,
	&ZSimplexNoiseTests,
	&ZTaskStreamTests,

	&ZKVTreeTests,
	&ZIniReaderTests,
	&ZIniWriterTests,
	&ZJSONReaderTests,
	&ZJSONWriterTests,
	&ZXMLReaderTests,
	&ZXMLWriterTests,
	&ZRegistryTests,
	&ZAllocWindowTests,

};

static size_t g_numtests = sizeof(g_tests)/sizeof(g_tests[0]);

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

const char* g_assert_message = NULL;
bool g_assert_triggered = false;

int ZTEST_HandleAssert(const char* _msg, void* _arg)
{
	(void)_arg;

	g_assert_triggered = true;
	g_assert_message = _msg;

	return 1;
}

void ZTEST_BeginHandleAssert()
{
	SST_OS_SetRuntimeAssertHandler(ZTEST_HandleAssert, NULL);
}

bool ZTEST_CheckAssert()
{
	bool result = g_assert_triggered;

	g_assert_triggered = false;

	return result;
}

void ZTEST_EndHandleAssert()
{
	SST_OS_SetRuntimeAssertHandler(NULL, NULL);
}

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

static bool doFullTestRun()
{
	bool success = true;
	for (size_t i = 0; i < g_numtests; i++)
		success &= RunUnitTests(g_tests[i]);
	return success;
}

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

int main(int argc, char **argv)
{
	//Make sure we set these values
	ZUnitTestGlobals::ARGC = argc;
	ZUnitTestGlobals::ARGV = argv;

	if(SST_OS_Init() == 0)
	{
		printf("Failed to initialize OS library, exiting.\n");
		exit(0xff);
	}

	//Check our input arguments for test conditions
	for(size_t i=1; i<(size_t)argc; i++)
	{
		if(!strcmp(argv[i], "-n"))
			ZUnitTestGlobals::NonInteractive = 1;
		else if(!strcmp(argv[i], "-c"))
		{
			printf("%lu\n", g_numtests);
			exit(0);
		}
		else if(!strcmp(argv[i], "-r")) //Run a specific test
		{
			if(i == (size_t)argc-1)
			{
				printf("Invalid usage of -r");
				exit(1);
			}
			
			ZUnitTestGlobals::TestNumber = atoi(argv[i+1]);

			if(ZUnitTestGlobals::TestNumber >= (int)g_numtests)
			{
				printf("Invalid test number \"%s\"\n",
					argv[i+1]);
				exit(1);
			}
			ZUnitTestGlobals::NonInteractive = 1;

			// Skip already parsed arg.
			i++;	
		}
		else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help"))
		{
			printf(
			"-h     : help (this screen)\n"
			"-n     : non-interactive (don't wait for keys)\n"
			"-r <N> : run test number <N>"
			"-c     : print number of tests available\n");
			exit(0);	
		}
		else
		{
			printf("ERROR: Option \"%s\" not recognized", argv[1]);
			exit(1);
		}
	}

	if(ZUnitTestGlobals::TestNumber >= 0)
	{
		return (RunUnitTests(g_tests[ZUnitTestGlobals::TestNumber]) ? 0 : 1);	
	}

	bool success = true;
	if (ZUnitTestGlobals::NonInteractive == 1)
		success = doFullTestRun();
	else
	{
		char inputbuff[512];
		for(;;)
		{
			printf("===============\nAVAILABLE TESTS\n===============\n");
			for (size_t i = 0; i < g_numtests; i++)
				printf("%lu. %s\n",i+1,g_tests[i]->blockName);
			printf("(q to quit)\n");
			printf("(0 for all tests)\n");
			memset(inputbuff,0,512);
			fgets(inputbuff,512,stdin);
			inputbuff[511] = '\0';

			if (inputbuff[0]=='q')
				break;

			int selection = atoi(inputbuff);
			if (selection < 0 || selection > (int)g_numtests)
				continue;
			else if (selection == 0)
			{
				success &= doFullTestRun();
				printf("\nPress ENTER to continue...\n");
				fgetc(stdin);
			}
			else
			{
				success &= RunUnitTests(g_tests[selection-1]);
				printf("\nPress ENTER to continue...\n");
				fgetc(stdin);
			}
		}
	}

	/***********************/
	/* End Unit Tests Here */
	/***********************/

	if (success)
		printf("**************\nALL TESTS SUCCESSFUL!\n**************\n\n");
	else
		printf("**************\nWARNING! SOME TESTS FAILED!\n**************\n\n");

	ZALLOC_MEM_DUMP_LEAKS(NULL);

	if (g_assert_triggered)
		printf("WARNING: Unit tests completed with assert flag tripped!\n\nMESSAGE: %s\n\n", g_assert_message);

	if(!ZUnitTestGlobals::NonInteractive)
	{
		printf("\nPress ENTER to continue...\n");
		fflush(stdout);
		fgetc(stdin);
	}

	if (success)
		exit(0);
	else
		exit(1);
}
