//	Tone.cpp - Base class for all Tone types.
//  ----------------------------------------------------------------------------
//	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 "Tone.h"
#include "GameCounter.h"
#include "DebugHeaders.h"
#include "TonesSounder.h"
#include "drawing/Drawer.h"
#include "OverrideBehaviour.h"

#include <cmath>
#include <cassert>
#include <sstream>

using namespace std;

//------------------------------------------------------------------------------
const float Tone::Radius = 96.0f;
//ofstream Tone::logFile("C:\\Users\\Niall\\Programming\\Applications\\Tones\\log.txt");

//------------------------------------------------------------------------------
Tone::Tone(int startRail,
		   float startNumRails,
		   const ThreeFloats& col,
		   float speed,
		   TonesSounderPtr& sound):
rail(startRail),
numRails(startNumRails),
prevNumRails(numRails),
railInterp(1.0f),
numRailsPending((int)startNumRails),
newRail(startRail),
movingRail(0.0f),
colour(col),
speed(speed),
textAboveCount(0),
textBelowCount(0),
warning(false),
warningScale(0.0f),
swiping(false),
swipeStart(0.0f),
swipeCount(0),
synthIndex(-1),
noteOff(false),
collided(false),
isPaused(false),
sounder(sound)
{
	int i;
	int trailInc = 2;
	int intNumRails = (int)numRails;
	float fraction = numRails - (float)intNumRails;

	if(speed > 0.0f)
		position.x = 1280 + 64;
	else
	{
		position.x = -64;
		trailInc = -2;
	}

	position.y = 64.0f + (((float)rail + fraction) * (592.0f/(float)(intNumRails-1)));

	prevPos.push_back(TwoFloats(position.x+trailInc, position.y));
	lineScale.push_back(4.0f);
	lineColour.push_back(colour);
	lineAlpha.push_back(1.0f);
	for(i=1;i<TrailSize;++i)
	{
		prevPos.push_back(TwoFloats(prevPos[i-1].x+trailInc, prevPos[i-1].y));
		lineScale.push_back(4.0f);
		lineColour.push_back(colour);
		lineAlpha.push_back(1.0f - ((float)i/(float)TrailSize));
	}
}

//------------------------------------------------------------------------------
Tone::Tone(const Tone& other):
position(other.position),
rail(other.rail),
numRails(other.numRails),
prevNumRails(other.prevNumRails),
railInterp(other.railInterp),
numRailsPending(other.numRailsPending),
gameCounter(other.gameCounter),
newRail(other.rail),
movingRail(other.movingRail),
colour(other.colour),
speed(other.speed),
textAbove(other.textAbove),
textAboveCount(other.textAboveCount),
textBelow(other.textBelow),
textBelowCount(other.textBelowCount),
warning(other.warning),
warningScale(other.warningScale),
warningTone(other.warningTone),
swiping(other.swiping),
swipeStart(other.swipeStart),
swipeCount(other.swipeCount),
noteOff(other.noteOff),
collided(other.collided),
isPaused(other.isPaused),
sounder(other.sounder)
{
	int i;

	synthIndex = sounder->createCopyNote(other.synthIndex);

	for(i=0;i<TrailSize;++i)
	{
		/*prevPos[i].x = other.prevPos[i].x;
		prevPos[i].y = other.prevPos[i].y;*/
		prevPos.push_back(other.prevPos[i]);

		lineScale.push_back(4.0f);
		lineColour.push_back(colour);
		lineAlpha.push_back(1.0f - ((float)i/(float)TrailSize));
	}
}

//------------------------------------------------------------------------------
Tone::~Tone()
{
	if(!noteOff)
	{
		//logFile << "Note Off (destructor): " << synthIndex << " (" << (int)this << ")" << endl;
		sounder->noteOff(synthIndex, collided);
	}
	/*else
		logFile << "Destructor" << " (" << (int)this << ")" << endl;*/
}

