//	Survival.cpp - Survival game mode.
//  ----------------------------------------------------------------------------
//	This file is part of 'GameTemplate', template code for a game.
//	Copyright (C) 2011  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/>.
//	----------------------------------------------------------------------------

#ifdef DEBUG
#pragma warning(disable : 4786) //Stupid MS headers...
#endif

#include "Survival.h"
#include "NiallsDrawingLib/Drawer.h"
#include "NiallsDrawingLib/ImageLoader.h"
#include "Images.h"
#include "SynthSingleton.h"
#include "Random.h"
#include "Crash.h"
#include "Dot.h"
#include "GameCounter.h"

#include <sstream>
#include <algorithm>

using namespace std;

//------------------------------------------------------------------------------
uint32_t Survival::crashImage = 0;
uint32_t Survival::gameOverImage = 0;
uint32_t Survival::font = 0;

//------------------------------------------------------------------------------
Survival::Survival(Application *app):
GameState(app),
dotCount(5),
dotCountLength(30.0f),
numLives(3),
gameOverCount(30),
swipingDot(0)
{
	numRails = SynthSingleton::getInstance().getNumPitches();
	GameCounter::getInstance().reset();
}

//------------------------------------------------------------------------------
Survival::~Survival()
{
	unsigned int i;

	for(i=0;i<dots.size();++i)
		delete dots[i];
	for(i=0;i<crashes.size();++i)
		delete crashes[i];
}

