/*H************************************************************************************************/
/*!
  \File nav_unittest.cpp

  \Description

  \Notes
  None.

  \Copyright
  (c) 2010	 Electronic Arts Inc.

  \Version    1.0     10/06/2010 (JS) First version
  */
/************************************************************************************************H*/

#if EA_RUNTIME_TESTING

#include "EABaseTypes/factory.h"
#include "EABaseTypes/stringtable.h"
#include "EABaseTypes/callermap.h"
#include "EABaseTypes/metatypes.h"
#include "EABaseTypes/functor.h"
#include "luanavtester.h"
#include "luanavpathfinder.h"
#include "EABaseTypesAssetStream/assetstreamfilehandler.h"
#include "ux_base/trace.h"
#include "EABaseTypesJson/jsonloader.h"

#include "ux_eatrace/tracerfactory.h"

#include "EABaseTypesLua/datautillua.h"

#include "unit/unit.h"
#include "coreunit/interface.h"
#include "coreallocator/icoreallocatormacros.h"
#include "coreallocator/icoreallocator_interface.h"
#include "EASmallBlockAllocator.h"
#include "coreunit/interface.h"

//lint -e961
// Violates MISRA Advisory Rule 19.1, only preprocessor statements and comments before '#include'
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

#include "EABaseTypesLua/EABaseTypesLua.h"

//nav does a lot of small allocations, in order to keep the unit test running in a reasonable amount of time we use a small SBA
class NavUnitTestAllocator : public EA::Allocator::ICoreAllocator
{
public:
	
	NavUnitTestAllocator(EA::Allocator::ICoreAllocator* pAllocator)
		: mAllocator(pAllocator)
	{
		EA::Allocator::SmallBlockAllocator::Parameters sbaparams;
		sbaparams.mnMaxAllocSize = 0xFFF0 - static_cast<uint16_t>(EA::Allocator::SmallBlockAllocator::CoreBlockOverhead());
		sbaparams.mnCoreBlockSize = 0xFFF0;
		mSBA.Init(&sbaparams, &NavUnitTestAllocator::AllocationFunction, &NavUnitTestAllocator::FreeFunction, this);
	}

	virtual ~NavUnitTestAllocator() 
	{
		mSBA.Shutdown();
	}

	void *Alloc(size_t size, const char *name, unsigned int flags)
	{
		void* ptr = mSBA.Malloc(size);
		EA_ASSERT(ptr != NULL);
		return ptr;
	}

	void *Alloc(size_t size, const char *name, unsigned int flags,
		unsigned int align, unsigned int alignOffset = 0)
	{
		void* ptr = NULL;

		if(size <= 8192)
		{
			ptr = mSBA.MallocAligned(size, align);
		}
		
		if(ptr == NULL)
		{
			ptr = mAllocator->Alloc(size, name, flags, align, alignOffset);
		}

		return ptr;
	}

	void Free(void *block, size_t size=0)
	{
		if(!mSBA.FreeSafe(block, size))
		{
			mAllocator->Free(block);
		}
	}

private:
	static void* AllocationFunction(EA::Allocator::SmallBlockAllocator* pAllocator, size_t nSize, size_t nAlignment, size_t nAlignmentOffset, void* pContext)
	{
		NavUnitTestAllocator* pThis = static_cast<NavUnitTestAllocator*>(pContext);
		return pThis->mAllocator->Alloc(nSize, "NavUnitTestAllocator", EA::Allocator::MEM_PERM, nAlignment, nAlignmentOffset);
	}

	static void FreeFunction(EA::Allocator::SmallBlockAllocator* pAllocator, void* pCoreBlock, void* pContext)
	{
		NavUnitTestAllocator* pThis = static_cast<NavUnitTestAllocator*>(pContext);
		pThis->mAllocator->Free(pCoreBlock);

	}
	EA::Allocator::SmallBlockAllocator mSBA;
	EA::Allocator::ICoreAllocator*	mAllocator;
};
class UXNavUnitTests : public EATest::AsyncTestSuite
{
	public:

		UXNavUnitTests()
		{
		}