//------------------------------------------------------------------------------
void Tone::move(float globalSpeed,
				vector<TonePtr>& allTones,
				bool ignoreOverride)
{
	int i;
	float tempf;
	const float oldRailDist = (592.0f/(float)(prevNumRails-1));
	const float newRailDist = (592.0f/(float)(numRails-1));
	const float railDist = (railInterp * newRailDist) + ((1.0f-railInterp) * oldRailDist);

	tick();

	if(overrideBehaviour && !ignoreOverride)
	{
		if(overrideBehaviour->moveTone(*this, allTones, globalSpeed))
			overrideBehaviour.reset();
	}
	else
	{
		if(!isPaused)
		{
			//Update dot trail.
			for(i=(TrailSize-1);i>0;--i)
				prevPos[i] = prevPos[i-1];
			prevPos[0] = position;

			//Update dot position.
			position.x -= speed * globalSpeed;
			if((((speed > 0.0f) && (position.x < 0.0f)) ||
			    ((speed < 0.0f) && (position.x > 1280.0f))) &&
			   !noteOff)
			{
				wstringstream tempstr;

				//logFile << "Note Off (outside screen): " << synthIndex << " (" << (int)this << ")" << endl;
				sounder->noteOff(synthIndex, collided);
				noteOff = true;

				if(gameCounter)
				{
					tempstr << getName() << L" Survived";
					gameCounter->incrementCount(tempstr.str(),
												(int)((float)getScore() * (0.5f+globalSpeed)));
				}
			}

			if(newRail != rail)
			{
				tempf = (cosf(movingRail)+1.0f)*0.5f; //1 -> 0

				if(newRail < rail)
				{
					tempf *= railDist;
					position.y = 64 + (newRail * railDist) + tempf;
				}
				else
				{
					tempf = 1.0f - tempf;
					tempf *= railDist;
					position.y = 64 + (rail * railDist) + tempf;
				}

				movingRail += 0.0125f * fabsf(speed);
				if(movingRail > 3.14159265f)
				{
					rail = newRail;
					position.y = 64 + (rail * railDist);
				}
			}
			else if(railInterp < 1.0f)
			{
				railInterp += 0.02f;

				if(railInterp >= 1.0f)
				{
					railInterp = 1.0f;
					prevNumRails = numRails;
				}

				position.y = 64 + (rail * railDist);
			}
		}
	}
}

//------------------------------------------------------------------------------
void Tone::draw(DrawerPtr& d, float timestepInterp, const TwoFloats& playerPos)
{
	float dist;
	float alpha;
	TwoFloats startPos;
	float bright = 1.0f;
	TwoFloats scale(1.0f);

	//Work out if the player's cursor is over this tone.
	dist = position.getDistance(playerPos);
	if(dist < Tone::Radius)
		bright = 1.75f;

	//Draw the line.
	if(prevPos[TrailSize-1].y == position.y)
	{
		scale.x = (prevPos[TrailSize-1].x - prevPos[0].x)/64.0f;

		startPos = prevPos[0];
		startPos.y -= 4.0f;
		d->drawImage(L"lineFade",
					 startPos,
					 scale,
					 colour);
	}
	else
		d->drawConnectedLine(L"", prevPos, lineScale, lineColour, lineAlpha);

	//Draw the warning if necessary.
	if(warning)
	{
		float tempf;

		tempf = 1.25f - (cosf(warningScale * 6.283185f) * 0.5f);

		d->drawImage(L"deflectWave",
					 position-(32.0f*tempf),
					 TwoFloats(tempf),
					 colour,
					 0.75f);

		warningScale += 0.05f;
		if(warningScale > 1.0f)
			warningScale = 0.0f;
	}

	//Draw the dot.
	d->drawImage(L"dot",
				 position-64,
				 TwoFloats(1.0f),
				 colour * bright);

	//Draw any override images.
	if(overrideBehaviour)
		overrideBehaviour->drawExtraInformation(d, timestepInterp, *this);

	//Draw any text.
	if(textAboveCount > 0)
	{
		if(textAboveCount > 30)
			alpha = 1.0f;
		else
			alpha = (float)textAboveCount/30.0f;

		d->drawText(textAbove,
					L"Font1",
					textAboveLayout,
					TwoFloats(position.x-128.0f, position.y-32.0f),
					256.0f,
					TextLayout::Centre,
					1.0f,
					ThreeFloats(0.5f),
					alpha);

		--textAboveCount;
	}
	if(textBelowCount > 0)
	{
		if(textBelowCount > 30)
			alpha = 1.0f;
		else
			alpha = (float)textBelowCount/30.0f;

		d->drawText(textBelow,
					L"Font1",
					textBelowLayout,
					TwoFloats(position.x-128.0f, position.y+48.0f),
					256.0f,
					TextLayout::Centre,
					1.0f,
					ThreeFloats(0.5f),
					alpha);

		--textBelowCount;
	}
}

//------------------------------------------------------------------------------
void Tone::setOverrideBehaviour(shared_ptr<OverrideBehaviour>& behave)
{
	overrideBehaviour = behave;
}

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

//------------------------------------------------------------------------------
void Tone::triggerTextAbove(const wstring& text)
{
	textAbove = text;
	textAboveLayout.clear();
	textAboveCount = 90;
}

//------------------------------------------------------------------------------
void Tone::triggerTextBelow(const wstring& text)
{
	textBelow = text;
	textBelowLayout.clear();
	textBelowCount = 90;
}

