//	GameCore.cpp - Class encapsulating the core of the game logic.
//  ----------------------------------------------------------------------------
//	This file is part of 'GameTemplate', template code for a game.
//	Copyright (C) 2012  Niall Moody
//	
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#include "generators/PickupGenerator.h"
#include "pickups/PickupAutopilot.h"
#include "drawing/Drawer.h"
#include "TonesSounder.h"
#include "PitchFactory.h"
#include "DebugHeaders.h"
#include "GameCore.h"

#include <sstream>
#include <algorithm>

using namespace std;

//------------------------------------------------------------------------------
GameCore::GameCore(TonesSounderPtr sound):
pure(false),
zen(false),
numWarnings(0),
railInterp(1.0f),
globalSpeed(0.5f),
slowDownCount(181.0f),
slowDown(0.0f),
railsPulse(0.0f),
dontDrawPlayer(false),
allTonesAuto(false),
secondsRemaining(300),
gameOverCount(30),
callback(0),
sounder(sound)
{
	numRails = sounder->getNumPitches();
	oldNumRails = numRails;
	numRailsPending = numRails;

	generator.setPureMode(pure);
}

//------------------------------------------------------------------------------
GameCore::~GameCore()
{
	sounder->setGlobalPitch(1.0f);
}

//------------------------------------------------------------------------------
void GameCore::setPure(bool val)
{
	pure = val;
}

//------------------------------------------------------------------------------
void GameCore::setZen(bool val)
{
	zen = val;
}

//------------------------------------------------------------------------------
void GameCore::setGameCounter(shared_ptr<GameCounter>& counter)
{
	gameCounter = counter;
}

//------------------------------------------------------------------------------
void GameCore::setNumRails(int val)
{
	numRails = val;
	oldNumRails = val;
	numRailsPending = val;
}

