#include "Game.h"
#include "GameCode.h"

#include <OgreRoot.h>
#include <OgreRenderWindow.h>
#include <OgreSceneManager.h>
#include <OgreCamera.h>
#include <OgreViewport.h>
#include <OgreWindowEventUtilities.h>
#include <OgreResourceGroupManager.h>
#include <OgreLight.h>
#include <OIS/OIS.h>
#include <OgreEntity.h>
#include <OgreMaterialManager.h>
#include <OgreSubMesh.h>

#include "OgreOverlay.h"
#include "OgreOverlayManager.h"
#include "OgreOverlayContainer.h"
#include "OgreTextAreaOverlayElement.h"
#include "OgreStringConverter.h"

#include "Player.h"
#include "Enemy.h"
#include "DefenseWall.h"
#include "functions.h"
#include "SoundManager.h"

#include <Windows.h>

#ifdef _DEBUG
#	define OGRE_FILE_PLUGINS "OgrePlugins_d.cfg"
#	define OGRE_FILE_LOG "Ogre_d.log"
#	define OGRE_FILE_CONFIG "OgreVideo_d.cfg"
#else
#	define OGRE_FILE_PLUGINS "OgrePlugins.cfg"
#	define OGRE_FILE_LOG "Ogre.log"
#	define OGRE_FILE_CONFIG "OgreVideo.cfg"
#endif
#define OGRE_DATA_DIRECTORY "./Data/"
#define OGRE_DATA_GROUP "Media"

using namespace Engine;
using namespace Ogre;
using namespace OIS;

#define R_UP Vector3(400,180,0)
#define R_UPMIDDLE Vector3(400,125,0)
#define R_DOWNMIDDLE Vector3(400,60,0)
#define R_DOWN Vector3(400,-5,0)

#define L_UP Vector3(-400,180,0)
#define L_UPMIDDLE Vector3(-400,125,0)
#define L_DOWNMIDDLE Vector3(-400,60,0)
#define L_DOWN Vector3(-400,-5,0)

Game* Engine::Singleton<Game>::mInstance = 0;
SceneManager*	Game::mOgreSceneManager = 0;
Mouse*			Game::mMouse = 0;
Keyboard*		Game::mKeyboard = 0;

Enemy**			Game::mEnemies = 0;
Player*			Game::mPlayer = 0;
CollisionBox*	Game::mBase = 0;
DefenseWall**	Game::mDefenseWalls = 0;
CollisionBox**  Game::mLevelBoxes = 0;
int				Game::mNumLevelBoxes = 0;
float			Game::mSurvivalTime = 0;
int				Game::mGameState = 0;
SoundManager*	Game::SoundMgr = 0;

// Constructor
Game::Game()
: mOgreRoot(0), mOgreWindow(0), mOgreCamera(0), mOgreViewport(0)
{
}

// Destructor
Game::~Game()
{
	if (mOgreViewport != 0)
	{
		mOgreWindow->removeViewport(mOgreViewport->getZOrder());
		mOgreViewport = 0;
	}
	if (mOgreCamera != 0)
	{
		mOgreSceneManager->destroyCamera(mOgreCamera);
		mOgreCamera = 0;
	}
	if (mOgreSceneManager != 0)
	{
		mOgreSceneManager->clearScene();
		mOgreRoot->destroySceneManager(mOgreSceneManager);
		mOgreSceneManager = 0;
	}
	if (mOgreRoot != 0)
	{
		delete mOgreRoot;
	}
}

