// (c) Electronic Arts.  All Rights Reserved.
//
// David Bollo (mailto:dbollo@ea.com)

#include "test.h"
#include "stdio.h"
#if !defined(EA_PLATFORM_PS3)
#include "signal.h"
#endif

#include "EATypeOf/typeof.h"
#include "EATypeOf/internal/hash.h"


int gNumTests = 0;
int gNumErrors = 0;


void Test(bool success, const char* expr, int line)
{
	static const char pass[] = "    PASS";
	static const char fail[] = "*** FAIL";

	const char* msg = pass;
	if (!success)
	{
		msg = fail;
		++gNumErrors;
	}
	++gNumTests;

	printf("%s: %s\n", msg, expr);
}


#define TEST(expr)	Test(expr, #expr, __LINE__)


int main(int argc, const char**)
{
	using namespace EA::Type;

	// Call some minimalist TypeOf functions so that we have a location for a
	// breakpoint in case we wish to step in to inspect the generated code
	printf("    INFO: TypeOf<Cat>() == %08x\n", static_cast<unsigned int>(TypeOf_Cat()));
	printf("    INFO: TypeOf<Dog>() == %08x\n", static_cast<unsigned int>(TypeOf_Dog()));
	printf("    INFO: TypeOf<Cow>() == %08x\n", static_cast<unsigned int>(TypeOf_Cow()));
	printf("    INFO: TypeOf<int>() == %08x\n", static_cast<unsigned int>(TypeOf_Int()));
	printf("\n");

	// Test basic types
	TEST(TypeOf<int>() != TypeOf<float>());
	TEST(TypeOf<int>() != TypeOf<unsigned>());
	TEST(TypeOf<int>() != TypeOf<void>());
	TEST(TypeOf<void>() != TypeOf<void*>());
	printf("\n");

	// Test TypeOf<T> vs TypeOf(object)
	int i;
	float f;
	TEST(TypeOf(i) == TypeOf<int>());
	TEST(TypeOf(f) == TypeOf<float>());
	TEST(TypeOf(i) != TypeOf(f));
	printf("\n");

	// Test typedefs and structures with matching layouts
	TEST(TypeOf<Pup>() == TypeOf<Dog>());
	TEST(TypeOf<Cat>() != TypeOf<Dog>());
	printf("\n");

	// Test enumerations
	Elephant e;
	TEST(TypeOf(e) == TypeOf<Elephant>());
	TEST(TypeOf(e) != TypeOf<int>());
	printf("\n");

	// Test TypeID consistency across translation units
	TEST(TypeOf_Int() == TypeOf<int>());
	TEST(TypeOf_Dog() == TypeOf<Dog>());
	printf("\n");

	// Test incomplete types (Cow is forward-declared but never defined)
	TEST(TypeOf_Cow() == TypeOf<Cow>());
	printf("\n");

	// Test that pointers are respected
	int* pi = &i;
	TEST(TypeOf(pi) != TypeOf<int>());
	TEST(TypeOf(pi) == TypeOf<int*>());
	printf("\n");

	// Test that references are ignored
	int& ri = i;
	TEST(TypeOf(ri) == TypeOf(i));
	TEST(TypeOf(ri) == TypeOf<int>());
	printf("\n");

	// Test that references are not pointers
	TEST(TypeOf(pi) != TypeOf(ri));
	printf("\n");

	// Test const and volatile equivalence
	const int ci = 1;
	volatile int vi = 2;
	const volatile int cvi = 3;
	TEST(TypeOf(ci) == TypeOf<int>());
	TEST(TypeOf(vi) == TypeOf<int>());
	TEST(TypeOf(cvi) == TypeOf<int>());
	TEST(TypeOf<const char>() == TypeOf<char>());
	TEST(TypeOf<const char*>() != TypeOf<char*>());
	printf("\n");

	// Test for TypeID being readable/writable in a single instruction
#if !defined(EA_PLATFORM_PS3)
	TEST(sizeof(TypeID) == sizeof(sig_atomic_t));
	TEST(EA_ALIGN_OF(TypeID) == EA_ALIGN_OF(sig_atomic_t));
	printf("\n");
#endif

	// Test that the internal hash function correctly suppresses zero hashes.  Note
	// that the magic string below hashes to zero under FNV1 (which is the hash
	// algorithm that we are using in this implementation).
	//
	// See also: http://isthe.com/chongo/tech/comp/fnv/#zero-hash, challenge #49 
	{
		using namespace EA::Type;

		const char fnv_zero[] = "\x30\x33\x53\x42\x5b";
		const char fnv_test[] = "Testing 123...";

		TEST(internal::RuntimeHash<TypeID>(fnv_zero) != 0);
		TEST(internal::StaticHash<TypeID>(fnv_zero) != 0);
		TEST(internal::RuntimeHash<TypeID>(fnv_zero) == internal::StaticHash<TypeID>(fnv_zero));
		TEST(internal::RuntimeHash<TypeID>(fnv_test) == internal::StaticHash<TypeID>(fnv_test));
		TEST(internal::RuntimeHash<TypeID>(fnv_zero) == static_cast<TypeID>(internal::HashConstants<sizeof(TypeID)*8>::FNV_BASIS));
		TEST(internal::RuntimeHash<TypeID>(fnv_test) != static_cast<TypeID>(internal::HashConstants<sizeof(TypeID)*8>::FNV_BASIS));
		printf("\n");
	}

	printf("%s: %d errors out of %d tests\n",
		(gNumErrors == 0) ? "SUCCESS" : "FAILURE",
		gNumErrors,
		gNumTests);

#if defined(EA_PLATFORM_PS3)
	// Write ^Z to instruct SN's ps3run to exit
	printf("\x1A");
#endif

	return gNumErrors;
}
