/*
 *  Original work: Copyright (c) 2014, Oculus VR, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  RakNet License.txt file in the licenses directory of this source tree. An additional grant 
 *  of patent rights can be found in the RakNet Patents.txt file in the same directory.
 *
 *
 *  Modified work: Copyright (c) 2017-2020, SLikeSoft UG (haftungsbeschränkt)
 *
 *  This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style
 *  license found in the license.txt file in the root directory of this source tree.
 */

// Demonstrates how to lag a client in the past using the interpolation history class,
// in order to get smooth visuals despite choppy input.
// Start two instances on the same computer, press 's' on one instance, 'c' on the other.
// Hold down space to see the actual networking.
// Change SERVER_IP_ADDRESS to connect over the internet

#ifdef WIN32
#include "slikenet/WindowsIncludes.h"
#else
#define HWND void*
#endif

// Ogre includes
#include "OgreTextAreaOverlayElement.h"
#include "Ogre.h"
#include <OIS.h>

// Stuff to help run ogre
#include "OverlayHelper.h"
#include "App3D.h"

// RakNet includes
#include "slikenet/GetTime.h"
#include "slikenet/sleep.h"
#include "slikenet/assert.h"
#include "slikenet/StringTable.h"
#include "slikenet/peerinterface.h"

#include "slikenet/BitStream.h"
#include "slikenet/MessageIdentifiers.h"
#include "slikenet/ReplicaManager3.h"
#include "slikenet/NetworkIDManager.h"
#include "slikenet/sleep.h"
#include "slikenet/FormatString.h"
#include "slikenet/StringCompressor.h"
#include "slikenet/Rand.h"
#include "TransformationHistory.h"

using namespace Ogre;
using namespace DataStructures;
using namespace OIS;
using namespace SLNet;

class Popcorn;

// Network variables
static const char *SERVER_IP_ADDRESS="127.0.0.1";
static const unsigned short SERVER_PORT=12345;
// How often the server sends position updates to the client
static const int DEFAULT_SERVER_MILLISECONDS_BETWEEN_UPDATES=250;

// Demo variables
static const int MIN_KERNELS=100;
static const int KERNELS_VARIANCE=60;
static const SLNet::TimeMS POP_COUNTDOWN_MIN_DELAY_MS=1000;
static const SLNet::TimeMS POP_COUNTDOWN_VARIANCE_MS=5000;
static const SLNet::TimeMS RESTART_TIMER_MS=14000;
static const float POSITION_VARIANCE=100.0f;
static const float PLANE_VELOCITY_VARIANCE=30.0f;
static const float UPWARD_VELOCITY_MINIMUM=35.0f;
static const float UPWARD_VELOCITY_VARIANCE=25.0f;
static const float DOWNWARD_ACCELERATION = -15.0f;

bool isServer;
Ogre::Entity *popcornKernel, *popcornPopped;
SLNet::RakPeerInterface *rakPeer;
DataStructures::List<Popcorn*> popcornList;
bool enableInterpolation;

App3D *app;

// This class represents a kernel of popcorn, which pops after a short delay
// When to pop, and the physics the popcorn takes is entirely controlled by the server
// Every DEFAULT_SERVER_MILLISECONDS_BETWEEN_UPDATES the server will send an update
// The client intentionally lags this far behind, so it always has a recent position to interpolate to
class Popcorn : public Replica3
{
public:
	Popcorn() {
		// Buffer up for 3 seconds if we were to get 30 updates per second
		transformationHistory.Init(30,3000);

		position=Ogre::Vector3::ZERO;
		orientation=Ogre::Quaternion::IDENTITY;
		// Visible position is where we are interpolating at, which is behind the real position
		visiblePosition=Ogre::Vector3::ZERO;
		visibleOrientation=Ogre::Quaternion::IDENTITY;
		isKernel=true;
	}
	virtual ~Popcorn()
	{
		if (isServer)
			BroadcastDestruction();

		app->GetSceneManager()->destroyEntity(sceneNode->getAttachedObject(0)->getName());
		app->GetSceneManager()->getRootSceneNode()->removeAndDestroyChild(sceneNode->getName());
		popcornList.RemoveAtIndex(popcornList.GetIndexOf(this));
	}

