#include "Application.h"

#include <OgreWindowEventUtilities.h>
#include <OgreEntity.h>
#include <OgreRenderSystem.h>

Application::Application()
{
	// Make all the used pointers NULL
	ogre                        = NULL;
	window                      = NULL;
	sceneManager                = NULL;
	camera                      = NULL;
	viewport                    = NULL;
	ois                         = NULL;
	mouse                       = NULL;
	keyboard                    = NULL;
	cursor                      = NULL;
	screen                      = NULL;
	picker                      = NULL;
	factory                     = NULL;
	firstPersonCameraController = NULL;
	world                       = NULL;
	defaultMaterialPair         = NULL;

	// Set desired framerate and variables used for updating the physics
	desiredPhysicsFramerate     = 120; //!< Desired physics update framerate
	simulationDuration          = 0;  //!< Number of milliseconds passed since the start of the simulation
	lastFrameDuration           = 0;  //!< Last frame duration, in milliseconds
	physicsUpdateStep           = 1.0f / static_cast<float>(desiredPhysicsFramerate);

	// Application is not yet running and debugger is not activated by default
	running                     = false;
	debuggerActive              = false;
}

Application::~Application()
{
	// Clean everything up in reverse order these were created
	if (defaultMaterialPair)
	{
		delete defaultMaterialPair;
		defaultMaterialPair = NULL;
	}

	if (factory)
	{
		delete factory;
		factory = NULL;
	}

	if (picker)
	{
		delete picker;
		picker = NULL;
	}

	if (world)
	{
		world->getDebugger().deInit();

		delete world;
		world = NULL;
	}

	if (firstPersonCameraController)
	{
		delete firstPersonCameraController;
		firstPersonCameraController = NULL;
	}

	if (screen)
	{
		delete screen;
		screen = NULL;
	}

	if (cursor)
	{
		delete cursor;
		cursor = NULL;
	}

	if (mouse)
	{
		ois->destroyInputObject(mouse);
		mouse = NULL;
	}

	if (keyboard)
	{
		ois->destroyInputObject(keyboard);
		keyboard = NULL;
	}
    
	if (ois)
	{
		ois->destroyInputObject(mouse);
		mouse = NULL;

		ois->destroyInputObject(keyboard);
		keyboard = NULL;

		OIS::InputManager::destroyInputSystem(ois);
		ois = NULL;
	}

	if (ogre)
	{
		delete ogre;
		ogre = NULL;
	}
}

