//
//  main.c
//  BulletTest
//
//  Copyright (c) 2011 Apple Inc.
//
#include <stdio.h>
#ifdef __APPLE__
#include <libgen.h>
#endif  //__APPLE__

#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include "main.h"
#include "Utils.h"
#include "TestList.h"
#include "LinearMath/btScalar.h"

#if defined(BT_USE_NEON) || defined(BT_USE_SSE_IN_API)

#ifdef _WIN32
#define strcasecmp _stricmp
#define basename(A) A
#endif

#define EXIT_NO_ERROR INT_MIN

//int gReportNanoseconds = 0;    // in Utils.c

int gReportAverageTimes = 0;
int gExitOnError = 0;
char *gFullPath = NULL;
const char *gAppName = NULL;
int gArgc;
const char **gArgv;

typedef struct TestNode
{
	struct TestNode *next;
	const char *name;
} TestNode;

TestNode *gNodeList = NULL;

static int ParseArgs(int argc, const char *argv[]);
static void PrintUsage(void);
static int Init(void);
static void ListTests(void);

const char *gArch =
#ifdef __i386__
	"i386";
#elif defined __x86_64__
	"x86_64";
#elif defined __arm__
	"arm";
#elif defined _WIN64
	"win64";
#elif defined _WIN32
	"win32";
#else
#error unknown arch
#endif

#include <stdio.h>

int main(int argc, const char *argv[])
{
	// Enable just one test programatically (instead of command-line param)
	// TestNode *node = (TestNode*) malloc( sizeof( TestNode ) );
	// node->name = "btDbvt";
	// node->next = 0;
	// gNodeList = node;

	srand(0.f);

	int numPassedTests = 0;
	int numFailedTests = 0;

	int err;

	// Parse arguments. Build gNodeList.
	if ((err = ParseArgs(argc, argv)))
	{
		if (EXIT_NO_ERROR == err)
			return 0;

		PrintUsage();
		return err;
	}

	printf("Arch: %s\n", gArch);

	if (gReportAverageTimes)
		printf("Reporting average times.\n");
	else
		printf("Reporting best times.\n");

	// Set a few things up
	if ((err = Init()))
	{
		printf("Init failed.\n");
		return err;
	}

	if (NULL == gNodeList)
	{  // test everything
		printf("No function list found. Testing everything...\n");
		size_t i;
		for (i = 0; NULL != gTestList[i].test_func; i++)
		{
			printf("\n----------------------------------------------\n");
			printf("Testing %s:\n", gTestList[i].name);
			printf("----------------------------------------------\n");
			uint64_t startTime = ReadTicks();
			int local_error = gTestList[i].test_func();
			uint64_t currentTime = ReadTicks() - startTime;
			if (local_error)
			{
				numFailedTests++;
				printf("*** %s test failed with error: %d\n", gTestList[i].name, local_error);
				if (gExitOnError)
					return local_error;
				if (0 == err)
					err = local_error;
			}
			else
			{
				numPassedTests++;
				printf("%s Passed.\t\t\t(%2.2gs)\n", gTestList[i].name, TicksToSeconds(currentTime));
			}
		}
	}
	else
	{  // test just the list
		while (NULL != gNodeList)
		{
			TestNode *currentNode = gNodeList;
			gNodeList = gNodeList->next;

			// Find the test with that name
			size_t i;
			for (i = 0; NULL != gTestList[i].test_func; i++)
				if (0 == strcasecmp(currentNode->name, gTestList[i].name))
					break;

			if (NULL != gTestList[i].test_func)
			{
				printf("\n----------------------------------------------\n");
				printf("Testing %s:\n", gTestList[i].name);
				printf("----------------------------------------------\n");
				uint64_t startTime = ReadTicks();
				int local_error = gTestList[i].test_func();
				uint64_t currentTime = ReadTicks() - startTime;
				if (local_error)
				{
					numFailedTests++;
					printf("*** %s test failed with error: %d\n", gTestList[i].name, local_error);
					if (gExitOnError)
						return local_error;
					if (0 == err)
						err = local_error;
				}
				else
				{
					numPassedTests++;
					printf("%s Passed.\t\t\t(%2.2gs)\n", gTestList[i].name, TicksToSeconds(currentTime));
				}
			}
			else
			{
				printf("\n***Error: Test name \"%s\" not found! Skipping.\n", currentNode->name);
				err = -1;
				if (gExitOnError)
					return -1;
			}

			free(currentNode);
		}
	}
	printf("\n----------------------------------------------\n");
	printf("numPassedTests = %d, numFailedTests = %d\n", numPassedTests, numFailedTests);

	free(gFullPath);
	return err;
}