	bool isKernel;
	Ogre::Vector3 position;
	Ogre::Quaternion orientation;
	Ogre::Quaternion rotationalVelocity;
	Ogre::Vector3 velocity;
	Ogre::SceneNode *sceneNode;
	SLNet::TimeMS popCountdown;
	Ogre::Vector3 visiblePosition;
	Ogre::Quaternion visibleOrientation;
	TransformationHistory transformationHistory;

	virtual void WriteAllocationID(SLNet::Connection_RM3 *destinationConnection, SLNet::BitStream *allocationIdBitstream) const
	{
		StringTable::Instance()->EncodeString("Popcorn", 128, allocationIdBitstream);
	}
	virtual RM3ConstructionState QueryConstruction(SLNet::Connection_RM3 *destinationConnection, ReplicaManager3 *replicaManager3)
	{
		if (isServer)
			return QueryConstruction_ServerConstruction(destinationConnection, isServer);
		else
			return QueryConstruction_ClientConstruction(destinationConnection, isServer);
	}
	virtual bool QueryRemoteConstruction(SLNet::Connection_RM3 *sourceConnection){
		if (isServer)
			return QueryRemoteConstruction_ServerConstruction(sourceConnection, isServer);
		else
			return QueryRemoteConstruction_ClientConstruction(sourceConnection, isServer);
	}
	virtual void SerializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *destinationConnection){}
	virtual bool DeserializeConstruction(SLNet::BitStream *constructionBitstream, SLNet::Connection_RM3 *sourceConnection){return true;}
	virtual void SerializeDestruction(SLNet::BitStream *destructionBitstream, SLNet::Connection_RM3 *destinationConnection){}
	virtual bool DeserializeDestruction(SLNet::BitStream *destructionBitstream, SLNet::Connection_RM3 *sourceConnection){return true;}
	virtual RM3ActionOnPopConnection QueryActionOnPopConnection(SLNet::Connection_RM3 *droppedConnection) const
	{
		if (isServer)
			return QueryActionOnPopConnection_Server(droppedConnection);
		else
			return QueryActionOnPopConnection_Client(droppedConnection);
	}
	virtual void DeallocReplica(SLNet::Connection_RM3 *sourceConnection) {delete this;}
	virtual RM3QuerySerializationResult QuerySerialization(SLNet::Connection_RM3 *destinationConnection)
	{
		if (isServer)
			return QuerySerialization_ServerSerializable(destinationConnection, isServer);
		else
			return QuerySerialization_ClientSerializable(destinationConnection, isServer);
	}
	virtual RM3SerializationResult Serialize(SLNet::SerializeParameters *serializeParameters)
	{
		// Autoserialize causes a network packet to go out when any of these member variables change.
		RakAssert(isServer==true);
		serializeParameters->outputBitstream[0].Write(isKernel);
		serializeParameters->outputBitstream[0].WriteAlignedBytes((const unsigned char*)&position,sizeof(position));
		serializeParameters->outputBitstream[0].WriteAlignedBytes((const unsigned char*)&velocity,sizeof(velocity));
		serializeParameters->outputBitstream[0].WriteAlignedBytes((const unsigned char*)&orientation,sizeof(orientation));
		return RM3SR_BROADCAST_IDENTICALLY;
	}	
	virtual void Deserialize(SLNet::DeserializeParameters *deserializeParameters)
	{
		bool lastIsKernel = isKernel;

		// Doing this because we are also lagging position and orientation behind by DEFAULT_SERVER_MILLISECONDS_BETWEEN_UPDATES
		// Without it, the kernel would pop immediately but would not start moving
		deserializeParameters->serializationBitstream[0].Read(isKernel);
		if (isKernel==false && lastIsKernel==true)
		popCountdown=DEFAULT_SERVER_MILLISECONDS_BETWEEN_UPDATES;

		deserializeParameters->serializationBitstream[0].ReadAlignedBytes((unsigned char*)&position,sizeof(position));
		deserializeParameters->serializationBitstream[0].ReadAlignedBytes((unsigned char*)&velocity,sizeof(velocity));
		deserializeParameters->serializationBitstream[0].ReadAlignedBytes((unsigned char*)&orientation,sizeof(orientation));

		// Scene node starts invisible until we deserialize the intial startup data
		// This data could also have been passed in SerializeConstruction()
		sceneNode->setVisible(true,true);

		// Every time we get a network packet, we write it to the transformation history class.
		// This class, given a time in the past, can then return to us an interpolated position of where we should be in at that time
		transformationHistory.Write(position,velocity,orientation, SLNet::GetTimeMS());
	}

	virtual void SetToPopped(void)
	{
		// Change the mesh, and add some velocity.
		isKernel=false;
		if (sceneNode->getAttachedObject(0))
			app->GetSceneManager()->destroyEntity(sceneNode->getAttachedObject(0)->getName());
		sceneNode->detachAllObjects();
		sceneNode->attachObject(popcornPopped->clone(FormatString("%p",this)));
		if (isServer)
		{
			velocity.x=-PLANE_VELOCITY_VARIANCE/2.0f+frandomMT()*PLANE_VELOCITY_VARIANCE;
			velocity.y=UPWARD_VELOCITY_MINIMUM+frandomMT()*UPWARD_VELOCITY_VARIANCE;
			velocity.z=-PLANE_VELOCITY_VARIANCE/2.0f+frandomMT()*PLANE_VELOCITY_VARIANCE;
		}		
	}
	
	virtual void Update(SLNet::TimeMS timeElapsedMs)
	{
		visiblePosition=position;
		visibleOrientation=orientation;

		if (isKernel==false)
		{
			if (isServer)
			{
				// Only the server is doing physics
				float timeElapsedSec = timeElapsedMs * .001f;
				position += velocity * timeElapsedSec + .5f * Ogre::Vector3(0.0f, DOWNWARD_ACCELERATION, 0.0f) * timeElapsedSec*timeElapsedSec;;
				velocity += Ogre::Vector3(0.0f, DOWNWARD_ACCELERATION, 0.0f) * timeElapsedSec;
				orientation = Quaternion::Slerp(timeElapsedSec, orientation, orientation * rotationalVelocity, true);
			}
			else
			{
				// See above - delay the pop until we start moving
				if (popCountdown <= timeElapsedMs)
				{
					SetToPopped();
					// #med - use unsigned short max?
					popCountdown= static_cast<SLNet::TimeMS>(-1);
				}
				else
					popCountdown-=timeElapsedMs;

				// interpolate visible position, lagging behind by a small amount so where know where to update to
				if (enableInterpolation)
				{
					// Important: the first 3 parameters are in/out parameters, so set their values to the known current values before calling Read()
					// We are subtracting DEFAULT_SERVER_MILLISECONDS_BETWEEN_UPDATES from the current time to get an interpolated position in the past
					// Without this we wouldn't have a node to interpolate to, and wouldn't know where to go
					transformationHistory.Read(&visiblePosition, 0, &visibleOrientation, SLNet::GetTimeMS()-DEFAULT_SERVER_MILLISECONDS_BETWEEN_UPDATES, SLNet::GetTimeMS());
				}
			}
		}
		else
		{
			if (isServer)
			{
				if (popCountdown <= timeElapsedMs)
				{
					// Only the server controls when to pop
					SetToPopped();
				}
				else
					popCountdown-=timeElapsedMs;
				
			}
			
		}

		sceneNode->setPosition(visiblePosition);
		sceneNode->setOrientation(visibleOrientation);
	}


	static void ClearPopcorn()
	{
		// Destructor removes itself from this list
		while (popcornList.Size())
			delete popcornList[popcornList.Size()-1];
	}
	static Popcorn * CreateKernel(ReplicaManager3 *replicaManager3)
	{
		Popcorn *p = new Popcorn;
		// Tell the replication system about this new Replica instance.
		replicaManager3->Reference(p);
		static int count=0;
		count++;
		popcornList.Insert(p, _FILE_AND_LINE_ );
		p->sceneNode = app->GetSceneManager()->getRootSceneNode()->createChildSceneNode();
		p->sceneNode->attachObject(popcornKernel->clone(FormatString("%p",p)));

		// Only server sets up initial positions, etc.
		if (isServer)
		{
			p->position.x=-POSITION_VARIANCE/2.0f+frandomMT()*POSITION_VARIANCE;
			p->position.y=0.0f;
			p->position.z=-POSITION_VARIANCE/2.0f+frandomMT()*POSITION_VARIANCE;
			p->velocity=Ogre::Vector3::ZERO;
			p->popCountdown=POP_COUNTDOWN_MIN_DELAY_MS + randomMT() % POP_COUNTDOWN_VARIANCE_MS;
			p->orientation.FromAngleAxis(Ogre::Radian(frandomMT()*6.28f), Ogre::Vector3(-1.0f+frandomMT()*2.0f,-1.0f+frandomMT()*2.0f,-1.0f+frandomMT()*2.0f).normalisedCopy());
			p->rotationalVelocity.FromAngleAxis(Ogre::Radian(frandomMT()*6.28f), Ogre::Vector3(-1.0f+frandomMT()*2.0f,-1.0f+frandomMT()*2.0f,-1.0f+frandomMT()*2.0f).normalisedCopy());
			p->visiblePosition=p->position;
			p->visibleOrientation=p->orientation;
		}
		else
			p->sceneNode->setVisible(false,true);


		return p;
	}
};