bool Application::initialise()
{
	// Initialise Ogre and load plugins based on debug mode
	ogre = new Ogre::Root("", "ogre.cfg", "ogre.log");

#ifdef _DEBUG
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
	ogre->loadPlugin("RenderSystem_Direct3D9_d");
#else
	ogre->loadPlugin("RenderSystem_GL_d");
#endif
	ogre->loadPlugin("Plugin_CgProgramManager_d");
#else
#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32
	ogre->loadPlugin("RenderSystem_Direct3D9");
#else
	ogre->loadPlugin("./RenderSystem_GL");
#endif
	ogre->loadPlugin("./Plugin_CgProgramManager");
#endif

	// Show the configuration dialog, exit if user cancels
	if (!ogre->showConfigDialog())
	{
		return false;
	}

	// Initialise Ogre and start listening window events
	window = ogre->initialise(true, "OgreNewt Minimal Application Project - Priit Kallas 2010");
	Ogre::WindowEventUtilities::addWindowEventListener(window, this);

	// Add used resource groups
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/gui", "FileSystem", "General");
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/materials/scripts", "FileSystem", "General");
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/fonts", "FileSystem", "General");
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/primitives", "FileSystem", "General");
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/meshes", "FileSystem", "General");

	// Initialise the resource groups
	Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup("General");

	// Create a generic scene manager
	sceneManager = ogre->createSceneManager(Ogre::ST_GENERIC);

	// Create a camera
	camera = sceneManager->createCamera("camera");
	camera->setNearClipDistance(0.1f);
	camera->setFarClipDistance(250.0f);

	// Create a first person camera controller
	firstPersonCameraController = new FirstPersonCameraController(sceneManager);

	// Add main viewport
    viewport = window->addViewport(camera);

	// Set camera aspect ratio based on viewport size
	camera->setAspectRatio(Ogre::Real(viewport->getActualWidth()) / Ogre::Real(viewport->getActualHeight()));

	// Get window handle, needed for OIS
	unsigned long windowHandle;
	window->getCustomAttribute("WINDOW", &windowHandle);

	// Feed OIS window handle needed to capture events
	OIS::ParamList oisParameters;
	oisParameters.insert(OIS::ParamList::value_type("WINDOW", Ogre::StringConverter::toString(windowHandle)));

	// Create an OIS input system
	ois = OIS::InputManager::createInputSystem(oisParameters);

	// Create mouse and keyboard, joystick can be made in the same manner
	mouse = static_cast<OIS::Mouse*>(ois->createInputObject(OIS::OISMouse, true));
	keyboard = static_cast<OIS::Keyboard*>(ois->createInputObject(OIS::OISKeyboard, true));

	// Tell mouse the size of out window
	const OIS::MouseState& mouseState = mouse->getMouseState();
	mouseState.width = window->getWidth();
	mouseState.height = window->getHeight();

	// Start listening for mouse and keyboard events
	mouse->setEventCallback(this);
	keyboard->setEventCallback(this);

	// Create a custom mouse cursor that uses Ogre overlays
	cursor = new MouseCursor();
	cursor->setImage("cursor.png");
    cursor->setWindowDimensions(window->getWidth(), window->getHeight());
	cursor->setVisible(true);

	// Create and initialize a debug screenwriter, that enables writing simple text on screen
	screen = new ScreenWriter(viewport->getActualWidth(), viewport->getActualHeight());

	// Create a new OgreNewt physics world and set some basic configuration options
	world = new OgreNewt::World(desiredPhysicsFramerate);
	world->setWorldSize(Ogre::Vector3(-250, -250, -250), Ogre::Vector3(250, 250, 250));
	world->setSolverModel(OgreNewt::World::SM_MEDIUM);// Try OgreNewt::World::SM_EXACT for precise but slower simulation
	world->setFrictionModel(OgreNewt::World::FM_EXACT);
	world->setThreadCount(2);
	world->setPlatformArchitecture(OgreNewt::World::PF_BEST_POSSIBLE);
	world->setDefaultLinearDamping(0.1f * (60.0f / (float)desiredPhysicsFramerate));
	world->setDefaultAngularDamping(Ogre::Vector3::UNIT_SCALE * 0.5f * (60.0f / (float)desiredPhysicsFramerate));

	// Initialise physics debugger
	world->getDebugger().init(sceneManager);

	// Create a new picker, hold down CTRL to use it
	picker = new Picker();
	picker->init(sceneManager, world, keyboard, mouse, 250, 10);

	// Create a primitive factory, use singleton to access it later
	factory = new PrimitiveFactory(world, sceneManager);

	// Create default material pair
	const OgreNewt::MaterialID* defaultMaterialId = world->getDefaultMaterialID();
    defaultMaterialPair = new OgreNewt::MaterialPair(world, defaultMaterialId, defaultMaterialId);

	// Set default material settings
	defaultMaterialPair->setDefaultElasticity(0.4f);
    defaultMaterialPair->setDefaultSoftness(0.05f);
    defaultMaterialPair->setDefaultFriction(0.9f, 0.6f);
    defaultMaterialPair->setDefaultCollidable(1);
    defaultMaterialPair->setContinuousCollisionMode(0);

	// Write some help info on the screen
	screen->write(-280, 20, "F1         - Toggle debug display");
	screen->write(-280, 36, "Hold CTRL  - Show cursor and pick");
	screen->write(-280, 52, "Hold SHIFT - Move faster");
	screen->write(-280, 68, "SPACE      - Throw a sphere");
	screen->write(-280, 84, "ESC        - Exit application");

	return true;
}

