#include <cstdio>
#include <GL/glut.h>

#include "NxPhysics.h"

// Set this to 0 to stop the program from trying to connect to the VRD
#define USE_VRD 1

// Physics
static NxPhysicsSDK* gPhysicsSDK = 0;
static NxScene* gScene = 0;

// Rendering
static NxVec3 gEye(50.0f, 50.0f, 50.0f);
static NxVec3 gDir(-0.6f, -0.2f, -0.7f);
static NxVec3 gViewY;
static int gMouseX = 0;
static int gMouseY = 0;

static bool InitNx()
{
	// Initialize PhysicsSDK
	gPhysicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION);

	if(gPhysicsSDK == 0) 
	{
		printf("\nError: Unable to initialize the PhysX SDK, exiting.\n\n");
		return false;
	}

#if USE_VRD
	if (gPhysicsSDK->getFoundationSDK().getRemoteDebugger())
	{
		gPhysicsSDK->getFoundationSDK().getRemoteDebugger()->connect("localhost", NX_DBG_DEFAULT_PORT, NX_DBG_EVENTMASK_EVERYTHING);
	}
#endif

	gPhysicsSDK->setParameter(NX_SKIN_WIDTH, 0.05f);

	// Create a scene
	NxSceneDesc sceneDesc;
	sceneDesc.gravity = NxVec3(0.0f, -9.81f, 0.0f);
	gScene = gPhysicsSDK->createScene(sceneDesc);

	if (gScene == 0) 
	{
		printf("\nError: Unable to create a PhysX scene, exiting.\n\n");
		return false;
	}

	// Set default material
	
	// TODO

	// Create ground plane
	
	// TODO

	return true;
}

static void ExitNx()
{
	if (gPhysicsSDK != 0)
	{
		if (gScene != 0)
		{
				gPhysicsSDK->releaseScene(*gScene);
		}

		gScene = 0;
		NxReleasePhysicsSDK(gPhysicsSDK);
		gPhysicsSDK = 0;
	}
}

static void CreateBox(const NxVec3& pos)
{
	if (gScene == 0)
	{
		return;
	}

	// TODO
}

static void CreateStack()
{
	// TODO
}

static void CreateSphere()
{
	if (gScene == 0)
	{
		return;
	}

	// TODO
}

static void KeyboardCallback(unsigned char key, int x, int y)
{
	switch (key)
	{
		case 27: // ESC	
			exit(0); 
			break;
		case ' ':	
			CreateSphere(); 
			break;
		case GLUT_KEY_UP:
		case '8':	
			gEye += gDir * 2.0f;
			break;
		case GLUT_KEY_DOWN:
		case '2':	
			gEye -= gDir * 2.0f;
			break;
		case GLUT_KEY_LEFT:
		case '4':	
			gEye -= gViewY * 2.0f;
			break;
		case GLUT_KEY_RIGHT:
		case '6':	
			gEye += gViewY * 2.0f;
			break;
	}
}

static void ArrowKeyCallback(int key, int x, int y)
{
	KeyboardCallback(key, x, y);
}

static void MouseCallback(int button, int state, int x, int y)
{
	gMouseX = x;
	gMouseY = y;
}

static void MotionCallback(int x, int y)
{
	int dx = gMouseX - x;
	int dy = gMouseY - y;
	
	gDir.normalize();
	gViewY.cross(gDir, NxVec3(0.0f, 1.0f, 0.0f));

	NxQuat qx(NxPiF32 * dx * 20.0f / 180.0f, NxVec3(0.0f, 1.0f, 0.0f));
	qx.rotate(gDir);
	NxQuat qy(NxPiF32 * dy * 20.0f / 180.0f, gViewY);
	qy.rotate(gDir);

	gMouseX = x;
	gMouseY = y;
}

static void RenderCallback()
{
	if (gScene == 0)
	{
		return;
	}

	// Start simulation
	
	// TODO
	
	// Clear buffers
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Setup projection matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0f, (float)glutGet(GLUT_WINDOW_WIDTH) / (float)glutGet(GLUT_WINDOW_HEIGHT), 1.0f, 10000.0f);
	gluLookAt(gEye.x, gEye.y, gEye.z, gEye.x + gDir.x, gEye.y + gDir.y, gEye.z + gDir.z, 0.0f, 1.0f, 0.0f);

	// Setup modelview matrix
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Render all actors
	
	// TODO

	// Fetch simulation results
	
	// TODO

	glutSwapBuffers();
}

static void ReshapeCallback(int width, int height)
{
	glViewport(0, 0, width, height);
}

static void IdleCallback()
{
	glutPostRedisplay();
}

int main(int argc, char** argv)
{
	// Initialize GLUT
	printf("Use the arrow keys or 2, 4, 6 and 8 to move the camera.\n");
	printf("Use the mouse to rotate the camera.\n");
	printf("Press SPACE to fire the gun and ESC to exit.\n");

	glutInit(&argc, argv);
	glutInitWindowSize(512, 512);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	int mainHandle = glutCreateWindow("Boxes");
	glutSetWindow(mainHandle);
	glutDisplayFunc(RenderCallback);
	glutReshapeFunc(ReshapeCallback);
	glutIdleFunc(IdleCallback);
	glutKeyboardFunc(KeyboardCallback);
	glutSpecialFunc(ArrowKeyCallback);
	glutMouseFunc(MouseCallback);
	glutMotionFunc(MotionCallback);
	MotionCallback(0, 0);
	atexit(ExitNx);

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_COLOR_MATERIAL);

	// Setup lighting
	glEnable(GL_LIGHTING);

	// TODO

	glEnable(GL_LIGHT0);

	// Initialize physics scene and start the application main loop if scene was created
	if (InitNx())
	{
		glutMainLoop();
	}

	return 0;
}