// One instance of Connection_RM2 is implicitly created per connection that uses ReplicaManager2. The most important function to implement is Construct() as this creates your game objects.
// It is designed this way so you can override per-connection behavior in your own game classes
class PopcornSampleConnection : public Connection_RM3
{
public:
	PopcornSampleConnection(const SystemAddress &_systemAddress, RakNetGUID _guid) : Connection_RM3(_systemAddress,_guid) {}
	virtual ~PopcornSampleConnection() {}

	// Callback used to create objects
	// See Connection_RM2::Construct in ReplicaManager2.h for a full explanation of each parameter
	virtual Replica3 *AllocReplica(SLNet::BitStream *allocationIdBitstream, ReplicaManager3 *replicaManager3)
	{
		char objectName[128];
		StringTable::Instance()->DecodeString(objectName,128,allocationIdBitstream);
		if (strcmp(objectName,"Popcorn")==0)
		{
			return Popcorn::CreateKernel(replicaManager3);
		}
		return 0;
	}
};
class PopcornDemoRM3 : public ReplicaManager3
{
	virtual Connection_RM3* AllocConnection(const SystemAddress &systemAddress, RakNetGUID rakNetGUID) const {return new PopcornSampleConnection(systemAddress,rakNetGUID);}
	virtual void DeallocConnection(Connection_RM3 *connection) const {delete connection;}
};
PopcornDemoRM3 popcornReplicaManager3;
class ExampleApp : public App3D
{
public:
	ExampleApp()
	{
		quit=false;
	}