bool Application::setup()
{
	// Set the ammount and colour of ambient light in the scene
	sceneManager->setAmbientLight(Ogre::ColourValue(0.35f, 0.35f, 0.35f));

	// Set viewport background color
	viewport->setBackgroundColour(Ogre::ColourValue(0.5f, 0.5f, 0.5f));

	// Set camera position
	firstPersonCameraController->setPosition(Ogre::Vector3(0, 2, 20));

	// Create a simple point-light
	Ogre::Light* light = sceneManager->createLight("Light");
	light->setType(Ogre::Light::LT_POINT);
	light->setPosition(Ogre::Vector3(100, 100, 100));


	// Create a body manually
	Ogre::Entity* entity = sceneManager->createEntity("Ellipsoid", "ellipsoid.mesh");
	entity->setMaterialName("Debug/Blue");
	Ogre::SceneNode* node = sceneManager->getRootSceneNode()->createChildSceneNode("Ellipsoid");
	node->attachObject(entity);
	node->setScale(Ogre::Vector3(5, 5, 5));

	OgreNewt::ConvexCollisionPtr collision = OgreNewt::ConvexCollisionPtr(new OgreNewt::CollisionPrimitives::Ellipsoid(world, node->getScale() / 2, 1));
	OgreNewt::Body* body = new OgreNewt::Body(world, collision);
	body->attachNode(node);
	body->setPositionOrientation(Ogre::Vector3(0.0f, 5.0f, -10.0f), Ogre::Quaternion::IDENTITY);
	
	Ogre::Vector3 inertia, centerOfMass;
	collision->calculateInertialMatrix(inertia, centerOfMass);
	body->setMassMatrix(50.0f, inertia * 50.0f);
	body->setCenterOfMass(centerOfMass);
	body->setStandardForceCallback();


	// Create a body out of an Ogre node
	entity = sceneManager->createEntity("Spinner", "cylinder.mesh");
	entity->setMaterialName("Debug/Red");
	node = sceneManager->getRootSceneNode()->createChildSceneNode("Cylinder");
	node->attachObject(entity);
	node->setScale(Ogre::Vector3(7, 1, 3));
	node->setPosition(Ogre::Vector3(0, 0.5f, 0));
	OgreNewt::Body* spinner = PrimitiveFactory::getSingleton().createCylinder(node, 100);
	spinner->setAutoSleep(0);

	spinnerHinge = new OgreNewt::Hinge(spinner, NULL, node->getPosition(), Ogre::Vector3::UNIT_Y);

	// Create static tree-collision using the factory
	OgreNewt::Body* sandbox = PrimitiveFactory::getSingleton().createTreeCollision("sandbox.mesh", "sandbox", "Debug/Green", Ogre::Vector3::ZERO, Ogre::Vector3::UNIT_SCALE, Ogre::Quaternion::IDENTITY, NULL, 0, false);

	for (int x = 0; x < 5; x++)
	{
		for (int y = 0; y < 10; y++)
		{
			// Create some boxes using the factory
			PrimitiveFactory::getSingleton().createBox("", (((x % 2 == 0) ^ (y % 2 == 0)) ? "Debug/Blue" : "Debug/Red"), 1.0f, Ogre::Vector3(x - 2.25f + (y % 2 == 0 ? 0.5f : 0.0f), y * 0.5f + 1.25f, 0.0f), Ogre::Vector3(1.0f, 0.5f, 0.5f));
		}
	}


	// Lets create a simple vehicle-like object
	Ogre::Vector3 pos = Ogre::Vector3(0, 1, 10);

	// Create two frame parts
	OgreNewt::Body* rearFrame = PrimitiveFactory::getSingleton().createBox("RearFrame", "Debug/Blue", 600, pos + Ogre::Vector3(0.0f, 0.0f, 1.5f), Ogre::Vector3(2.0f, 1.0f, 2.75f));
	OgreNewt::Body* frontFrame = PrimitiveFactory::getSingleton().createBox("FrontFrame", "Debug/Blue", 600, pos + Ogre::Vector3(0.0f, 0.0f, -1.5f), Ogre::Vector3(2.0f, 1.0f, 2.75f));

	// Create the wheels
	OgreNewt::Body* wheelRR = PrimitiveFactory::getSingleton().createCylinder("WheelRR", "Debug/Red", 20, pos + Ogre::Vector3(1.35f, 0.0f, 2.0f), 1.0f, 0.6f, Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_Z));
	OgreNewt::Body* wheelRL = PrimitiveFactory::getSingleton().createCylinder("WheelRL", "Debug/Red", 20, pos + Ogre::Vector3(-1.35f, 0.0f, 2.0f), 1.0f, 0.6f, Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_Z));
	OgreNewt::Body* wheelFR = PrimitiveFactory::getSingleton().createCylinder("WheelFR", "Debug/Red", 20, pos + Ogre::Vector3(1.35f, 0.0f, -2.0f), 1.0f, 0.6f, Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_Z));
	OgreNewt::Body* wheelFL = PrimitiveFactory::getSingleton().createCylinder("WheelFL", "Debug/Red", 20, pos + Ogre::Vector3(-1.35f, 0.0f, -2.0f), 1.0f, 0.6f, Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_Z));

	// Create hinges to attach the wheels to the frame
	OgreNewt::Joint* wheelRRhinge = new OgreNewt::Hinge(wheelRR, rearFrame, pos + Ogre::Vector3(1.35f, 0.0f, 2.0f), Ogre::Vector3::UNIT_X);
	OgreNewt::Joint* wheelRLhinge = new OgreNewt::Hinge(wheelRL, rearFrame, pos + Ogre::Vector3(-1.35f, 0.0f, 2.0f), Ogre::Vector3::UNIT_X);
	OgreNewt::Joint* wheelFRhinge = new OgreNewt::Hinge(wheelFR, frontFrame, pos + Ogre::Vector3(1.35f, 0.0f, -2.0f), Ogre::Vector3::UNIT_X);
	OgreNewt::Joint* wheelFLhinge = new OgreNewt::Hinge(wheelFL, frontFrame, pos + Ogre::Vector3(-1.35f, 0.0f, -2.0f), Ogre::Vector3::UNIT_X);

	// Create a center hinge to attach the two frames and apply some limits
	OgreNewt::Hinge* centerHinge = new OgreNewt::Hinge(frontFrame, rearFrame, pos, Ogre::Vector3::UNIT_Z);
	centerHinge->enableLimits(true);
	centerHinge->setLimits(Ogre::Degree(-60), Ogre::Degree(60));

	return true;
}

