/* 
 * 
 * Confidential Information of Telekinesys Research Limited (t/a Havok).  Not for disclosure or distribution without Havok's
 * prior written consent.This software contains code, techniques and know-how which is confidential and proprietary to Havok.
 * Level 2 and Level 3 source code contains trade secrets of Havok. Havok Software (C) Copyright 1999-2008 Telekinesys Research Limited t/a Havok. All Rights Reserved. Use of this software is subject to the terms of an end user license agreement.
 * 
 */

// Math and base include
#include <Common/Base/hkBase.h>
#include <Common/Base/hkBase.h>
#include <Common/Base/System/hkBaseSystem.h>
#include <Common/Base/Memory/hkThreadMemory.h>
#include <Common/Base/Memory/Memory/Pool/hkPoolMemory.h>
#include <Common/Base/System/Error/hkDefaultError.h>
#include <Common/Base/Monitor/hkMonitorStream.h>

// Dynamics includes
#include <Physics/Collide/hkpCollide.h>
#include <Physics/Collide/Agent/ConvexAgent/SphereBox/hkpSphereBoxAgent.h>
#include <Physics/Collide/Shape/Convex/Box/hkpBoxShape.h>
#include <Physics/Collide/Shape/Convex/Sphere/hkpSphereShape.h>
#include <Physics/Collide/Dispatch/hkpAgentRegisterUtil.h>


#include <Physics/Dynamics/World/hkpWorld.h>
#include <Physics/Dynamics/Entity/hkpRigidBody.h>
#include <Physics/Utilities/Dynamics/Inertia/hkpInertiaTensorComputer.h>

#include <Physics/Utilities/Thread/Multithreading/hkpMultithreadingUtil.h>

// Visual Debugger includes
#include <Common/Visualize/hkVisualDebugger.h>
#include <Physics/Utilities/VisualDebugger/hkpPhysicsContext.h>

// Keycode
#include <Common/Base/keycode.cxx>

#define HK_CLASSES_FILE <Common/Serialize/ClassList/hkPhysicsClasses.h>
#include <Common/Serialize/Util/hkBuiltinTypeRegistry.cxx>

// Generate a custom list to trim memory requirements
#define HK_COMPAT_FILE <Common/Compat/hkCompatVersions.h>
#include <Common/Compat/hkCompat_None.cxx>

#include <stdio.h>


static void HK_CALL errorReport(const char* msg, void*)
{
	printf("%s", msg);
}

#ifdef HK_PLATFORM_RVL
hkTestEntry* hkUnitTestDatabase;
hkBool HK_CALL hkTestReport(hkBool32 cond, const char* desc, const char* file, int line)
{
	errorReport(desc, 0);
	return false;
}
#endif


#if defined(HK_PLATFORM_HAS_SPU)
#	include <Common/Base/Spu/Util/hkSpuUtil.h>
#define	SPURS_THREAD_GROUP_PRIORITY 250
#define SPURS_HANDLER_THREAD_PRIORITY 1
#endif


#define NUM_THREADS 2

//
// Forward declarations
//

void setupPhysics(hkpWorld* physicsWorld);
hkVisualDebugger* setupVisualDebugger(hkpPhysicsContext* worlds);
void stepVisualDebugger(hkVisualDebugger* vdb, hkpMultithreadingUtil* multithreadingUtil, hkpPhysicsContext* context);
hkpRigidBody* g_ball;
void PlatformInit();