	~ExampleApp()
	{
	}

	void OnAppShutdown( void )
	{
		Popcorn::ClearPopcorn();

		App3D::OnAppShutdown();

		if( mInputManager )
		{
			mInputManager->destroyInputObject( mKeyboard );
			OIS::InputManager::destroyInputSystem(mInputManager);
			mInputManager = 0;
		}

		SLNet::RakPeerInterface::DestroyInstance(rakPeer);

	}

	void Render(AppTime curTimeMS)
	{
		App3D::Render(curTimeMS);
	}

	bool ShouldQuit(void) const {return quit || window->isClosed();}

	void Update(AppTime curTimeMS, AppTime elapsedTimeMS)
	{
#ifndef WIN32
		WindowEventUtilities::messagePump();
#endif

		// Update all subsystems
		App3D::Update(curTimeMS, elapsedTimeMS);
		overlayHelper.Update(elapsedTimeMS);

		mKeyboard->capture();

		Ogre::Vector3 mTranslateVector = Ogre::Vector3::ZERO;

		float mMoveSpeed=50.0f;
		float mRotateSpeed=1.0f;
		float mMoveScale = mMoveSpeed * elapsedTimeMS * .001f;
		// Take about 10 seconds for full rotation
		Ogre::Radian mRotScale(mRotateSpeed * elapsedTimeMS * .001f);

		if(mKeyboard->isKeyDown(KC_A))
			mTranslateVector.x = -mMoveScale;	// Move camera left

		if(mKeyboard->isKeyDown(KC_D))
			mTranslateVector.x = mMoveScale;	// Move camera RIGHT

		if(mKeyboard->isKeyDown(KC_UP) || mKeyboard->isKeyDown(KC_W) )
			mTranslateVector.z = -mMoveScale;	// Move camera forward

		if(mKeyboard->isKeyDown(KC_DOWN) || mKeyboard->isKeyDown(KC_S) )
			mTranslateVector.z = mMoveScale;	// Move camera backward

		if(mKeyboard->isKeyDown(KC_PGUP))
			mTranslateVector.y = mMoveScale;	// Move camera up

		if(mKeyboard->isKeyDown(KC_PGDOWN))
			mTranslateVector.y = -mMoveScale;	// Move camera down

		if(mKeyboard->isKeyDown(KC_RIGHT))
			camera->yaw(-mRotScale);

		if(mKeyboard->isKeyDown(KC_LEFT))
			camera->yaw(mRotScale);

		// Hold down space to see what it looks like without interpolation
		if(mKeyboard->isKeyDown(KC_SPACE))
			enableInterpolation=false;
		else
			enableInterpolation=true;

		static bool didUpdateDelayLastTick=false;
		bool didUpdateDelayThisTick=false;
		
		if (isStarted==false)
		{
			SLNet::SocketDescriptor sd;

			if(mKeyboard->isKeyDown(KC_S))
			{
				// Start server
				isStarted=true;
				isServer=true;
				ShowMessage("Server started");
				sd.port=SERVER_PORT;

			}

			if(mKeyboard->isKeyDown(KC_C))
			{
				isStarted=true;
				ShowMessage(FormatString("Client started, connecting to %s", SERVER_IP_ADDRESS));
				// Start server
				isStarted=true;
				isServer=false;
				sd.port=0;
			}

			if (isStarted)
			{
				// Start RakNet, up to 32 connections if the server
				rakPeer = SLNet::RakPeerInterface::GetInstance();
				StartupResult sr = rakPeer->Startup(isServer ? 32 : 1,&sd,1);
				RakAssert(sr==RAKNET_STARTED);
				rakPeer->AttachPlugin(&popcornReplicaManager3);
				popcornReplicaManager3.SetNetworkIDManager(&networkIdManager);
				//rakPeer->SetNetworkIDManager(&networkIdManager);
				// The server should allow systems to connect. Clients do not need to unless you want to use RakVoice or for some other reason want to transmit directly between systems.
				if (isServer)
				{
					rakPeer->SetMaximumIncomingConnections(32);
				}
				else
				{
					ConnectionAttemptResult car = rakPeer->Connect(SERVER_IP_ADDRESS,SERVER_PORT,0,0);
					RakAssert(car==CONNECTION_ATTEMPT_STARTED);

				}
				popcornReplicaManager3.SetAutoSerializeInterval(DEFAULT_SERVER_MILLISECONDS_BETWEEN_UPDATES);

				// StringTable has to be called after RakPeer started, or else first call StringTable::AddRef() yourself
				StringTable::Instance()->AddString("Popcorn",false);
			}
		}

		if (isStarted)
		{
			SLNet::Packet *packet;
			for (packet = rakPeer->Receive(); packet; rakPeer->DeallocatePacket(packet), packet = rakPeer->Receive())
			{
				switch (packet->data[0])
				{
				case ID_CONNECTION_ATTEMPT_FAILED:
					ShowMessage("ID_CONNECTION_ATTEMPT_FAILED\n");
					break;
				case ID_NO_FREE_INCOMING_CONNECTIONS:
					ShowMessage("ID_NO_FREE_INCOMING_CONNECTIONS\n");
					break;
				case ID_CONNECTION_REQUEST_ACCEPTED:
					ShowMessage("ID_CONNECTION_REQUEST_ACCEPTED\n");
					break;
				case ID_NEW_INCOMING_CONNECTION:
					ShowMessage(FormatString("ID_NEW_INCOMING_CONNECTION from %s\n", packet->systemAddress.ToString()));
					break;
				case ID_DISCONNECTION_NOTIFICATION:
					ShowMessage("ID_DISCONNECTION_NOTIFICATION\n");
					break;
				case ID_CONNECTION_LOST:
					ShowMessage("ID_CONNECTION_LOST\n");
					break;
				}
			}

			if (isServer)
			{
				// Restart the demo every RESTART_TIMER_MS milliseconds
				if (popcornLifetimeCountdown<=elapsedTimeMS)
				{
					Popcorn::ClearPopcorn();
					CreateKernels(&popcornReplicaManager3);
					popcornLifetimeCountdown=RESTART_TIMER_MS;
				}
				popcornLifetimeCountdown-=elapsedTimeMS;
			}


			unsigned i;
			for (i=0; i < popcornList.Size(); i++)
			{
				popcornList[i]->Update(elapsedTimeMS);
			}
		}
		
		camera->moveRelative(mTranslateVector);

		if( mKeyboard->isKeyDown(KC_ESCAPE) || mKeyboard->isKeyDown(KC_Q) )
			quit=true;
	}