void Application::run()
{
	// Mark the application running
	running = true;

	while (running)
	{
		unsigned long currentFrameTime = ogre->getTimer()->getMilliseconds();

		lastFrameDuration = currentFrameTime - simulationDuration;
		simulationDuration = currentFrameTime;

		// Pump window messages for nice behaviour
		Ogre::WindowEventUtilities::messagePump();

		// Capture input
		mouse->capture();
		keyboard->capture();

		// Check whether CTRL is being held down
		if (keyboard->isKeyDown(OIS::KC_LCONTROL) || keyboard->isKeyDown(OIS::KC_RCONTROL))
		{
			// If so, make the cursor visible and enable picking
			cursor->setVisible(true);
			picker->update(camera);
		}
		else
		{
			// Hide the cursor and update first person camera
			cursor->setVisible(false);

			firstPersonCameraController->update(keyboard, mouse, lastFrameDuration);

			Ogre::Vector3 newCameraPosition;
			Ogre::Quaternion newCameraOrientation;

			firstPersonCameraController->getPositionOrientation(newCameraPosition, newCameraOrientation);

			camera->setPosition(newCameraPosition);
			camera->setOrientation(newCameraOrientation);
		}

		// Update the physics world. This interpolates object positions and only actually
		// updates the physics if needed as set by OgreNewt::World::setUpdateFPS. Method returns
		// the number of physics updates performed.
		world->update((float)lastFrameDuration / 1000.0f);

		// Apply desired omega of 5deg/s for the spinning platform
		spinnerHinge->setDesiredOmega(Ogre::Degree(5));

		// Show the debug display if active (F1 to toggle)
		if (debuggerActive)
		{
			world->getDebugger().showDebugInformation();
		}

		// Write current FPS
		screen->write(20, 20, "FPS: %.0f", window->getStatistics().lastFPS);
		screen->write(20, 36, "Simulation duration: %02d:%02d", simulationDuration / 1000 / 60, (simulationDuration / 1000) % 60);
		
		// Update the screenwriter
		screen->update();

		// Render a frame
		ogre->renderOneFrame();
	}
}