int HK_CALL main(int argc, const char** argv)
{
	// Do platform specific initialization
	PlatformInit();

	// Initialize the base system including our memory system
	hkPoolMemory* memoryManager = new hkPoolMemory();
	hkThreadMemory* threadMemory = new hkThreadMemory(memoryManager, 16);
	hkBaseSystem::init( memoryManager, threadMemory, errorReport );
	memoryManager->removeReference();

	// We now initialize the stack area to 100k (fast temporary memory to be used by the engine).
	char* stackBuffer;
	{
		int stackSize = 0x100000;
		stackBuffer = hkAllocate<char>( stackSize, HK_MEMORY_CLASS_BASE);
		hkThreadMemory::getInstance().setStackArea( stackBuffer, stackSize);
	}

	{
		// Create the physics world
		hkpWorld* physicsWorld;
		{
			// The world cinfo contains global simulation parameters, including gravity, solver settings etc.
			hkpWorldCinfo worldInfo;

			// Set the simulation type of the world to multi-threaded.
			worldInfo.m_simulationType = hkpWorldCinfo::SIMULATION_TYPE_MULTITHREADED;

			physicsWorld = new hkpWorld(worldInfo);
		}


		//
		// Pre-allocate some larger memory blocks. These are used by the physics system when in multithreaded mode
		// The amount and size of these depends on your physics usage.  Larger simulation islands will use larger memory blocks.
		//
		{
			hkMemory::getInstance().preAllocateRuntimeBlock(512000, HK_MEMORY_CLASS_BASE);
			hkMemory::getInstance().preAllocateRuntimeBlock(256000, HK_MEMORY_CLASS_BASE);
			hkMemory::getInstance().preAllocateRuntimeBlock(128000, HK_MEMORY_CLASS_BASE);
			hkMemory::getInstance().preAllocateRuntimeBlock(64000, HK_MEMORY_CLASS_BASE);
			hkMemory::getInstance().preAllocateRuntimeBlock(32000, HK_MEMORY_CLASS_BASE);
			hkMemory::getInstance().preAllocateRuntimeBlock(16000, HK_MEMORY_CLASS_BASE);
			hkMemory::getInstance().preAllocateRuntimeBlock(16000, HK_MEMORY_CLASS_BASE);
		}


		//
		// When the simulation type is SIMULATION_TYPE_MULTITHREADED, in the debug build, the sdk performs checks
		// to make sure only one thread is modifying the world at once to prevent multithreaded bugs. Each thread
		// must call markForRead / markForWrite before it modifies the world to enable these checks.
		//

		physicsWorld->markForWrite();

		//
		// Register all collision agents, even though only box - box will be used in this particular example.
		// It's important to register collision agents before adding any entities to the world.
		//
		{
			hkpAgentRegisterUtil::registerAllAgents( physicsWorld->getCollisionDispatcher() );
		}

		//
		// Create a multi-threading utility.  This utility will create a number of threads that will run the
		// physics step in parallel. The main thread calls functions in the utility which signal these threads
		// to start a new step, or wait for step completion.
		//
		hkpMultithreadingUtilCinfo info;
		info.m_world = physicsWorld;
		info.m_numThreads = NUM_THREADS;

		// In this example we enable timers. The multi-threading util will allocate a buffer per thread for capturing timer data.
		// If you leave this flag to false, no timers will be enabled.
		info.m_enableTimers = true;
		hkpMultithreadingUtil* multithreadingUtil = new hkpMultithreadingUtil(info);


		// Create all the physics rigid bodies
		setupPhysics( physicsWorld );




		//
		// Initialize the visual debugger so we can connect remotely to the simulation
		// The context must exist beyond the use of the VDB instance, and you can make
		// whatever contexts you like for your own viewer types.
		//

		hkpPhysicsContext* context = new hkpPhysicsContext();
		hkpPhysicsContext::registerAllPhysicsProcesses(); // all the physics viewers
		context->addWorld(physicsWorld); // add the physics world so the viewers can see it

		hkVisualDebugger* vdb = setupVisualDebugger(context);

		context->m_monitorStreamBegins.setSize(NUM_THREADS);
		context->m_monitorStreamEnds.setSize(NUM_THREADS);


		// Now we have finished modifying the world, release our write marker.
		physicsWorld->unmarkForWrite();

		//
		// Simulate the world for 1 minute.
		// Take fixed time steps of 1/60th of a second.
		// This works well if your game runs solidly at 60Hz. If your game runs at 30Hz
		// you can take either 2 60Hz steps or 1 30Hz step. Note that at lower frequencies (i.e. 30 Hz)
		// more bullet through paper issues appear, and constraints will not be as stiff.
		// If you run at variable frame rate, or are likely to drop frames, you can consider
		// running your physics for a variable number of steps based on the system clock (i.e. last frame time).
		// Please refer to the user guide section on time stepping for a full treatment of this issue.
		//

		// A stopwatch for waiting until the real time has passed
		hkStopwatch stopWatch;
		stopWatch.start();
		hkReal lastTime = stopWatch.getElapsedSeconds();

		hkReal timestep = 1.f / 60.f;
		int numSteps = int(60.f / timestep);

		for ( int i = 0; i < numSteps; ++i )
		{

			// This must be called single threaded here, prior to calling startStepWorld() below.
			physicsWorld->resetThreadTokens();

			// This will signal all threads which are in the wait state, to start processing stepDeltaTime() concurrently.
			multithreadingUtil->startStepWorld( timestep );

			// We can now do other things in this thread if we wish, while the physics runs in separate threads.
			// The call below will block until all physics processing the timestep is finished in all threads.
			multithreadingUtil->waitForStepWorldFinished();

			stepVisualDebugger( vdb, multithreadingUtil, context );

			// Display the sphereRigidBody position to the console every second
			if (i % 60 == 0)
			{
				hkVector4 pos = g_ball->getPosition();
				printf("[%f,%f,%f]\n", pos(0), pos(1), pos(2));
			}

			// Pause until the actual time has passed
			while (stopWatch.getElapsedSeconds() < lastTime + timestep);
			lastTime += timestep;

			// Step the graphics display (none in this demo).
		}

		//
		// Clean up physics and graphics
		//

		physicsWorld->markForWrite();

		physicsWorld->removeReference();

		delete multithreadingUtil;

		vdb->removeReference();

		// Contexts are not reference counted at the base class level by the VDB as
		// they are just interfaces really. So only delete the context after you have
		// finished using the VDB.
		context->removeReference();
	}
	// Deallocate stack area
	threadMemory->setStackArea(0, 0);
	hkDeallocate(stackBuffer);

	threadMemory->removeReference();

	// Quit base system
	hkBaseSystem::quit();

	return 0;
}