//------------------------------------------------------------------------------
void GameCore::tick()
{
	int i, j;
	TwoFloats playerPos;
	stringstream tempstr;
	bool zenSlowDown = false;
	float tempSpeed = globalSpeed;
	float prevSlowDownCount = slowDownCount;

	//Slowly increase the global speed.
	if(zen)
	{
		if(globalSpeed < 1.0f)
			globalSpeed += 0.0001f;
		else if(globalSpeed < 1.33f)
			globalSpeed += 0.00005f;
	}
	else
	{
		if(globalSpeed < 1.0f)
			globalSpeed += 0.0005f;
		else if(globalSpeed < 2.0f)
			globalSpeed += 0.0001f;
		else if(globalSpeed < 4.0f)
			globalSpeed += 0.00005f;
	}

	//Handle any crash-triggered slowdown.
	if(slowDownCount < 180.0f)
	{
		if(slowDownCount < 30)
			slowDown = slowDownCount/30.0f;
		/*else if(slowDownCount == 150.0f)
			sounder->setGlobalPitch(1.0f);*/
		else if(slowDownCount > 150.0f)
			slowDown = 1.0f - ((slowDownCount-150.0f)/30.0f);
		else
			slowDown = 1.0f;

		tempSpeed = (1.0f-slowDown) * globalSpeed;
		tempSpeed += slowDown * 0.25f;

		slowDownCount += 1.0f;
		if((prevSlowDownCount < 150.0f) && (slowDownCount >= 150.0f))
			sounder->setGlobalPitch(1.0f);
	}
	else
	{
		tempSpeed = globalSpeed;
		slowDown = 0.0f;
	}

	//Delete any inactive Tones.
	for(i=((int)tones.size()-1);i>=0;--i)
	{
		if((tones[i]->getPosition().x < (-64-512)) ||
			(tones[i]->getPosition().x > (1280+128)))
		{
			toneTypes[tones[i]->getName()] = toneTypes[tones[i]->getName()]-1;
			tones.erase(tones.begin()+i);
		}
	}

	//Add new Tones.
	{
		int tempNumRails = numRails;

		vector<TonePtr>& newTones = generator.tick(tones,
												   toneTypes,
												   pickups,
												   numWarnings,
												   tempNumRails,
												   tempSpeed,
												   numRailsPending,
												   sounder);

		if(tempNumRails != numRails)
		{
			numRailsPending = tempNumRails;
			if(zen && (numRailsPending < 5))
				numRailsPending = 5;
		}

		for(i=0;i<(int)newTones.size();++i)
		{
			newTones[i]->setGameCounter(gameCounter);
			if(allTonesAuto)
				newTones[i]->setOverrideBehaviour(shared_ptr<OverrideBehaviour>(new BehaviourAutopilotTitle()));
			tones.push_back(newTones[i]);
			toneTypes[newTones[i]->getName()] = toneTypes[newTones[i]->getName()]+1;
		}

		//Handle the number of rails changing if it's been triggered.
		//if((numRails != oldNumRails) && (railInterp >= 1.0f))
		if((numRailsPending > numRails) && (railInterp >= 1.0f))
		{
			numRails = numRailsPending;

			sounder->setPitches(PitchFactory::getInstance().getPitches(numRails));

			for(i=0;i<(int)tones.size();++i)
				tones[i]->setNumRails((float)numRails);

			railInterp = 0.0f;
		}
		else if((numRailsPending < numRails) && (railInterp >= 1.0f))
		{
			for(i=0;i<(int)tones.size();++i)
				tones[i]->setNumRailsPending(numRailsPending);
		}
	}
		
	//Check if we're waiting for some of the rails to become free of Tones.
	if(numRailsPending < numRails)
	{
		bool stillWaiting = false;

		for(i=0;i<(int)tones.size();++i)
		{
			if(tones[i]->getRail() >= numRailsPending)
			{
				stillWaiting = true;
				break;
			}
		}

		if(!stillWaiting)
		{
			numRails = numRailsPending;

			sounder->setPitches(PitchFactory::getInstance().getPitches(numRails));

			for(i=0;i<(int)tones.size();++i)
				tones[i]->setNumRails((float)numRails);

			railInterp = 0.0f;
		}
	}

	//Interpolate to the new number of rails if necessary.
	if(railInterp < 1.0f)
	{
		railInterp += 0.02f;
		if(railInterp >= 1.0f)
		{
			railInterp = 1.0f;
			oldNumRails = numRails;
		}

		/*for(i=0;i<(int)tones.size();++i)
			tones[i]->setNumRails((float)oldNumRails + railInterp);*/
	}

	//Move Tones.
	for(i=0;i<(int)tones.size();++i)
		tones[i]->move(tempSpeed, tones);

	//Collide Tones.
	if(railInterp >= 1.0f) //Tones are invincible while the number of rails is changing.
		collisionDetection();

	//Check if any Tones should be in warning state.
	numWarnings = 0;
	for(i=0;i<(int)tones.size();++i)
	{
		float dist;
		TonePtr warningTone;
		bool warning = false;
		float warningDistance;
		int rail = tones[i]->getRail();
		float toneSpeed = tones[i]->getSpeed();
		float warningDistanceI, warningDistanceJ;
		const wstring& toneName = tones[i]->getName();
		const TwoFloats& tonePos = tones[i]->getPosition();

		if(tones[i]->getNewRail() != rail)
			rail = tones[i]->getNewRail();

		if(tones[i]->getSpeed() < 0.0f)
			warningDistanceI = 350.0f;
		else
			warningDistanceI = 250.0f;

		//Skip this Tone if it's outside the screen.
		if((toneSpeed > 0.0f) && (tonePos.x < 0.0f))
			continue;
		else if((toneSpeed < 0.0f) && (tonePos.x > 1280.0f))
			continue;

		//Check if this Tone is in danger of crashing into another.
		for(j=0;j<(int)tones.size();++j)
		{
			float twoSpeed = tones[j]->getSpeed();
			const TwoFloats& twoPos = tones[j]->getPosition();

			if(tones[j]->getSpeed() < 0.0f)
				warningDistanceJ = 350.0f;
			else
				warningDistanceJ = 250.0f;

			if(warningDistanceJ > warningDistanceI)
				warningDistance = warningDistanceJ;
			else
				warningDistance = warningDistanceI;

			//Skip this Tone if it's outside the screen.
			if((twoSpeed > 0.0f) && (twoPos.x < 0.0f))
				continue;
			else if((twoSpeed < 0.0f) && (twoPos.x > 1280.0f))
				continue;

			if(i != j)
			{
				dist = tonePos.getDistance(twoPos);

				//Check if they're on the same rail, within 250px, and not
				//the same type.
				if(((tones[j]->getRail() == rail) ||
					(tones[j]->getNewRail() == rail)) &&
					(dist < warningDistance) &&
					(toneName != tones[j]->getName()))
				{
					//Depending on which direction they're moving, check
					//they've not already passed each other.
					if(toneSpeed > 0.0f)
					{
						if((toneSpeed > twoSpeed) &&
							(tonePos.x > twoPos.x))
						{
							warning = true;
								
							//In zen mode we slow down before collisions.
							if(dist < 75.0f)
								zenSlowDown = true;

							break;
						}
						else if((toneSpeed < twoSpeed) &&
								(tonePos.x < twoPos.x))
						{
							warning = true;
								
							//In zen mode we slow down before collisions.
							if(dist < 75.0f)
								zenSlowDown = true;

							break;
						}
					}
					else
					{
						warning = true;

						//In zen mode we slow down before collisions.
						if(dist < 75.0f)
							zenSlowDown = true;

						break;
					}
				}
			}
		}
		if((unsigned int)j < tones.size())
		{
			warningTone = tones[j];
				
			if(warning = true)
				++numWarnings;
		}
		tones[i]->setWarning(warning, warningTone);
	}
	if(zen && zenSlowDown)
	{
		if(slowDownCount >= 180.0f)
		{
			slowDownCount = 0.0f;
			sounder->setGlobalPitch(0.5f);
		}
		else if(slowDownCount >= 150.0f)
			slowDownCount -= 30.0f;
	}
	else if(zen && !zenSlowDown && (slowDownCount < 150.0f))
	{
		slowDownCount = 150.0f;
		sounder->setGlobalPitch(1.0f);
	}

	//Update SynthSingleton's global pitch.
	//sounder->setGlobalPitch(0.5f + ((1.0f-slowDown)*0.5f));
}