	// Just Ogre startup stuff
	virtual void PostConfigure(const char *defaultResourceConfigurationPath, bool recursive)
	{
		App3D::PostConfigure(defaultResourceConfigurationPath, false);
		App3D::InitSceneManager(0);
		App3D::InitGUIManager();
		App3D::InitCamera(0);
		App3D::InitViewport(0);

		ParamList pl;
		size_t windowHnd = 0;
		std::ostringstream windowHndStr;

		window->getCustomAttribute("WINDOW", &windowHnd);
		windowHndStr << windowHnd;
		pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));

		mInputManager = InputManager::createInputSystem( pl );
		//Create all devices (We only catch joystick exceptions here, as, most people have Key/Mouse)
		mKeyboard = static_cast<Keyboard*>(mInputManager->createInputObject( OISKeyboard, false ));

		// Start the overlay helper class, which handles fading of overlays and other stuff
		overlayHelper.Startup();

		sceneManager->setAmbientLight( ColourValue( .5, .5, .5 ) );

		mainLight = sceneManager->createLight("MainLight");
		mainLightNode = sceneManager->getRootSceneNode()->createChildSceneNode( "MainLightNode" );
		mainLightNode->attachObject(mainLight);
		mainLight->setType(Light::LT_POINT);
		mainLight->setPosition(200.0f, 200.0f, 200.0f);
		
		camera->setPosition(150.0f, 150.0f, 70.0f);
		camera->lookAt(0.0f,50.0f,0.0f);
		camera->setNearClipDistance(1.0f);

		popcornKernel = sceneManager->createEntity("PopcornKernel", "PopcornKernel.mesh");
		popcornPopped = sceneManager->createEntity("PopcornPopped", "PopcornPopped.mesh");
		popcornLifetimeCountdown=0;

		sceneManager->setSkyBox(true, "Examples/SteveCubeSkyBox");
		isStarted=false;
		enableInterpolation=true;


		// Bug: Since updating to ogre OgreSDK_vc8_v1-7-1 from Ogre 3D 1.6.2, the first call to ShowMessage doesn't show up anymore
		ShowMessage("'S'erver. 'C'lient. Hold ' ' to disable interp.");
		ShowMessage("'S'erver. 'C'lient. Hold ' ' to disable interp.");
	}

	virtual void CreateKernels(ReplicaManager3 *replicaManager3)
	{
		RakAssert(isServer);
		unsigned int kernelCount;
		// #med - consider using a template function instead of the if/else construct and/or introduce a cleaner/simpler macro
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4127) // conditional expression is constant
#endif
		if (KERNELS_VARIANCE!=0)