void setupPhysics(hkpWorld* physicsWorld)
{
	// Create the floor as a fixed box
	{
		hkpRigidBodyCinfo boxInfo;
		hkVector4 boxSize(5.0f, 0.5f , 5.0f);
		hkpBoxShape* boxShape = new hkpBoxShape(boxSize);
		boxInfo.m_shape = boxShape;
		boxInfo.m_motionType = hkpMotion::MOTION_FIXED;
		boxInfo.m_position.set(0.0f, 0.0f, 0.0f);
		boxInfo.m_restitution = 0.9f;

		hkpRigidBody* floor = new hkpRigidBody(boxInfo);
		boxShape->removeReference();

		physicsWorld->addEntity(floor);
		floor->removeReference();
	}

	// Create a moving sphere
	{
		hkReal sphereRadius = 0.5f;
		hkpConvexShape* sphereShape = new hkpSphereShape(sphereRadius);

		hkpRigidBodyCinfo sphereInfo;
		sphereInfo.m_shape = sphereShape;
		sphereInfo.m_position.set(0.0f, 5.0f, 0.0f);
		sphereInfo.m_motionType = hkpMotion::MOTION_SPHERE_INERTIA;

	        // Compute mass properties
		hkReal sphereMass = 10.0f;
		hkpMassProperties sphereMassProperties;
		hkpInertiaTensorComputer::computeSphereVolumeMassProperties(sphereRadius, sphereMass, sphereMassProperties);
		sphereInfo.m_inertiaTensor = sphereMassProperties.m_inertiaTensor;
		sphereInfo.m_centerOfMass = sphereMassProperties.m_centerOfMass;
		sphereInfo.m_mass = sphereMassProperties.m_mass;

		// Create sphere RigidBody
		hkpRigidBody* sphereRigidBody = new hkpRigidBody(sphereInfo);
		sphereShape->removeReference();

		physicsWorld->addEntity(sphereRigidBody);
	    g_ball = sphereRigidBody;
	    sphereRigidBody->removeReference();
	}
}

hkVisualDebugger* setupVisualDebugger(hkpPhysicsContext* physicsWorlds)
{
	// Setup the visual debugger
	hkArray<hkProcessContext*> contexts;
	contexts.pushBack(physicsWorlds);

	hkVisualDebugger* vdb = new hkVisualDebugger(contexts);
	vdb->serve();

	return vdb;
}

void stepVisualDebugger(hkVisualDebugger* vdb, hkpMultithreadingUtil* multithreadingUtil, hkpPhysicsContext* context)
{

	context->m_monitorStreamBegins.setSize( NUM_THREADS );
	context->m_monitorStreamEnds.setSize( NUM_THREADS );

	// Set the timer streams for passing to the visual debugger
	for (int j = 0; j < multithreadingUtil->getNumThreads(); ++j)
	{
		context->m_monitorStreamBegins[j] = multithreadingUtil->m_state.m_workerThreads[j].m_monitorStreamBegin;
		context->m_monitorStreamEnds[j] = multithreadingUtil->m_state.m_workerThreads[j].m_monitorStreamEnd;
	}

	// Step the debugger
	vdb->step();
}


/*
* Havok SDK - NO SOURCE PC DOWNLOAD, BUILD(#20080529)
*
* Confidential Information of Havok.  (C) Copyright 1999-2008 
* Telekinesys Research Limited t/a Havok. All Rights Reserved. The Havok
* Logo, and the Havok buzzsaw logo are trademarks of Havok.  Title, ownership
* rights, and intellectual property rights in the Havok software remain in
* Havok and/or its suppliers.
*
* Use of this software for evaluation purposes is subject to and indicates 
* acceptance of the End User licence Agreement for this product. A copy of 
* the license is included with this software and is also available at
* www.havok.com/tryhavok
*
*/