bool Application::mouseMoved(const OIS::MouseEvent &evt)
{
	if(cursor != NULL)
	{
		// Update cursor position
		cursor->updatePosition(evt.state.X.abs, evt.state.Y.abs);
	}

	return true;
}

bool Application::mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID)
{
	return true;
}

bool Application::mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID)
{
	return true;
}

bool Application::keyPressed(const OIS::KeyEvent &evt)
{
	return true;
}

bool Application::keyReleased(const OIS::KeyEvent &evt)
{
	if (evt.key == OIS::KC_ESCAPE)
	{
		// Request shutdown
		running = false;
	} 
	else if (evt.key == OIS::KC_F1)
	{
		// Toggle debug display
		if (debuggerActive)
		{
			debuggerActive = false;
			world->getDebugger().hideDebugInformation();
		}
		else
		{
			debuggerActive = true;
		}
	}
	else if (evt.key == OIS::KC_F2)
	{
		for (int x = 0; x < 10; x++)
		{
			for (int y = 0; y < 10; y++)
			{
				PrimitiveFactory::getSingleton().createBox("", (((x % 2 == 0) ^ (y % 2 == 0)) ? "Debug/Blue" : "Debug/Red"), 1.0f, Ogre::Vector3(x * 2.0f - 5.0f + (y % 2 == 0 ? 1.0f : 0.0f), y + 0.5f, -25.0f), Ogre::Vector3(2.0f, 1.0f, 1.0f));
			}
		}
	}
	else if (evt.key == OIS::KC_SPACE)
	{
		// Make a sphere and add impulse in the direction of the camera
		OgreNewt::Body* ball = PrimitiveFactory::getSingleton().createEllipsoid("", "", 5);
		ball->setPositionOrientation(camera->getPosition(), camera->getOrientation());
		ball->setContinuousCollisionMode(1);
		ball->addImpulse(camera->getDirection() * 30.0f, camera->getPosition());
	}

	return true;
}

void Application::windowResized(Ogre::RenderWindow* rw) 
{
	if (screen)
	{
		// Update screenwriter extents
		screen->updateWindowExtents(rw->getWidth(), rw->getHeight());
	}

	if (mouse)
	{
		// Notify OIS of new window size
		const OIS::MouseState& mouseState = mouse->getMouseState();
		mouseState.width = rw->getWidth();
		mouseState.height = rw->getHeight();
	}

	if (cursor)
	{
		// Also notify the cursor
		cursor->setWindowDimensions(rw->getWidth(), rw->getHeight());
	}

	if (camera)
	{
		// Correct the aspect ratio
		camera->setAspectRatio((float)rw->getWidth() / (float)rw->getHeight());
	}
}

void Application::windowClosed(Ogre::RenderWindow* rw) 
{
	// Request shutdown when renderwindow is closed
	running = false;
}