		virtual void Initialize()
		{
			SuiteName("UXNavUnitTests");
			EATEST_REGISTER_ASYNC("TestBootFlow", "Testing BootFlow", UXNavUnitTests, TestBootFlow);
			EATEST_REGISTER_ASYNC("TestAsyncTasks", "Testing Async Tasks", UXNavUnitTests, TestAsyncTasks);
			EATEST_REGISTER_ASYNC("TestMultiFile", "Testing Multi File", UXNavUnitTests, TestMultiFile);
			EATEST_REGISTER_ASYNC("TestInvoke", "Testing Invoke", UXNavUnitTests, TestInvoke);
			EATEST_REGISTER_ASYNC("TestInputs", "Testing Inputs", UXNavUnitTests, TestInputs);
			EATEST_REGISTER_ASYNC("TestTransitiveInputs", "Testing Transitive Inputs", UXNavUnitTests, TestTransitiveInputs);
			EATEST_REGISTER_ASYNC("TestInputOutput", "Testing Input/Output", UXNavUnitTests, TestInputOutput);
			EATEST_REGISTER_ASYNC("TestParallelImplicit", "Testing Test Parallel Implicit Transition", UXNavUnitTests, TestParallelImplicit);
			EATEST_REGISTER_ASYNC("TestInitialActions", "Testing Initial Actions", UXNavUnitTests, TestInitialActions);
			EATEST_REGISTER_ASYNC("TestTransitionActions", "Testing Transition Actions", UXNavUnitTests, TestTransitionActions);
			EATEST_REGISTER_ASYNC("TestConflict1", "Testing Conflict 1", UXNavUnitTests, TestConflict1);
			EATEST_REGISTER_ASYNC("TestConflict2", "Testing Conflict 2", UXNavUnitTests, TestConflict2);
			EATEST_REGISTER_ASYNC("TestProperties", "Testing Properties", UXNavUnitTests, TestProperties);
			EATEST_REGISTER_ASYNC("TestIf", "Testing If", UXNavUnitTests, TestIf);
			EATEST_REGISTER_ASYNC("TestConditions", "Testing Conditional Expressions", UXNavUnitTests, TestConditions);
			EATEST_REGISTER_ASYNC("TestTernary", "Testing Ternary Operator", UXNavUnitTests, TestTernary);
			EATEST_REGISTER_ASYNC("TestEventless", "Testing Eventless transitions", UXNavUnitTests, TestEventless);
			EATEST_REGISTER_ASYNC("TestMultiFilePathfinding", "Testing Multi File Pathfinding", UXNavUnitTests, TestMultiFilePathfinding);
			EATEST_REGISTER_ASYNC("TestTransitiveInputsPathfinding", "Testing Transitive Inputs Pathfinding", UXNavUnitTests, TestTransitiveInputsPathfinding);
			EATEST_REGISTER_ASYNC("TestInputOutputPathfinding", "Testing Transitive Input/Output Pathfinding", UXNavUnitTests, TestInputOutputPathfinding);
			EATEST_REGISTER_ASYNC("TestConditionalPathfinding", "Testing Conditional Pathfinding", UXNavUnitTests, TestConditionalPathfinding);
		}

		void Setup(void* data)
		{
			CoreUnit::UnitTestInfo* info = static_cast<CoreUnit::UnitTestInfo*>(data);
			EA::Allocator::ICoreAllocator* allocator = info->mAllocator;

			mAllocator = allocator;

			mStringTable = CORE_NEW(allocator, "EA::StringTable", EA::Allocator::MEM_PERM) EA::StringTable(allocator);
			mCallerMap = CORE_NEW(allocator, "EA::Types::CallerMap", EA::Allocator::MEM_PERM) EA::Types::CallerMap(allocator);
			mMetaTypes = CORE_NEW(allocator, "EA::Types::MetaTypes", EA::Allocator::MEM_PERM) EA::Types::MetaTypes();
			mFactory = CORE_NEW(allocator, "EA::Types::Factory", EA::Allocator::MEM_PERM) EA::Types::Factory(allocator, mStringTable, mCallerMap, mMetaTypes);

			UX::Trace::ITracerFactoryRef tracerFactory = CORE_NEW(allocator, "UX::EATrace::TracerFactory", EA::Allocator::MEM_PERM) UX::EATrace::TracerFactory(allocator);
			UX::Trace::Initialize(allocator, tracerFactory);

			mFileHandler = CORE_NEW(allocator, "AssetStreamFileHandler", EA::Allocator::MEM_TEMP) EA::Types::AssetStreamFileHandler(allocator, ".");

			mJsonLoader = CORE_NEW(allocator, "JsonLoader", EA::Allocator::MEM_PERM) EA::Types::JsonLoader(mFactory, mFileHandler);
			
			mNavAllocator = CORE_NEW(allocator, "NavAllocator", EA::Allocator::MEM_PERM) NavUnitTestAllocator(allocator);
			
			EA::Types::FunctionRef print;// = mFactory->Function(this, &UXNavUnitTests::Print);

			mL = EA::Lua::NewState(mNavAllocator, allocator, mFactory, mFileHandler, mJsonLoader, print, EA::Types::FunctionIn(NULL));
			
			lua_pushlightuserdata(mL, this);
			lua_pushcclosure(mL, TestAssert, 1);
			lua_setglobal(mL, "testassert");

			mIsFinishedTest = false;
		}
		