//------------------------------------------------------------------------------
void GameCore::draw(DrawerPtr& d, float timestepInterp)
{
	float y;
	int i, j;
	float alpha;
	float oldRailDist;
	float newRailDist;
	TwoFloats playerPos;
	ThreeFloats railCol;
	const TwoFloats origin;

	playerPos = player.getPosition();

	//Draw the background.
	d->fillRect(origin, d->getDrawingAreaSize(), ThreeFloats(1.0f-slowDown));

	y = 64;
	oldRailDist = (592.0f/(float)(oldNumRails-1));
	newRailDist = (592.0f/(float)(numRails-1));
	railCol.x = 0.85f;
	railCol.y = 0.85f;
	railCol.z = 0.85f;
	for(i=0;i<numRailsPending;++i)
	{
		d->drawLine(L"",
					TwoFloats(0.0f, y),
					TwoFloats(d->getDrawingAreaSize().x, y),
					railCol);
		y += (railInterp * newRailDist) + ((1.0f-railInterp) * oldRailDist);
	}
	railCol.x = 0.6640625f * 1.5f;
	railCol.y = 0.25f * 1.5f;
	railCol.z = 0.25f * 1.5f;
	alpha = (sinf(railsPulse)*0.5f)+0.5f;
	railsPulse += 0.1f;
	if(railsPulse > 6.283185f)
		railsPulse = 0.0f;
	for(i=numRailsPending;i<numRails;++i)
	{
		d->drawLine(L"",
					TwoFloats(0.0f, y),
					TwoFloats(d->getDrawingAreaSize().x, y),
					railCol,
					alpha);
		y += (railInterp * newRailDist) + ((1.0f-railInterp) * oldRailDist);
	}

	//Draw the Tones.
	for(i=0;i<(int)tones.size();++i)
	{
		bool warning = false;
		int rail = tones[i]->getRail();
		wstring toneName = tones[i]->getName();
		TwoFloats tonePos = tones[i]->getPosition();

		if(tones[i]->getNewRail() != rail)
			rail = tones[i]->getNewRail();

		//Check if this Tone is in danger of crashing into another.
		for(j=0;j<(int)tones.size();++j)
		{
			if(i != j)
			{
				if(((tones[j]->getRail() == rail) ||
				    (tones[j]->getNewRail() == rail)) &&
					(tonePos.getDistance(tones[j]->getPosition()) < 250.0f) &&
					(toneName != tones[j]->getName()))
				{
					warning = true;
					break;
				}
			}
		}

		tones[i]->draw(d, timestepInterp, playerPos);
	}

	//Draw the Pickups.
	for(i=0;i<(int)pickups.size();++i)
		pickups[i]->draw(d, timestepInterp);

	//Draw any crashes.
	for(i=((int)crashes.size()-1);i>=0;--i)
	{
		if(crashes[i]->draw(d, timestepInterp, globalSpeed))
			crashes.erase(crashes.begin()+i);
	}

	if(!dontDrawPlayer)
		player.draw(d);
}