#ifdef _MSC_VER
#pragma warning(pop)
#endif
			kernelCount = MIN_KERNELS + randomMT() % KERNELS_VARIANCE;
		else
			kernelCount = MIN_KERNELS;
		for (unsigned int i=0; i < kernelCount; i++)
			Popcorn::CreateKernel(replicaManager3);

	}

protected:
	virtual char * GetWindowTitle(void) const {return (char *)"Popcorn popper";}
	void ShowMessage(const char *msg, float timescale=1.0f)
	{
		// Create a panel
		static int count=0;
		OverlayContainer* panel =overlayHelper.CreatePanel(FormatString("%i",count++));
		panel->setMetricsMode(Ogre::GMM_PIXELS);
		panel->setPosition(10, 10);
		panel->setDimensions(100, 100);
		//panel->setMaterialName("MaterialName"); // Optional background material

		// Create a text area
		TextAreaOverlayElement *textArea = overlayHelper.CreateTextArea(FormatString("%i",count++), "BlueHighway", panel);
		textArea->setMetricsMode(Ogre::GMM_PIXELS);
		textArea->setPosition(10, 10);
		textArea->setDimensions(200, 200);
		textArea->setCaption(msg);
		textArea->setCharHeight(32);
		textArea->setColourBottom(ColourValue(0.3f, 0.5f, 0.3f));
		textArea->setColourTop(ColourValue(0.5f, 0.7f, 0.5f));

		// Destroy the children (the text area) before destroying the parents.
		overlayHelper.FadeOverlayElement(textArea, static_cast<unsigned int>(3000*timescale), static_cast<unsigned int>(1000 * timescale), 0.0f, true);
		overlayHelper.FadeOverlayElement(panel, static_cast<unsigned int>(3000 * timescale), static_cast<unsigned int>(1000 * timescale), 0.0f, true);
	}

	// Our major systems.  Note the base class ExampleApplication has all the Ogre 3D systems
	OverlayHelper overlayHelper;
	bool quit;

	SceneNode *mainLightNode;
	Light *mainLight;

	OIS::InputManager* mInputManager;
	OIS::Keyboard* mKeyboard;

	NetworkIDManager networkIdManager;
	bool isStarted;
	SLNet::TimeMS popcornLifetimeCountdown;
};


#ifdef WIN32
int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
#else
int main (int argc, char** argv)
#endif
{
	
	HWND     hWnd;
	SLNet::TimeMS curTime, lastTime, elapsed;
	app = new ExampleApp;
	app->PreConfigure();
	if (app->Configure()==false)
	{
		delete app;
		return 0;
	}
#ifdef WIN32
	app->window->getCustomAttribute("HWND", &hWnd);
	MSG msg;
#else
	app->window->getCustomAttribute("GLXWINDOW", &hWnd);
#endif

	app->PostConfigure("resources.cfg",false);
	lastTime= SLNet::GetTimeMS();

	while (app->ShouldQuit()==false)
	{
		curTime= SLNet::GetTimeMS();
		elapsed = curTime-lastTime;
		if (elapsed > 100)
			elapsed=100; // Spike limiter
		app->Update(curTime, elapsed);
		lastTime=curTime;
		app->Render(curTime);
#ifdef WIN32
		if (PeekMessage( &msg, nullptr, 0U, 0U, PM_REMOVE )>0)
		{
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
#endif
		// Make sure the RakNet thread runs
		RakSleep(0);
	}

	app->OnAppShutdown();

	delete app;

	return 0;
}