		void Print(const char* str)
		{
			printf("%s\n", str);
		}

		void TestFinished()
		{
			mIsFinishedTest = true;
		}

		virtual void Idle()
		{
			mFileHandler->Update();
			if(mIsFinishedTest)
			{
				mIsFinishedTest = false;
				EATESTContinue();
			}
		}

		void Teardown(void* data)
		{
			CoreUnit::UnitTestInfo* info = static_cast<CoreUnit::UnitTestInfo*>(data);
			EA::Allocator::ICoreAllocator* allocator = info->mAllocator;

			lua_close(mL);
			mL = NULL;

			CORE_DELETE(allocator, mNavAllocator);
			mNavAllocator = NULL;

			CORE_DELETE(allocator, mJsonLoader);
			mJsonLoader = NULL;

			mFileHandler = NULL;

			UX::Trace::Shutdown();
			
			mMetaTypes->Unload();

			CORE_DELETE(mAllocator, mFactory);
			CORE_DELETE(mAllocator, mMetaTypes);
			CORE_DELETE(mAllocator, mCallerMap);
			CORE_DELETE(mAllocator, mStringTable);
		}

		void TestBootFlow(void* data);
		void TestAsyncTasks(void* data);
		void TestMultiFile(void* data);
		void TestInvoke(void* data);
		void TestInputs(void* data);
		void TestTransitiveInputs(void* data);
		void TestInputOutput(void* data);
		void TestParallelImplicit(void* data);
		void TestInitialActions(void* data);
		void TestTransitionActions(void* data);
		void TestConflict1(void* data);
		void TestConflict2(void* data);
		void TestProperties(void* data);
		void TestIf(void* data);
		void TestConditions(void *data);
		void TestTernary(void* data);
		void TestEventless(void* data);

		void TestMultiFilePathfinding(void* data);
		void TestTransitiveInputsPathfinding(void* data);
		void TestInputOutputPathfinding(void* data);
		void TestConditionalPathfinding(void* data);

private:

		static int TestAssert(lua_State* L)
		{
			int numArgs = lua_gettop(L);  // number of arguments
			if (numArgs > 0)
			{
				if(lua_toboolean(L, 1) == 0)
				{
					const char *msg = "";
					
					if(numArgs > 1)
					{
						msg = lua_tostring(L, 2);
					}

					EATest::TestSuite* testSuite = 
						reinterpret_cast<EATest::TestSuite*>(lua_touserdata(L, lua_upvalueindex(1)));
			
					// not grabbing the LUA line and file right now
					testSuite->SignalError(msg, __LINE__,__FILE__);
				}
			}

			return 1;
		}

		void TestNav(void* data,
					 const char* navFileName,
					 const char* navTestFileName);

		void TestPathfinding(void* data,
							 const char* navFileName,
							 const char* target);

		EA::Allocator::ICoreAllocator*	mAllocator;
		EA::StringTable*				mStringTable;
		EA::Types::CallerMap*			mCallerMap;
		EA::Types::MetaTypes*			mMetaTypes;
		EA::Types::Factory*				mFactory;

		EA::Types::IFileHandlerRef		mFileHandler;
		EA::Types::JsonLoader*			mJsonLoader;
		lua_State*						mL;
		bool							mIsFinishedTest;
		NavUnitTestAllocator*			mNavAllocator;
};  

// make the unit test suite instance
UXNavUnitTests UXNavUnitTestsInstance;

// this declaration is so that main.cpp in the game can pretend to use it hence keeping
// this entire translation unit from being excluded from the build
EATest::TestSuite* gUXNavUnitTests = &UXNavUnitTestsInstance;

// *********************************************************************************************
// The test function
// *********************************************************************************************