//------------------------------------------------------------------------------
void Tone::setNumRails(float val)
{
	/*int intNumRails;
	float fraction;
	float oldRailDist;
	float newRailDist;

	numRails = val;

	intNumRails = (int)numRails;
	fraction = numRails - (float)intNumRails;
	oldRailDist = (592.0f/(float)(intNumRails-1));
	newRailDist = (592.0f/(float)(intNumRails));
	//position.y = 64.0f + (((float)rail + fraction) * (592.0f/(float)(intNumRails-1)));

	position.y = 64.0f + ((float)rail * ((fraction * newRailDist) + ((1.0f-fraction) * oldRailDist)));*/
	numRails = val;
	numRailsPending = (int)val;
	railInterp = 0.0f;
}

//------------------------------------------------------------------------------
void Tone::setNumRailsPending(int val)
{
	numRailsPending = val;
}

//------------------------------------------------------------------------------
void Tone::setColour(const ThreeFloats& col)
{
	colour = col;
}

//------------------------------------------------------------------------------
void Tone::setSpeed(float val)
{
	speed = val;
}

//------------------------------------------------------------------------------
void Tone::setWarning(bool val, TonePtr otherTone)
{
	warning = val;
	if(!otherTone)
		warningTone.reset();
	//else
		warningTone = otherTone;
}

//------------------------------------------------------------------------------
void Tone::pause(bool val)
{
	isPaused = val;
}

//------------------------------------------------------------------------------
void Tone::killTone()
{
	//logFile << "Note Off (killTone): " << synthIndex << " (" << (int)this << ")" << endl;
	noteOff = true;
	sounder->noteOff(synthIndex, collided);
}

//------------------------------------------------------------------------------
void Tone::startSwipe(const TwoFloats& mousePos)
{
	const int SwipeCountNum = 3;

	swiping = true;
	swipeStart = mousePos.y;
	swipeCount = SwipeCountNum;
}

//------------------------------------------------------------------------------
void Tone::swipe(const TwoFloats& mousePos)
{
	--swipeCount;
	if(!swipeCount)
	{
		swiping = false;

		if(mousePos.y < swipeStart)
			switchRail(true);
		else
			switchRail(false);
	}
}

//------------------------------------------------------------------------------
void Tone::endSwipe(const TwoFloats& mousePos)
{
	if(swiping)
	{
		swiping = false;

		if(mousePos.y < swipeStart)
			switchRail(true);
		else
			switchRail(false);
	}
}

//------------------------------------------------------------------------------
void Tone::setCollided()
{
	collided = true;
}

//------------------------------------------------------------------------------
void Tone::switchRail(bool direction)
{
	int tempint;
	float octave;
	//int intNumRails = (int)numRails;
	//float fraction = numRails - (float)intNumRails;

	if(newRail != rail)
		return;

	if(direction)
		tempint = rail - 1;
	else
		tempint = rail + 1;

	if((tempint >= 0) && //If we're not on the top rail.
	   ((tempint < numRailsPending) || //If we're not trying to go to a rail that's about to disappear, or...
	    ((rail >= numRailsPending) && (tempint < rail)))) //We're already on a rail that's going to disappear, and we're moving upwards.
	{
		newRail = tempint;

		movingRail = 0.025f * speed;

		if(speed < 4.0f)
			octave = 0.25f;
		else if(speed < 8.0f)
			octave = 0.5f;
		else
			octave = 1.0f;
		sounder->changePitch(synthIndex, newRail, octave);
		//logFile << "Pitch Change (switch rail): " << synthIndex << " (" << (int)this << ")" << endl;

		if(warning)
		{
			float dist;
			TonePtr otherTone = warningTone.lock();

			if(otherTone)
			{
				assert(gameCounter);

				triggerTextAbove(L"Near miss!");
				gameCounter->incrementCount(L"NearMisses", 1, false);

				if(gameCounter->getCount(L"Lives") < 3)
				{
					wstringstream tempstr;

					tempstr << "Extra Life in " << (6 - gameCounter->getCount(L"NearMisses"));

					triggerTextBelow(tempstr.str());
				}

				dist = position.getDistance(otherTone->getPosition());
				if(dist < 64)
					dist = 200.0f;
				else
				{
					dist = ((dist-64.0f)/(250.0f-186.0f))*200.0f;
					if(dist < 20.0f)
						dist = 20.0f;
				}
				gameCounter->incrementCount(L"Near miss!", (int)dist);
			}
		}
	}
}

//------------------------------------------------------------------------------
void Tone::setPosition(const TwoFloats& newPos)
{
	position = newPos;
}

//------------------------------------------------------------------------------
void Tone::setSynthIndex(int val)
{
	synthIndex = val;
	//logFile << "Note On (setSynthIndex): " << synthIndex << " (" << (int)this << ")" << endl;
}