//------------------------------------------------------------------------------
void GameCore::setPause(bool val, bool kill)
{
	int i;

	//Pause all the dots.
	for(i=0;i<(int)tones.size();++i)
	{
		tones[i]->pause(val);

		if(kill)
		{
			tones[i]->killTone();
			tones[i]->setWarning(false, TonePtr());
		}
	}
}

//------------------------------------------------------------------------------
void GameCore::setGlobalSpeed(float val)
{
	globalSpeed = val;
}

//------------------------------------------------------------------------------
void GameCore::setDontDrawPlayer(bool val)
{
	dontDrawPlayer = val;
}

//------------------------------------------------------------------------------
void GameCore::setAllTonesAuto(bool val)
{
	allTonesAuto = val;
}

//------------------------------------------------------------------------------
void GameCore::mouseDown(int button, const TwoFloats& pos)
{
	float tempf;
	unsigned int i;
	map<float, TonePtr> validTones;
	const TwoFloats playerPos = player.getPosition();

	for(i=0;i<tones.size();++i)
	{
		tempf = tones[i]->getPosition().getDistance(playerPos);

		if(tempf < Tone::Radius)
			validTones.insert(make_pair(tempf, tones[i]));
	}

	if(validTones.size() > 0)
	{
		//Maps are sorted, so the closest dot is the first entry in validDots.
		swipingTone = validTones.begin()->second;
		swipingTone->startSwipe(pos);
	}
}

//------------------------------------------------------------------------------
void GameCore::mouseUp(int button, const TwoFloats& pos)
{
	if(swipingTone)
	{
		//Make sure the dot's not been deleted.
		if(find(tones.begin(), tones.end(), swipingTone) != tones.end())
			swipingTone->endSwipe(pos);

		swipingTone.reset();
	}
}

//------------------------------------------------------------------------------
void GameCore::mouseMove(const TwoFloats& pos)
{
	player.mouseMove(pos);

	if(swipingTone)
	{
		//Make sure the dot's not been deleted.
		if(find(tones.begin(), tones.end(), swipingTone) != tones.end())
			swipingTone->swipe(pos);
		else
			swipingTone.reset();
	}
}

//------------------------------------------------------------------------------
void GameCore::setCallback(Callback *c)
{
	callback = c;
}

//------------------------------------------------------------------------------
void GameCore::collisionDetection()
{
	int i, j;
	float dist;
	wstring tempstr;
	TwoFloats tone1Pos;
	vector<int> tonesToDelete;

	for(i=0;i<(int)tones.size();++i)
	{
		tone1Pos = tones[i]->getPosition();

		if(tone1Pos.x < 8)
			continue;

		//Check if it's collided with any other tones.
		for(j=(i+1);j<(int)tones.size();++j)
		{
			dist = tones[j]->getPosition().getDistance(tone1Pos);

			if(dist < 15)
			{
				if(find(tonesToDelete.begin(), tonesToDelete.end(), i) == tonesToDelete.end())
					tonesToDelete.push_back(i);
				if(find(tonesToDelete.begin(), tonesToDelete.end(), j) == tonesToDelete.end())
					tonesToDelete.push_back(j);

				tempstr = tones[i]->getName();
				tempstr = tones[j]->getName();

				crashes.push_back(CrashPtr(NiallNew Crash(tone1Pos.x, tone1Pos.y, tones[j]->getColour())));
				tones[j]->setCollided();

				if(callback)
					callback->toneCollision();
				slowDownCount = 0;
				sounder->setGlobalPitch(0.5f);

				break;
			}
		}

		//Check if it's collided with any Pickups.
		for(j=((int)pickups.size()-1);j>=0;--j)
		{
			dist = pickups[j]->getPosition().getDistance(tone1Pos);

			if(dist < 15)
			{
				tones[i]->setOverrideBehaviour(pickups[j]->getBehaviour());
				pickups.erase(pickups.begin()+j);
			}
		}
	}

	//Make sure the indices of the tones we're deleting are in ascending order
	//or we'll end up deleting the wrong ones.
	sort(tonesToDelete.begin(), tonesToDelete.end());
	for(i=((int)tonesToDelete.size()-1);i>=0;--i)
	{
		wstring tempTone = tones[tonesToDelete[i]]->getName();
		int tempint = toneTypes[tempTone];
		toneTypes[tempTone] = toneTypes[tempTone]-1;
		tempint = toneTypes[tempTone];
		tones.erase(tones.begin()+tonesToDelete[i]);
	}
}