//------------------------------------------------------------------------------
void Survival::draw(Drawer *d)
{
	int i;
	float y;
	float dist;
	float bright;
	TwoFloats playerPos;
	stringstream tempstr;

	//Load the font.
	if(!font)
	{
		ImageLoader imLoad;
		ImageData data;

		data = imLoad.loadImage(Images::crash_png, Images::crash_pngSize);
		crashImage = d->loadImage(data.data, data.width, data.height);

		data = imLoad.loadImage(Images::gameover_png, Images::gameover_pngSize);
		gameOverImage = d->loadImage(data.data, data.width, data.height);

		font = d->loadFont("AlteHaasGroteskBold.ttf", 48.0f);
	}

	if(numLives > 0)
	{
		//Delete any inactive dots.
		for(i=((int)dots.size()-1);i>=0;--i)
		{
			if(dots[i]->getPosition().x < (-64-512))
			{
				delete dots[i];
				dots.erase(dots.begin()+i);
			}
		}

		//Add new dots.
		--dotCount;
		if(dotCount <= 0)
		{
			float speed;
			int rail = (int)(Random::getGlobal().getRandomFloat() * ((float)numRails*0.9999f));
			int tempint = (int)(Random::getGlobal().getRandomFloat() * 2.9f);

			//Check which slow and med dots are too near the spawn for us to
			//be able to spawn faster dots.
			calcSlowDotsNearSpawn();
			calcMedDotsNearSpawn();

			//In case all the rails are covered by slow OR med dots, and we
			//shouldn't spawn fast dots.
			if((int)medDotsNearSpawn.size() < numRails)
			{
				slowMedDotsNearSpawn.clear();
				for(i=0;i<(int)slowDotsNearSpawn.size();++i)
					slowMedDotsNearSpawn.push_back(slowDotsNearSpawn[i]);
				for(i=0;i<(int)medDotsNearSpawn.size();++i)
				{
					if(find(slowMedDotsNearSpawn.begin(),
							slowMedDotsNearSpawn.end(),
							medDotsNearSpawn[i]) == slowMedDotsNearSpawn.end())
					{
						slowMedDotsNearSpawn.push_back(medDotsNearSpawn[i]);
					}
				}
			}

			if((int)slowDotsNearSpawn.size() >= numRails)
				tempint = 0;
			else if((int)medDotsNearSpawn.size() >= numRails)
				tempint = (int)(Random::getGlobal().getRandomFloat() * 1.9f);
			else if((int)slowMedDotsNearSpawn.size() >= numRails)
				tempint = (int)(Random::getGlobal().getRandomFloat() * 1.9f);

			if(tempint == 1)
			{
				while(find(slowDotsNearSpawn.begin(), slowDotsNearSpawn.end(), rail) != slowDotsNearSpawn.end())
				{
					++rail;
					rail %= numRails;
				}
			}
			else if(tempint == 2)
			{
				while(find(slowMedDotsNearSpawn.begin(), slowMedDotsNearSpawn.end(), rail) != slowMedDotsNearSpawn.end())
				{
					++rail;
					rail %= numRails;
				}
			}

			switch(tempint)
			{
				case 0:
					speed = 2.0f;
					break;
				case 1:
					speed = 4.0f;
					break;
				case 2:
					speed = 8.0f;
					break;
			}
			dots.push_back(new Dot(rail, numRails, speed));

			dotCount = (int)(Random::getGlobal().getRandomFloat() * 29.999f) + (int)dotCountLength;
			//dotCount += dotCountLength;
			dotCountLength *= 0.995f;
		}

		//Deflect/attract dots.
		if(player.getDeflecting() && !player.getAttracting())
			deflectDots();
		else if(player.getAttracting() && !player.getDeflecting())
			attractDots();

		//Collide dots.
		collisionDetection();
		calcNearby();
	}
	else if(gameOverCount)
	{
		if(gameOverCount == 30)
		{
			//Pause all the dots.
			for(i=0;i<(int)dots.size();++i)
			{
				dots[i]->pause();
				dots[i]->killTone();
			}
		}

		--gameOverCount;
	}

	playerPos = player.getPosition();

	//Draw the background.
	d->fillRect(0, 0, 1280, 720, 255, 255, 255, 255);

	y = 64;
	for(i=0;i<numRails;++i)
	{
		d->drawLine(0, y, 1280, y, 0.85f, 0.85f, 0.85f);
		y += (592.0f/(float)(numRails-1));
	}

	//Draw the dots.
	for(i=0;i<(int)dots.size();++i)
	{
		dist = dots[i]->getPosition().getDistance(playerPos);
		if(dist > Dot::Radius)
			bright = 1.0f;
		else
			bright = 1.75f;
		dots[i]->draw(d, bright);
	}

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

	player.draw(d);

	//Draw the score.
	tempstr << GameCounter::getInstance().getScore();
	d->drawText("Score:", font, 20, 10, 512, 1.0f, 0.5f, 0.5f, 0.5f);
	d->drawText(tempstr.str(), font, 175, 12, 512, 1.0f, 0.75f, 0.75f, 0.75f);

	//Draw the player's remaining lives.
	d->drawText("Lives:", font, 940, 10, 512, 1.0f, 0.5f, 0.5f, 0.5f);
	if(numLives > 0)
	{
		d->drawImage(crashImage,
					 1075,
					 5,
					 0.5f,
					 0.5f,
					 0.75f,
					 0.75f,
					 0.75f);
	}
	if(numLives > 1)
	{
		d->drawImage(crashImage,
					 1140,
					 5,
					 0.5f,
					 0.5f,
					 0.75f,
					 0.75f,
					 0.75f);
	}
	if(numLives > 2)
	{
		d->drawImage(crashImage,
					 1205,
					 5,
					 0.5f,
					 0.5f,
					 0.75f,
					 0.75f,
					 0.75f);
	}

	//Draw the game over text.
	if(!numLives)
	{
		d->drawImage(gameOverImage, 246, 299);
	}
}

//------------------------------------------------------------------------------
void Survival::keyDown(const string& action, const SDL_keysym& keyPressed)
{
	if(action == "Escape")
		nextState("TitleScreen");
}

//------------------------------------------------------------------------------
void Survival::mouseDown(int button, int posX, int posY)
{
	if(numLives > 0)
	{
		float tempf;
		unsigned int i;
		map<float, Dot *> validDots;
		const TwoFloats playerPos = player.getPosition();

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

			if(tempf < Dot::Radius)
				validDots.insert(make_pair(tempf, dots[i]));
		}

		if(validDots.size() > 0)
		{
			//Maps are sorted, so the closest dot is the first entry in validDots.
			swipingDot = validDots.begin()->second;
			swipingDot->startSwipe(TwoFloats(posX, posY));
		}
	}
	else if(!gameOverCount)
		nextState("TitleScreen");
}