void UXNavUnitTests::TestBootFlow(void* data)
{
	printf("\n\n");

	TestNav(data,
			"data/harelbootflow.nav",
			"data/harelbootflow.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestAsyncTasks(void* data)
{
	printf("\n\n");

	TestNav(data,
			"data/asynctasks.nav",
			"data/asynctasks.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestMultiFile(void* data)
{
	printf("\n\n");

	TestNav(data,
			"data/multifile/root.nav",
			"data/multifile/multifile.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestInvoke(void* data)
{
	printf("\n\n");

	TestNav(data,
			"data/invoke.nav",
			"data/invoke.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestInputs(void *data)
{
	printf("\n\n");

		TestNav(data,
			"data/inputs/root.nav",
			"data/inputs/inputs.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestTransitiveInputs(void *data)
{
	printf("\n\n");

		TestNav(data,
			"data/transitiveinputs/root.nav",
			"data/transitiveinputs/transitiveinputs.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestInputOutput(void *data)
{
	printf("\n\n");

		TestNav(data,
			"data/inputoutput/root.nav",
			"data/inputoutput/inputoutput.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestParallelImplicit(void *data)
{
	printf("\n\n");

		TestNav(data,
			"data/parallelimplicit.nav",
			"data/parallelimplicit.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestInitialActions(void *data)
{
	printf("\n\n");

		TestNav(data,
			"data/initialactions.nav",
			"data/initialactions.navtest");

	printf("\n\n");
}


void UXNavUnitTests::TestTransitionActions(void *data)
{
	printf("\n\n");

		TestNav(data,
			"data/transitionactions.nav",
			"data/transitionactions.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestConflict1(void *data)
{
	printf("\n\n");

		TestNav(data,
			"data/conflict1.nav",
			"data/conflict1.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestConflict2(void *data)
{
	printf("\n\n");

		TestNav(data,
			"data/conflict2.nav",
			"data/conflict2.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestProperties(void *data)
{
	printf("\n\n");

	TestNav(data,
		"data/properties.nav",
		"data/properties.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestIf(void *data)
{
	printf("\n\n");

	TestNav(data,
		"data/if.nav",
		"data/if.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestConditions(void *data)
{
	printf("\n\n");

	TestNav(data,
		"data/conditions/root.nav",
		"data/conditions/conditions.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestTernary(void *data)
{
	printf("\n\n");

	TestNav(data,
		"data/ternary.nav",
		"data/ternary.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestEventless(void *data)
{
	printf("\n\n");

	TestNav(data,
		"data/eventless.nav",
		"data/eventless.navtest");

	printf("\n\n");
}

void UXNavUnitTests::TestMultiFilePathfinding(void *data)
{
	printf("\n\n");

	TestPathfinding(data,
					"data/multifile/root.nav",
					"frontend.playnow.startgame.pausemenu");

	printf("\n\n");
}

void UXNavUnitTests::TestTransitiveInputsPathfinding(void *data)
{
	printf("\n\n");

	TestPathfinding(data,
					"data/transitiveinputs/root.nav",
					"modes.submodes.playnowsubmode");

	printf("\n\n");	
}

void UXNavUnitTests::TestInputOutputPathfinding(void *data)
{
	printf("\n\n");

	TestPathfinding(data,
					"data/inputoutput/root.nav",
					"frontend.playnow");

	printf("\n\n");	
}

void UXNavUnitTests::TestConditionalPathfinding(void *data)
{
	printf("\n\n");

	TestPathfinding(data,
					"data/conditions/root.nav",
					"online.C");

	printf("\n\n");	
}

/* private */ void UXNavUnitTests::TestNav(void* data, 
										   const char* navFileName,
										   const char* navTestFileName)
{
	EA::Types::FunctionRef finished = mFactory->Function(this, &UXNavUnitTests::TestFinished);

	UX::LuaNavTester::Run(mL, finished, "data/lua/packages/navtester.lua", navFileName, navTestFileName);
}

/* private */ void UXNavUnitTests::TestPathfinding(void* data,
												   const char* navFileName,
												   const char* target)
{
	EA::Types::FunctionRef finished = mFactory->Function(this, &UXNavUnitTests::TestFinished);
	
	UX::LuaNavPathfinder::Run(mL, finished, "data/lua/packages/navpathfinder.lua", navFileName, "data/auto.hint", "data/auto.exclude", target);
}

#endif
