//
//  Test_mindot.cpp
//  BulletTest
//
//  Copyright (c) 2011 Apple Inc.
//

#include "LinearMath/btScalar.h"
#if defined(BT_USE_SSE_IN_API) || defined(BT_USE_NEON)

#include "Test_mindot.h"
#include "vector.h"
#include "Utils.h"
#include "main.h"
#include <math.h>
#include <string.h>

#include <LinearMath/btVector3.h>

// reference code for testing purposes
static long mindot_ref(const btSimdFloat4 *vertices,
					   float *vec,
					   size_t count,
					   float *dotResult);

#ifdef __arm__
#define MAX_LOG2_SIZE 9
#else
#define MAX_LOG2_SIZE 9
#endif
#define MAX_SIZE (1U << MAX_LOG2_SIZE)
#define LOOPCOUNT 100

int Test_mindot(void)
{
	// Init an array flanked by guard pages
	btSimdFloat4 *data = (btSimdFloat4 *)GuardCalloc(1, MAX_SIZE * sizeof(btSimdFloat4), NULL);
	float *fp = (float *)data;
	long correct, test;
	btVector3 localScaling(0.1f, 0.2f, 0.3f);
	size_t size;

	// Init the data
	size_t i;
	for (i = 0; i < MAX_SIZE; i++)
	{
		fp[4 * i] = (int32_t)RANDF_16;
		fp[4 * i + 1] = (int32_t)RANDF_16;
		fp[4 * i + 2] = (int32_t)RANDF_16;
		fp[4 * i + 3] = BT_NAN;  // w channel NaN
	}

	float correctDot, testDot;
	fp = (float *)localScaling;
	float maxRelativeError = 0.f;

	for (size = 1; size <= MAX_SIZE; size++)
	{
		float *in = (float *)(data + MAX_SIZE - size);
		size_t position;

		for (position = 0; position < size; position++)
		{
			float *biggest = in + position * 4;
			float old[4] = {biggest[0], biggest[1], biggest[2], biggest[3]};
			biggest[0] -= LARGE_FLOAT17;
			biggest[1] -= LARGE_FLOAT17;
			biggest[2] -= LARGE_FLOAT17;
			biggest[3] -= LARGE_FLOAT17;

			correctDot = BT_NAN;
			testDot = BT_NAN;
			correct = mindot_ref((btSimdFloat4 *)in, (float *)&localScaling, size, &correctDot);
			test = localScaling.minDot((btVector3 *)in, size, testDot);
			if (test < 0 || test >= size)
			{
				vlog("Error @ %ld: index out of bounds! *%ld vs %ld \n", size, correct, test);
				continue;
			}
			if (correct != test)
			{
				vlog("Error @ %ld: index misreported! *%ld vs %ld  (*%f, %f)\n", size, correct, test,
					 fp[0] * in[4 * correct] + fp[1] * in[4 * correct + 1] + fp[2] * in[4 * correct + 2],
					 fp[0] * in[4 * test] + fp[1] * in[4 * test + 1] + fp[2] * in[4 * test + 2]);
				return 1;
			}
			if (test != position)
			{
				vlog("Biggest not found where it is supposed to be: *%ld vs %ld (*%f, %f)\n", position, test,
					 fp[0] * in[4 * test] + fp[1] * in[4 * test + 1] + fp[2] * in[4 * test + 2],
					 fp[0] * in[4 * position] + fp[1] * in[4 * position + 1] + fp[2] * in[4 * position + 2]);
				return 1;
			}

			if (correctDot != testDot)
			{
				float relativeError = btFabs((testDot - correctDot) / correctDot);
				if (relativeError > 1e6)
				{
					vlog("Error @ %ld: dotpr misreported! *%f vs %f    (*%f, %f)\n", size, correctDot, testDot,
						 fp[0] * in[4 * correct] + fp[1] * in[4 * correct + 1] + fp[2] * in[4 * correct + 2],
						 fp[0] * in[4 * test] + fp[1] * in[4 * test + 1] + fp[2] * in[4 * test + 2]);
					return 1;
				}
				else
				{
					if (maxRelativeError < relativeError)
					{
						maxRelativeError = relativeError;
					}
				}
			}

			memcpy(biggest, old, 16);
		}
	}

	if (maxRelativeError)
	{
		printf("Warning: relative error = %e\n", maxRelativeError);
	}
	uint64_t scalarTimes[33 + (MAX_LOG2_SIZE - 5)];
	uint64_t vectorTimes[33 + (MAX_LOG2_SIZE - 5)];
	size_t j, k;
	float *in = (float *)data;
	for (size = 1; size <= 32; size++)
	{
		uint64_t startTime, bestTime, currentTime;

		bestTime = -1LL;
		scalarTimes[size] = 0;
		for (j = 0; j < 100; j++)
		{
			startTime = ReadTicks();
			for (k = 0; k < LOOPCOUNT; k++)
				correct += mindot_ref((btSimdFloat4 *)in, (float *)&localScaling, size, &correctDot);
			currentTime = ReadTicks() - startTime;
			scalarTimes[size] += currentTime;
			if (currentTime < bestTime)
				bestTime = currentTime;
		}
		if (0 == gReportAverageTimes)
			scalarTimes[size] = bestTime;
		else
			scalarTimes[size] /= 100;
	}

	uint64_t *timep = &scalarTimes[33];
	for (size = 64; size <= MAX_SIZE; size *= 2)
	{
		uint64_t startTime, bestTime, currentTime;

		bestTime = -1LL;
		timep[0] = 0;
		for (j = 0; j < 100; j++)
		{
			startTime = ReadTicks();
			for (k = 0; k < LOOPCOUNT; k++)
				correct += mindot_ref((btSimdFloat4 *)in, (float *)&localScaling, size, &correctDot);
			currentTime = ReadTicks() - startTime;
			timep[0] += currentTime;
			if (currentTime < bestTime)
				bestTime = currentTime;
		}
		if (0 == gReportAverageTimes)
			timep[0] = bestTime;
		else
			timep[0] /= 100;

		timep++;
	}

	for (size = 1; size <= 32; size++)
	{
		uint64_t startTime, bestTime, currentTime;

		bestTime = -1LL;
		vectorTimes[size] = 0;
		for (j = 0; j < 100; j++)
		{
			startTime = ReadTicks();
			for (k = 0; k < LOOPCOUNT; k++)
				test += localScaling.minDot((btVector3 *)in, size, testDot);
			currentTime = ReadTicks() - startTime;
			vectorTimes[size] += currentTime;
			if (currentTime < bestTime)
				bestTime = currentTime;
		}
		if (0 == gReportAverageTimes)
			vectorTimes[size] = bestTime;
		else
			vectorTimes[size] /= 100;
	}

	timep = &vectorTimes[33];
	for (size = 64; size <= MAX_SIZE; size *= 2)
	{
		uint64_t startTime, bestTime, currentTime;

		bestTime = -1LL;
		timep[0] = 0;
		for (j = 0; j < 100; j++)
		{
			startTime = ReadTicks();
			for (k = 0; k < LOOPCOUNT; k++)
				test += localScaling.minDot((btVector3 *)in, size, testDot);
			currentTime = ReadTicks() - startTime;
			timep[0] += currentTime;
			if (currentTime < bestTime)
				bestTime = currentTime;
		}
		if (0 == gReportAverageTimes)
			timep[0] = bestTime;
		else
			timep[0] /= 100;

		timep++;
	}

	vlog("Timing:\n");
	vlog(" size\t    scalar\t    vector\n");
	for (size = 1; size <= 32; size++)
		vlog("%5lu\t%10.2f\t%10.2f\n", size, TicksToCycles(scalarTimes[size]) / LOOPCOUNT, TicksToCycles(vectorTimes[size]) / LOOPCOUNT);
	size_t index = 33;
	for (size = 64; size <= MAX_SIZE; size *= 2)
	{
		vlog("%5lu\t%10.2f\t%10.2f\n", size, TicksToCycles(scalarTimes[index]) / LOOPCOUNT, TicksToCycles(vectorTimes[index]) / LOOPCOUNT);
		index++;
	}

	// Useless check to make sure that the timing loops are not optimized away
	if (test != correct)
		vlog("Error: Test != correct: *%ld vs. %ld\n", correct, test);

	GuardFree(data);

	return 0;
}

static long mindot_ref(const btSimdFloat4 *vertices,
					   float *vec,
					   size_t count,
					   float *dotResult)
{
	const float *dp = (const float *)vertices;
	float minDot = BT_INFINITY;
	long i = 0;
	long ptIndex = -1;

	for (i = 0; i < count; i++)
	{
		float dot = vec[0] * dp[0] + vec[1] * dp[1] + vec[2] * dp[2];
		dp += 4;

		if (dot < minDot)
		{
			minDot = dot;
			ptIndex = i;
		}
	}

	*dotResult = minDot;

	return ptIndex;
}

#endif  //BT_USE_SSE