//------------------------------------------------------------------------------
void Survival::mouseUp(int button, int posX, int posY)
{
	if(swipingDot)
	{
		//Make sure the dot's not been deleted.
		if(find(dots.begin(), dots.end(), swipingDot) != dots.end())
			swipingDot->endSwipe(TwoFloats(posX, posY));

		swipingDot = 0;
	}
}

//------------------------------------------------------------------------------
void Survival::mouseMove(int posX, int posY)
{
	player.mouseMove((float)posX, (float)posY);

	if(swipingDot)
	{
		//Make sure the dot's not been deleted.
		if(find(dots.begin(), dots.end(), swipingDot) != dots.end())
			swipingDot->swipe(TwoFloats(posX, posY));
		else
			swipingDot = 0;
	}
}

//------------------------------------------------------------------------------
void Survival::deflectDots()
{
	unsigned int i;
	float distance;
	const TwoFloats& playerPos = player.getPosition();

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

		if(distance < 96.0f)
			dots[i]->deflect(playerPos);
	}
}

//------------------------------------------------------------------------------
void Survival::attractDots()
{
	unsigned int i;
	float distance;
	const TwoFloats& playerPos = player.getPosition();

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

		if(distance < 96.0f)
			dots[i]->attract(playerPos);
	}
}

//------------------------------------------------------------------------------
void Survival::collisionDetection()
{
	int i, j;
	float dist;
	TwoFloats dot1Pos;
	vector<int> dotsToDelete;

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

		if(dot1Pos.x < 8)
			continue;

		for(j=(i+1);j<(int)dots.size();++j)
		{
			dist = dots[j]->getPosition().getDistance(dot1Pos);

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

				crashes.push_back(new Crash(dot1Pos.x, dot1Pos.y, dots[j]->getColour()));
				dots[j]->setCollided();

				--numLives;

				break;
			}
		}
	}

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

//------------------------------------------------------------------------------
void Survival::calcNearby()
{
	float dist;
	float tempf;
	unsigned int i, j;

	for(i=0;i<dots.size();++i)
	{
		tempf = 1.0f;
		for(j=0;j<dots.size();++j)
		{
			if(j != i)
			{
				dist = dots[i]->getPosition().getDistance(dots[j]->getPosition());
				if(dist < 256.0f)
					tempf += 1.0f - (dist/256.0f);
			}
		}
		//dots[i]->setNearby(1.0f/tempf);
	}
}

//------------------------------------------------------------------------------
void Survival::calcSlowDotsNearSpawn()
{
	int rail;
	unsigned int i;
	const float safeZone = 1280.0f - 280.0f;

	slowDotsNearSpawn.clear();

	for(i=0;i<dots.size();++i)
	{
		if(dots[i]->getSpeed() < 4.0f)
		{
			if(dots[i]->getPosition().x > safeZone)
			{
				rail = dots[i]->getRail();
				if(find(slowDotsNearSpawn.begin(),
						slowDotsNearSpawn.end(),
						rail) == slowDotsNearSpawn.end())
				{
					slowDotsNearSpawn.push_back(rail);
				}
			}
		}
	}
}

//------------------------------------------------------------------------------
void Survival::calcMedDotsNearSpawn()
{
	int rail;
	unsigned int i;
	const float safeZone = 1280.0f - 280.0f;

	medDotsNearSpawn.clear();

	for(i=0;i<dots.size();++i)
	{
		if((dots[i]->getSpeed() > 2.0f) && (dots[i]->getSpeed() < 8.0f))
		{
			if(dots[i]->getPosition().x > safeZone)
			{
				rail = dots[i]->getRail();
				if(find(medDotsNearSpawn.begin(),
						medDotsNearSpawn.end(),
						rail) == medDotsNearSpawn.end())
				{
					medDotsNearSpawn.push_back(rail);
				}
			}
		}
	}
}
