//	MetaGenerator.cpp - Generator which incorporates other Generators, and
//					    higher-level events.
//  ----------------------------------------------------------------------------
//	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/>.
//	----------------------------------------------------------------------------

#include "PickupGenerator.h"
#include "../ToneFactory.h"
#include "MetaGenerator.h"
#include "PitchFactory.h"
#include "TonesSounder.h"
#include "Random.h"

using namespace std;

//------------------------------------------------------------------------------
MetaGenerator::MetaGenerator():
ToneGenerator(),
countToggle(true)
{
	counter = CountMinLength +
			  (int)(Random::getGlobal().getRandomFloat() * (float)CountRandRange);
}

//------------------------------------------------------------------------------
MetaGenerator::~MetaGenerator()
{

}

//------------------------------------------------------------------------------
vector<TonePtr>& MetaGenerator::tick(const vector<TonePtr>& tonesInPlay,
								     const map<wstring, int>& toneTypesInPlay,
								     vector<PickupPtr>& pickupsInPlay,
									 int numWarnings,
								     int& numRails,
									 float globalSpeed,
									 int maxRailStart,
									 shared_ptr<TonesSounder>& sounder)
{
	float tempf;

	tickReturn.clear();

	//Check if we're due a special event.
	--counter;
	if(counter <= 0)
	{
		tempf = Random::getGlobal().getRandomFloat();
		//If we want to generate a special Tone...
		//if(tempf > 0.33f)
		if((tempf > 0.5f) || (countToggle && !pure))
		{
			TonePtr newTone(getSpecialTone(tonesInPlay,
										   toneTypesInPlay,
										   pickupsInPlay,
										   numWarnings,
										   numRails,
										   globalSpeed,
										   maxRailStart,
										   sounder));

			if(newTone)
			{
				tickReturn.push_back(newTone);

				//Only reset counter if a Tone was actually generated, so we
				//can have another shot in the next frame.
				counter = CountMinLength +
						  (int)(Random::getGlobal().getRandomFloat() *
								(float)CountRandRange);
				countToggle = !countToggle;
			}
		}
		//...or trigger a puzzle sequence...
		/*else if(tempf > 0.66f)
		{

		}*/
		//...or trigger a change in the number of rails/pitches.
		else if((maxRailStart == numRails) && !countToggle)
		{
			int newNumRails = numRails;

			//Select a new number of rails.
			while(newNumRails == numRails)
				newNumRails = (int)(Random::getGlobal().getRandomFloat() * 4.99f) + 4;
			numRails = newNumRails;

			//Obtain the new pitches.
			//sounder->setPitches(PitchFactory::getInstance().getPitches(numRails));

			//Reset counter.
			counter = CountMinLength +
					  (int)(Random::getGlobal().getRandomFloat() *
							(float)CountRandRange);
			countToggle = !countToggle;
		}
		else if(countToggle)
		{
			//Reset counter.
			counter = CountMinLength +
					  (int)(Random::getGlobal().getRandomFloat() *
							(float)CountRandRange);
			countToggle = !countToggle;
		}

		return tickReturn;
	}
	else
	{
		return defaultGenerator.tick(tonesInPlay,
									 toneTypesInPlay,
									 pickupsInPlay,
									 numWarnings,
									 numRails,
									 globalSpeed,
									 maxRailStart,
									 sounder);
	}
}

//------------------------------------------------------------------------------
void MetaGenerator::setPureMode(bool val)
{
	pure = val;
}