// Methods
bool Game::initialize()
{
	// Initialize Ogre root
    mOgreRoot = new Root(OGRE_FILE_PLUGINS, OGRE_FILE_CONFIG, OGRE_FILE_LOG);

	// Show configuration dialog
	if (!mOgreRoot->showConfigDialog())
	{
		delete mOgreRoot;
		mOgreRoot = 0;
		return false;
	}

	// Initialize window
	mOgreWindow = mOgreRoot->initialise(true, "Ogre");
	mOgreSceneManager = mOgreRoot->createSceneManager(ST_GENERIC);
	mOgreCamera = mOgreSceneManager->createCamera("Camera");
	mOgreViewport = mOgreWindow->addViewport(mOgreCamera);

	// Add resource path
	ResourceGroupManager::getSingleton().addResourceLocation(OGRE_DATA_DIRECTORY, "FileSystem", OGRE_DATA_GROUP);
	ResourceGroupManager::getSingleton().addResourceLocation("./Data/Sound", "FileSystem","General",true);
	ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

	// Setup Input
	ParamList pl;
	size_t windowHnd = 0;
	std::ostringstream windowHndStr;
	mOgreWindow->getCustomAttribute("WINDOW", &windowHnd);
	windowHndStr << windowHnd;
	pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
	mInputManager = InputManager::createInputSystem( pl );

	Game::mKeyboard = static_cast<Keyboard*>(mInputManager->createInputObject( OISKeyboard, true));
	Game::mMouse = static_cast<Mouse*>(mInputManager->createInputObject( OISMouse, true ));

	mGameState = GS_INTRO;

	// Setup a few basic lights
	mOgreSceneManager->setAmbientLight(ColourValue(0.5f, 0.5f, 0.5f));
	mOgreSceneManager->setShadowTechnique(SHADOWTYPE_STENCIL_ADDITIVE);
	Light* light = mOgreSceneManager->createLight("Light");
	light->setType(Light::LT_DIRECTIONAL);
	light->setDiffuseColour(0.9f, 0.9f, 0.9f);
	light->setSpecularColour(1.0f, 1.0f, 1.0f);
	light->setDirection(0, -0.3f, -0.6f);

	// Set the camera to it's default position
	mZoomDist = 100;

	mOgreCamera->setNearClipDistance(1);
	mOgreCamera->setFarClipDistance(1000);
	mOgreCamera->setPosition(0, 10, mZoomDist);
	mOgreCamera->lookAt(0, 10, 0);


	SceneNode* levelNode = mOgreSceneManager->getRootSceneNode()->createChildSceneNode();

	Entity* level = mOgreSceneManager->createEntity("levelpart", "DieHeimat.mesh");
	levelNode->attachObject(level);

	//ShowCursor(false);

	mEnemies = new Enemy*[MAXENEMIES];
	mDefenseWalls = new DefenseWall*[MAXWALLS];
	mLevelBoxes = new CollisionBox*[MAXLEVELOBJECTS];

	mPlayer = new Player();

	mBase = new CollisionBox();
	mBase->mPosition = Vector3::ZERO;
	mBase->mSpeed = Vector3::ZERO;
	mBase->mWidth = 25;
	mBase->mHeight = 500;
	mBase->mType = 3;
	mBase->onGround = true;

	mNumEvents = 0;
	mEventListPos = 0;


	for(int i = 0; i < FIREMENSTART; i++)
	{
		mEnemies[i] = new Enemy(i,0);
	}
	for(int i = FIREMENSTART; i < OGRESTART; i++)
	{
		mEnemies[i] = new Enemy(i,1);
	}
	for(int i = OGRESTART; i < MAXENEMIES; i++)
	{
		mEnemies[i] = new Enemy(i,2);
	}

	for(int i = 0; i < MAXWALLS; i++)
	{
		mDefenseWalls[i] = new DefenseWall(i);
	}

	mNumLevelBoxes = 0;

//base
	AddLevelObject(Vector3(0,-40,0),225,40);
	AddLevelObject(Vector3(243.75f+400,-30,0),262.5f+800,20);
	AddLevelObject(Vector3((243.75f+400)*-1,-30,0),262.5f+800,20);
//right part
	float MFAdd = 107-72.5f; //maikels fault!
	AddLevelObject(Vector3(72.5f+MFAdd,45,0),45,10);
	AddLevelObject(Vector3(147.75f+MFAdd,45,0),55,10);
	AddLevelObject(Vector3(217.5f+MFAdd,45,0),45,10);
	AddLevelObject(Vector3(320+400+MFAdd,45,0),110+800,10);
	AddLevelObject(Vector3(97.5f+MFAdd,110,0),45,10);
	AddLevelObject(Vector3(180+MFAdd,110,0),70,10);
	AddLevelObject(Vector3(270+MFAdd,110,0),60,10);
	AddLevelObject(Vector3(347.75f+400+MFAdd,110,0),55+800,10);
	AddLevelObject(Vector3(252.5f+MFAdd,165,0),45,10);
	AddLevelObject(Vector3(337.5f+400+MFAdd,165,0),75+800,10);

	AddLevelObject(Vector3((72.5f+MFAdd)*-1,45,0),45,10);
	AddLevelObject(Vector3((147.75f+MFAdd)*-1,45,0),55,10);
	AddLevelObject(Vector3((217.5f+MFAdd)*-1,45,0),45,10);
	AddLevelObject(Vector3((320+400+MFAdd)*-1,45,0),110+800,10);
	AddLevelObject(Vector3((97.5f+MFAdd)*-1,110,0),45,10);
	AddLevelObject(Vector3((180+MFAdd)*-1,110,0),70,10);
	AddLevelObject(Vector3((270+MFAdd)*-1,110,0),60,10);
	AddLevelObject(Vector3((347.75f+400+MFAdd)*-1,110,0),55+800,10);
	AddLevelObject(Vector3((252.5f+MFAdd)*-1,165,0),45,10);
	AddLevelObject(Vector3((337.5f+400+MFAdd)*-1,165,0),75+800,10);

	AddEvent(15000,0,L_DOWN);
	AddEvent(15110,0,L_DOWN);

	AddEvent(30000,0,R_DOWN);
	AddEvent(30110,0,R_DOWN);


for(int i = 0; i < 2; i ++)
{
	AddEvent(55100,0,R_DOWN);
	AddEvent(55210,0,R_DOWN);
	AddEvent(55310,0,L_DOWN);
	AddEvent(55400,0,L_DOWN);

	AddEvent(70500,0,R_DOWN);
	AddEvent(70600,1,L_DOWN);

	AddEvent(90700,1,R_DOWN);
	AddEvent(90800,1,L_DOWN);

	AddEvent(110100,1,R_DOWN);
	AddEvent(110200,0,L_DOWNMIDDLE);
	AddEvent(110310,0,L_DOWNMIDDLE);
	AddEvent(110420,0,L_DOWNMIDDLE);

	AddEvent(130000,2,R_DOWN);

	AddEvent(160000,1,R_DOWN);
	AddEvent(160100,1,L_DOWN);
	AddEvent(160200,0,L_DOWNMIDDLE);
	AddEvent(160300,0,L_DOWNMIDDLE);
	AddEvent(160400,0,L_DOWNMIDDLE);
	AddEvent(160500,0,R_DOWNMIDDLE);
	AddEvent(160600,0,R_DOWNMIDDLE);
	AddEvent(160700,0,R_DOWNMIDDLE);

	AddEvent(200100,0,L_DOWNMIDDLE);
	AddEvent(200210,0,L_DOWNMIDDLE);
	AddEvent(200300,0,L_UPMIDDLE);
	AddEvent(200410,0,L_UPMIDDLE);
	AddEvent(200500,0,R_DOWNMIDDLE);
	AddEvent(200610,0,R_DOWNMIDDLE);
	AddEvent(200700,0,R_UPMIDDLE);
	AddEvent(200810,0,R_UPMIDDLE);
	AddEvent(200900,1,R_DOWN);
	AddEvent(200000,2,L_DOWN);
	AddEvent(20100,0,L_UP);
	AddEvent(201010,0,L_UP);
	AddEvent(201220,0,L_UP);
	AddEvent(201330,0,L_UP);
	AddEvent(201400,0,R_UP);
	AddEvent(201510,0,R_UP);
	AddEvent(201620,0,R_UP);
	AddEvent(201030,0,R_UP);

	
	for(int j = 0; j < 2; j ++)
	{
		AddEvent(201100+i*500,0,L_DOWNMIDDLE);
		AddEvent(202210+i*500,0,L_DOWNMIDDLE);
		AddEvent(203300+i*500,0,L_UPMIDDLE);
		AddEvent(204410+i*500,0,L_UPMIDDLE);
		AddEvent(205500+i*500,0,R_DOWNMIDDLE);
		AddEvent(206610+i*500,0,R_DOWNMIDDLE);
		AddEvent(207700+i*500,0,R_UPMIDDLE);
		AddEvent(208810+i*500,0,R_UPMIDDLE);
		AddEvent(209900+i*500,1,R_DOWN);
		AddEvent(210000+i*500,2,L_DOWN);
		AddEvent(211000+i*500,0,L_UP);
		AddEvent(212010+i*500,0,L_UP);
		AddEvent(213220+i*500,0,L_UP);
		AddEvent(214330+i*500,0,L_UP);
		AddEvent(215400+i*500,0,R_UP);
		AddEvent(216510+i*500,0,R_UP);
		AddEvent(217620+i*500,0,R_UP);
		AddEvent(218030+i*500,0,R_UP);
	}
}



	Ogre::OverlayManager& overlayManager = Ogre::OverlayManager::getSingleton();

	float aspect = 1.33f;

	// Create a panel
	Ogre::OverlayContainer* PressToCharge = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_PressToCharge"));
	PressToCharge->setMetricsMode(Ogre::GMM_RELATIVE);
	PressToCharge->setPosition(0.3f,0.9f);
	PressToCharge->setDimensions(0.4f,0.1f*aspect);
	PressToCharge->setMaterialName("PressToCharge");

	Ogre::OverlayContainer* PressToRelease = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_PressToRelease"));
	PressToRelease->setMetricsMode(Ogre::GMM_RELATIVE);
	PressToRelease->setPosition(0.3f,0.9f);
	PressToRelease->setDimensions(0.4f,0.1f*aspect);
	PressToRelease->setMaterialName("PressToRelease");

	Ogre::OverlayContainer* GoodEnergyBar = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_GoodEnergy"));
	GoodEnergyBar->setMetricsMode(Ogre::GMM_PIXELS);
	GoodEnergyBar->setPosition(10,10);
	GoodEnergyBar->setDimensions(400,20);
	GoodEnergyBar->setMaterialName("GoodEnergyBar");

	Ogre::OverlayContainer* BadEnergyBar = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_BadEnergy"));
	BadEnergyBar->setMetricsMode(Ogre::GMM_PIXELS);
	BadEnergyBar->setPosition(10,40);
	BadEnergyBar->setDimensions(400,20);
	BadEnergyBar->setMaterialName("BadEnergyBar");

	Ogre::OverlayContainer* HealthBar = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_HealthBar"));
	HealthBar->setMetricsMode(Ogre::GMM_PIXELS);
	HealthBar->setPosition(10,70);
	HealthBar->setDimensions(400,20);
	HealthBar->setMaterialName("HealthBar");

	// Create a panel
	TextPanel = static_cast<OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "TextPanel"));
	TextPanel->setMetricsMode(GMM_RELATIVE);
	TextPanel->setPosition(0, 0);
	TextPanel->setDimensions(1, 1);

	// Create a text area
	TextAreaOverlayElement* textArea = static_cast<TextAreaOverlayElement*>(
		overlayManager.createOverlayElement("TextArea", "TimeText"));
	textArea->setMetricsMode(GMM_PIXELS);
	textArea->setPosition(10, 100);
	textArea->setDimensions(100, 100);
	textArea->setCaption("Survival Time: "+Ogre::StringConverter::toString(mSurvivalTime));
	textArea->setCharHeight(20);
	textArea->setFontName("TrebuchetMSBold");
	textArea->setColourBottom(ColourValue(0.0, 0.0, 0.0));
	textArea->setColourTop(ColourValue(0.2, 0.2, 0.2));
	TextPanel->addChild(textArea);

	IntroMovie = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_IntroMovie"));
	IntroMovie->setMetricsMode(Ogre::GMM_RELATIVE);
	IntroMovie->setPosition(0,0);
	IntroMovie->setDimensions(1.0f,1.0f);
	IntroMovie->setMaterialName("IntroMovie");

	Credits = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_Credits"));
	Credits->setMetricsMode(Ogre::GMM_RELATIVE);
	Credits->setPosition(0,0);
	Credits->setDimensions(1.0f,1.0f);
	Credits->setMaterialName("Credits");

	MainMenu = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_MainMenu"));
	MainMenu->setMetricsMode(Ogre::GMM_RELATIVE);
	MainMenu->setPosition(0,0);
	MainMenu->setDimensions(1.0f,1.0f);
	MainMenu->setMaterialName("MainMenu");

	PauseMenu = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_PauseMenu"));
	PauseMenu->setMetricsMode(Ogre::GMM_RELATIVE);
	PauseMenu->setPosition(0,0);
	PauseMenu->setDimensions(1.0f,1.0f);
	PauseMenu->setMaterialName("PauseMenu");

	GameOverMenu = static_cast<Ogre::OverlayContainer*>(
		overlayManager.createOverlayElement("Panel", "GUI_GameOverMenu"));
	GameOverMenu->setMetricsMode(Ogre::GMM_RELATIVE);
	GameOverMenu->setPosition(0,0);
	GameOverMenu->setDimensions(1.0f,1.0f);
	GameOverMenu->setMaterialName("GameOverMenu");

	// Create an overlay, and add the panel
	Ogre::Overlay* overlay = overlayManager.create("OverlayName");

	overlay->add2D(PressToCharge);
	overlay->add2D(PressToRelease);
	overlay->add2D(GoodEnergyBar);
	overlay->add2D(BadEnergyBar);
	overlay->add2D(HealthBar);

	overlay->add2D(IntroMovie);
	overlay->add2D(MainMenu);
	overlay->add2D(PauseMenu);
	overlay->add2D(GameOverMenu);
	overlay->add2D(Credits);

	overlay->add2D(TextPanel);



	IntroMovie->show();
	MainMenu->hide();
	PauseMenu->hide();
	GameOverMenu->hide();
	Credits->hide();

	TextPanel->hide();

	PressToCharge->hide();
	PressToRelease->hide();

	// Show the overlay
	overlay->show();

	//init sound!
	
	return true;
}