static int Init(void)
{
	// init the timer
	TicksToCycles(0);

	return 0;
}

static int ParseArgs(int argc, const char *argv[])
{
	int listTests = 0;
	TestNode *list = NULL;

	gArgc = argc;
	gArgv = argv;
	gFullPath = (char *)malloc(strlen(argv[0]) + 1);
	strcpy(gFullPath, argv[0]);
	gAppName = basename(gFullPath);
	if (NULL == gAppName)
		gAppName = "<unknown app name>";

	printf("%s ", gAppName);
	int skipremaining = 0;

	size_t i;
	for (i = 1; i < argc; i++)
	{
		const char *arg = argv[i];
		printf("\t%s", arg);
		if (arg[0] == '-')
		{
			arg++;
			while (arg[0] != '\0')
			{
				int stop = 0;
				switch (arg[0])
				{
					case 'a':
						gReportAverageTimes ^= 1;
						break;
					case 'e':
						gExitOnError ^= 1;
						break;
					case 'h':
						PrintUsage();
						return EXIT_NO_ERROR;
					case 'l':
						listTests ^= 1;
						return EXIT_NO_ERROR;
					case 's':
						gReportNanoseconds ^= 1;
						break;
					case ' ':
						stop = 1;
						break;
					case 'N':  //ignore the -NSDocumentRevisionsDebugMode argument from XCode 4.3.2
						skipremaining = 1;
						stop = 1;
						break;
					default:
						printf("\nError: Unknown flag \'%c\'\n", arg[0]);
						return -1;
				}
				if (stop)
					break;
				arg++;
			}
		}
		else
		{  // add function name to the list
			TestNode *node = (TestNode *)malloc(sizeof(TestNode));
			node->name = arg;
			node->next = list;
			list = node;
		}
		if (skipremaining)
			break;
	}

	// reverse the list of test names, and stick on gNodeList
	while (list)
	{
		TestNode *node = list;
		TestNode *next = node->next;
		node->next = gNodeList;
		gNodeList = node;
		list = next;
	}

	printf("\n");
	if (listTests)
		ListTests();

	return 0;
}

static void PrintUsage(void)
{
	printf("\nUsage:\n");
	printf("%s: <-aehls> <test names>", gAppName);
	printf("Options:\n");
	printf("\t-a\tToggle report average times vs. best times. (Default: best times)\n");
	printf("\t-e\tToggle exit immediately on error behavior. (Default: off)\n");
	printf("\t-h\tPrint this message.\n");
	printf("\t-l\tToggle list available test names.  (Default: off)\n");
	printf("\t-s\tToggle report times in cycles or nanoseconds. (Default: cycles)\n\n");
	printf("\tOptions may be followed by one or more test names. If no test names \n");
	printf("\tare provided, then all tests are run.\n\n");
}

static void ListTests(void)
{
	size_t i;

	printf("\nTests:\n");
	for (i = 0; NULL != gTestList[i].test_func; i++)
	{
		printf("%19s", gTestList[i].name);
		if (NULL != gTestList[i].test_func)
			printf(",");
		if (3 == (i & 3))
			printf("\n");
	}
}
#else
#include <stdio.h>
int main(int argc, char* argv[])
{
	printf("error: no SIMD enabled through BT_USE_NEON or BT_USE_SSE_IN_API \n(enable in LinearMath/btScalar.h or through build system)\n");
	return 0;
}
#endif