//------------------------------------------------------------------------------
TonePtr MetaGenerator::getSpecialTone(const vector<TonePtr>& tonesInPlay,
									  const map<wstring, int>& toneTypesInPlay,
								      std::vector<PickupPtr>& pickupsInPlay,
									  int numWarnings,
									  int numRails,
									  float globalSpeed,
									  int maxRailStart,
									  shared_ptr<TonesSounder>& sounder)
{
	TonePtr retval;

	int firstRail;
	int railIndex;
	int firstCreator;
	int creatorIndex;

	const vector<shared_ptr<ToneCreator> >& creators = ToneFactory::getInstance().getSpecialToneCreators();

	//Pick a random ToneCreator.
	firstCreator = (int)(Random::getGlobal().getRandomFloat() *
						 ((float)creators.size() - 0.001f));
	creatorIndex = firstCreator;

	//Loop through each ToneCreator in creators until we've generated a new Tone.
	while(!retval)
	{
		bool nextCreator = false;
		map<wstring, int>::const_iterator typesIt;

		//If we're in Pure mode, we shouldn't be generating DropperTones.
		if(pure && (creators[creatorIndex]->getCarriesPickup()))
			nextCreator = true;
		//Check there aren't too many warnings for this Tone type to
		//be generated.
		else if(numWarnings >= creators[creatorIndex]->getMaxNumWarnings())
			nextCreator = true;
		else
		{
			//Loop through the tone types currently in play and check there aren't
			//too many according to ToneCreator::getMaxNumOtherTones().
			for(typesIt=toneTypesInPlay.begin();
				typesIt!=toneTypesInPlay.end();
				++typesIt)
			{
				int maxOther = creators[creatorIndex]->getMaxNumOtherTones(typesIt->first);

				if((maxOther > -1) && (typesIt->second > maxOther))
				{
					nextCreator = true;
					break;
				}
			}
		}

		if(!nextCreator)
		{
			//Pick a random rail.
			firstRail = (int)(Random::getGlobal().getRandomFloat() *
								((float)maxRailStart - 0.001f));
			railIndex = firstRail;

			//This bit's slightly different depending on which side of
			//the screen the Tone's supposed to start from.
			if(creators[creatorIndex]->getStartingSide()) //Right.
			{
				vector<TonePtr>::const_iterator toneIt;
				int minDistance = (int)((1.0f/globalSpeed) *
										(float)creators[creatorIndex]->getMinDistanceToOtherTones());
				if(minDistance > 800)
					minDistance = 800;

				//Loop through each rail until we find one we can use.
				while(!retval)
				{
					bool nextRail = false;

					//Go through all the Tones in play and break if we
					//find one on this rail that's too close to the
					//start point.
					for(toneIt=tonesInPlay.begin();
						toneIt!=tonesInPlay.end();
						++toneIt)
					{
						const TonePtr& tempTone = *toneIt;
						int tempDist = minDistance;

						//Tones travelling left-right require a larger
						//gap or it's too unfair.
						if(tempTone->getSpeed() < 0.0f)
							tempDist += (int)((float)minDistance * 4 * globalSpeed);

						//If this Tone is within minDistance we can't
						//use this rail.
						if((tempTone->getRail() == railIndex) &&
							((int)(1280.0f - tempTone->getPosition().x) < tempDist))
						{
							nextRail = true;
							break;
						}
					}

					//If we haven't set nextRail, we can create a new
					//Tone and return it.
					if(!nextRail)
					{
						retval = creators[creatorIndex]->createTone(railIndex,
																	numRails,
																	sounder);
						if(creators[creatorIndex]->getCarriesPickup())
						{
							PickupGenerator gen;
							PickupPtr pickup = gen.generate();

							pickup->setTone(retval);
							retval->setPickup(pickup);
							pickupsInPlay.push_back(pickup);
						}
					}
					else
					{
						++railIndex;
						railIndex %= maxRailStart;
						if(railIndex == firstRail)
							break;
					}
				}
			}
			else //Left.
			{
				vector<TonePtr>::const_iterator toneIt;
				int minDistance = (int)((1.0f/globalSpeed) *
										(float)creators[creatorIndex]->getMinDistanceToOtherTones());
				if(minDistance > 800)
					minDistance = 800;

				//Loop through each rail until we find one we can use.
				while(!retval)
				{
					bool nextRail = false;

					//Go through all the Tones in play and break if we
					//find one on this rail that's too close to the
					//start point.
					for(toneIt=tonesInPlay.begin();
						toneIt!=tonesInPlay.end();
						++toneIt)
					{
						const TonePtr& tempTone = *toneIt;

						//If this Tone is within minDistance we can't
						//use this rail.
						if((tempTone->getRail() == railIndex) &&
							(tempTone->getPosition().x < minDistance) &&
							(tempTone->getPosition().x > 8))
						{
							nextRail = true;
							break;
						}
					}

					//If we haven't set nextRail, we can create a new
					//Tone and return it.
					if(!nextRail)
					{
						retval = creators[creatorIndex]->createTone(railIndex,
																	numRails,
																	sounder);
					}
					else
					{
						++railIndex;
						railIndex %= maxRailStart;
						if(railIndex == firstRail)
							break;
					}
				}
			}
		}

		//If we get to this stage we need to try the next ToneCreator along.
		++creatorIndex;
		creatorIndex %= creators.size();
		if(creatorIndex == firstCreator)
			break;
	}

	return retval;
}