void Game::run(GameCode* gameCode)
{
	while (true)
	{
		mKeyboard->capture();
		mMouse->capture();

		if (mGameState == GS_INTRO)
		{
			if(mKeyboard->isKeyDown(KC_RETURN))
			{
				mGameState = GS_MAINMENU;
				IntroMovie->hide();
				MainMenu->show();
			}
		}
		else if (mGameState == GS_MAINMENU)
		{
			if(mKeyboard->isKeyDown(KC_SPACE))
			{
				mGameState = GS_GAME;
				MainMenu->hide();
				TextPanel->show();
			}
		}
		else if (mGameState == GS_GAME)
		{
			// Update GameCode
			//gameCode->update();
			// Render

			if(mKeyboard->isKeyDown(KC_ESCAPE))
			{
				mGameState = GS_PAUSE;
				PauseMenu->show();
			}

			//Need to capture/update each device

			if(mKeyboard->isKeyDown(KC_UP))
				mZoomDist-=2;
			if(mKeyboard->isKeyDown(KC_DOWN))
				mZoomDist+=2;

			mPlayer->Update();

			for(int i = 0; i < MAXENEMIES; i++)
			{
				mEnemies[i]->Update();
			}

			for(int i = 0; i < MAXWALLS; i++)
			{
				if(mDefenseWalls[i]->mActive != 0)
					mDefenseWalls[i]->Update();
			}

			if(mPlayer->mHealth <= 0)
			{
				Sleep(500);
				mGameState = GS_GAMEOVER;
				GameOverMenu->show();

				TextPanel->setPosition(0.4f,0.4f);
			}

			mOgreCamera->move((mPlayer->mPosition-(mOgreCamera->getPosition()-Vector3(0,20,mZoomDist)))*0.02f);
			mOgreCamera->lookAt(mPlayer->mPosition);

			mSurvivalTime += 16.6666666666667f;
			OverlayManager& overlayManager = OverlayManager::getSingleton();
			OverlayElement* panel = overlayManager.getOverlayElement("TimeText");
			panel->setCaption("Survival Time: "+Ogre::StringConverter::toString((int)(mSurvivalTime/60000.0f))+":"+Ogre::StringConverter::toString(((int)(mSurvivalTime/10000.0f))%6)+Ogre::StringConverter::toString(((int)(mSurvivalTime/1000.0f))%10));

			int ISurvivalTime = (int)mSurvivalTime;
			if(mEventListPos != mNumEvents)
			{
				if (ISurvivalTime >= mEvents[mEventListPos]->mTime)
				{
					mEvents[mEventListPos]->Do();
					mEventListPos++;
				}
			}

			if (!mOgreWindow->isActive() && mOgreWindow->isVisible()) 
			{
				mOgreViewport->getTarget()->update(true);
			}

			const RenderTarget::FrameStats& stats = mOgreWindow->getStatistics();
			//stats.lastFPS;

			HWND windowHnd = 0;
			mOgreWindow->getCustomAttribute("WINDOW", &windowHnd);

			char t[256];
			sprintf(t,"Cardboard Carnival - FPS: %.0f",stats.lastFPS);
			SetWindowTextA(windowHnd, t);
		}
		else if (mGameState == GS_PAUSE)
		{
			if(mKeyboard->isKeyDown(KC_RETURN))
			{
				mGameState = GS_GAME;
				PauseMenu->hide();
			}
			if(mKeyboard->isKeyDown(KC_Q))
			{
				mGameState = GS_CREDITS;
				Credits->show();
				PauseMenu->hide();
			}
		}
		else if (mGameState == GS_GAMEOVER)
		{
			if(mKeyboard->isKeyDown(KC_SPACE))
			{
				mGameState = GS_CREDITS;
				Credits->show();
				GameOverMenu->hide();
				OverlayManager& overlayManager = OverlayManager::getSingleton();
				OverlayElement* panel = overlayManager.getOverlayElement("TimeText");
				panel->hide();
			}
		}
		else if (mGameState == GS_CREDITS)
		{
			if(mKeyboard->isKeyDown(KC_ESCAPE))
			{
				exit(-1);
			}
		}

		WindowEventUtilities::messagePump();
		if (mOgreWindow->isClosed()) break;
		mOgreRoot->renderOneFrame();
	}

}

void Game::AddLevelObject(Vector3 aPos, float aWidth, float aHeight)
{
	mLevelBoxes[mNumLevelBoxes] = new CollisionBox();
	mLevelBoxes[mNumLevelBoxes]->mPosition = aPos;
	mLevelBoxes[mNumLevelBoxes]->mSpeed = Vector3::ZERO;
	mLevelBoxes[mNumLevelBoxes]->mWidth = aWidth;
	mLevelBoxes[mNumLevelBoxes]->mHeight = aHeight;
	mLevelBoxes[mNumLevelBoxes]->mType = 0;
	mLevelBoxes[mNumLevelBoxes]->onGround = true;
	mNumLevelBoxes++;
}

void Game::AddEvent(int aTime, int aType, Ogre::Vector3 aPos)
{
	mEvents[mNumEvents] = new GameEvent(aTime*0.75f,aType,aPos);
	mNumEvents++;
}

GameEvent::GameEvent(int aTime, int aType, Vector3 aPos)
{
	mTime = aTime;
	mType = aType;
	mPos = aPos;
}

void GameEvent::Do()
{
	int start = 0; 
	int end = 0;

	if(mType == 0)
	{
		start = 0;
		end = FIREMENSTART;
	}
	else if(mType == 1)
	{
		start = FIREMENSTART;
		end = OGRESTART;
	}
	else if(mType == 2)
	{
		start = OGRESTART;
		end = MAXENEMIES;
	}

	for(int i = start; i < end; i++) //get an empty enemy
	{
		if(!Game::mEnemies[i]->mActive)
		{
			Game::mEnemies[i]->mDir = -sign(mPos.x);
			Game::mEnemies[i]->mPosition = mPos;
			Game::mEnemies[i]->mActive = true;
			Game::mEnemies[i]->mNode->setVisible(true);
			break;
		}
	}
}