//	Dot.cpp - A sine dot.
//  ----------------------------------------------------------------------------
//	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 "Dot.h"
#include "NiallsDrawingLib/Drawer.h"
#include "NiallsDrawingLib/ImageLoader.h"
#include "Images.h"
#include "SynthSingleton.h"
#include "GameCounter.h"

#include <sstream>
#include <cmath>

using namespace std;

//------------------------------------------------------------------------------
const float Dot::Radius = 96.0f;
uint32_t Dot::dotImage = 0;
GLuint Dot::lineImage = 0;

//------------------------------------------------------------------------------
Dot::Dot(int rail, int numRails, float speed):
swiping(false),
swipeStart(0.0f),
swipeCount(0),
noteOff(false),
collided(false),
isPaused(false)
{
	int i;

	this->rail = rail;
	this->numRails = numRails;
	this->speed = speed;

	newRail = rail;
	movingRail = 0.0f;

	position.x = 1280 + 64;
	position.y = 64 + (rail * (592.0f/(float)(numRails-1)));

	prevPos[0].x = position.x + 2;
	prevPos[0].y = position.y;
	for(i=1;i<TrailSize;++i)
	{
		prevPos[i].x = prevPos[i-1].x + 2;
		prevPos[i].y = prevPos[i-1].y;
	}

	if(speed < 2.5f)
	{
		colour.x = 0.6640625f;
		colour.y = 0.25f;
		colour.z = 0.25f;
		colIndex = 0;

		synthIndex = SynthSingleton::getInstance().noteOn(rail, 0.25f);
	}
	else if(speed < 4.5f)
	{
		colour.x = 0.328125f;
		colour.y = 0.6640625f;
		colour.z = 0.25f;
		colIndex = 1;

		synthIndex = SynthSingleton::getInstance().noteOn(rail, 0.5f);
	}
	else
	{
		colour.x = 0.265625f;
		colour.y = 0.25f;
		colour.z = 0.6640625f;
		colIndex = 2;

		synthIndex = SynthSingleton::getInstance().noteOn(rail, 1.0f);
	}
}

//------------------------------------------------------------------------------
Dot::~Dot()
{
	if(!noteOff)
		SynthSingleton::getInstance().noteOff(synthIndex, collided);
}

//------------------------------------------------------------------------------
void Dot::draw(Drawer *d, float bright)
{
	int i;
	float tempf;
	//stringstream tempstr;
	const float railDist = (592.0f/(float)(numRails-1));

	if(!dotImage)
	{
		ImageLoader imLoad;
		ImageData data;

		data = imLoad.loadImage(Images::dot_png, Images::dot_pngSize);
		dotImage = d->loadImage(data.data, data.width, data.height);

		//Load line texture.
		data = imLoad.loadImage(Images::line_png, Images::line_pngSize);

		glGenTextures(1, &lineImage);

		glBindTexture(GL_TEXTURE_2D, lineImage);

		glTexImage2D(GL_TEXTURE_2D,
					 0,
					 GL_RGBA,
					 data.width,
					 data.height,
					 0,
					 GL_RGBA,
					 GL_UNSIGNED_BYTE,
					 data.data);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glBindTexture(GL_TEXTURE_2D, 0);

		delete [] data.data;
	}

	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;
		if((position.x < 0.0f) && !noteOff)
		{
			SynthSingleton::getInstance().noteOff(synthIndex, collided);
			noteOff = true;
			switch(colIndex)
			{
				case 0:
					GameCounter::getInstance().incrementCount("Red Dot Survived", 20);
					break;
				case 1:
					GameCounter::getInstance().incrementCount("Blue Dot Survived", 10);
					break;
				case 2:
					GameCounter::getInstance().incrementCount("Green Dot Survived", 5);
					break;
			}
		}

		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 * speed;
			if(movingRail > 3.14159265f)
			{
				rail = newRail;
				position.y = 64 + (rail * railDist);
			}
		}
	}

	//Draw the trail.
	if(prevPos[TrailSize-1].y == position.y)
		drawStraightLine();
	else
		drawMitredLine();

	//Draw the dot.
	d->drawImage(dotImage,
				 position.x-64,
				 position.y-64,
				 1.0f,
				 1.0f,
				 colour.x * bright,
				 colour.y * bright,
				 colour.z * bright);

	//Draw the dot's synth index.
	//tempstr << synthIndex;
	//d->drawText(tempstr.str(), position.x-64, position.y-64, 32.0f, colour.x, colour.y, colour.z);
}

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

//------------------------------------------------------------------------------
void Dot::killTone()
{
	noteOff = true;
	SynthSingleton::getInstance().noteOff(synthIndex, collided);
}

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

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

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

		if(mousePos.y < swipeStart)
			attract(position - 1);
		else
			attract(position + 1);
	}
}

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

		if(mousePos.y < swipeStart)
			attract(position - 1);
		else
			attract(position + 1);
	}
}

//------------------------------------------------------------------------------
void Dot::deflect(const TwoFloats& playerPos)
{
	int tempint;
	float octave;

	if(newRail != rail)
		return;

	if(playerPos.y < position.y)
		tempint = rail + 1;
	else if(playerPos.y > position.y)
		tempint = rail - 1;

	if((tempint >= 0) && (tempint < numRails))
	{
		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;
		SynthSingleton::getInstance().changePitch(synthIndex, newRail, octave);
	}
}

//------------------------------------------------------------------------------
void Dot::attract(const TwoFloats& playerPos)
{
	int tempint;
	float octave;

	if(newRail != rail)
		return;

	if(playerPos.y < position.y)
		tempint = rail - 1;
	else if(playerPos.y > position.y)
		tempint = rail + 1;

	if((tempint >= 0) && (tempint < numRails))
	{
		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;
		SynthSingleton::getInstance().changePitch(synthIndex, newRail, octave);
	}
}

//------------------------------------------------------------------------------
void Dot::setNearby(float val)
{
	SynthSingleton::getInstance().changeAmplitude(synthIndex, val);
}

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

//------------------------------------------------------------------------------
void Dot::drawMitredLine()
{
	int i;
	float alpha1, alpha2;
	const float size = 4.0f;

	glBindTexture(GL_TEXTURE_2D, lineImage);

	//The two intersecting lines where the line we're drawing meets its
	//neighbours.
	TwoFloats intersections[2];

	intersections[0] = (prevPos[1] - prevPos[0]).perpendicular().normal();
	for(i=1;i<(TrailSize-2);++i)
	{
		//The 2 alpha values.
		alpha1 = 1.0f-((float)i/(float)TrailSize);
		alpha2 = 1.0f-((float)(i+1)/(float)TrailSize);

		//The 4 trail points we need to calculate the correct corners for the
		//line prevPos[i] -> prevPos[i+1].
		TwoFloats a0(prevPos[i-1]);
		TwoFloats a1(prevPos[i]);
		TwoFloats a2(prevPos[i+1]);
		TwoFloats a3(prevPos[i+2]);

		//The 4 corners of the quad we use to draw the line
		//prevPos[i] -> prevPos[i+1]. The arrangement is:
		//3--2
		//|..|
		//|..|
		//0--1
		TwoFloats corners[4];

		//Start with the 'bottom-left' corner.
		intersections[0] = intersections[1];
		corners[0] = a1 - (intersections[0] * size);

		//Now the 'top-left' corner.
		corners[3] = a1 + (intersections[0] * size);

		//Now the 'bottom-right' corner.
		intersections[1] = (a3-a2).perpendicular().normal();
		corners[1] = a2 - (intersections[1] * size);

		//Now the 'top-left' corner.
		corners[2] = a2 + (intersections[1] * size);

		//Now draw the line.
		glBegin(GL_QUADS);
		{
			glColor4f(colour.x, colour.y, colour.z, alpha1);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 0.0f);
			glVertex2f(corners[0].x, corners[0].y);
			glColor4f(colour.x, colour.y, colour.z, alpha2);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 1.0f);
			glVertex2f(corners[1].x, corners[1].y);
			glColor4f(colour.x, colour.y, colour.z, alpha2);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 1.0f);
			glVertex2f(corners[2].x, corners[2].y);
			glColor4f(colour.x, colour.y, colour.z, alpha1);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 0.0f);
			glVertex2f(corners[3].x, corners[3].y);
		}
		glEnd();
	}

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	glBindTexture(GL_TEXTURE_2D, 0);
}

//------------------------------------------------------------------------------
void Dot::drawStraightLine()
{
	//int i;
	float alpha1, alpha2;
	const float size = 4.0f;

	glBindTexture(GL_TEXTURE_2D, lineImage);

	//The two intersecting lines where the line we're drawing meets its
	//neighbours.
	//TwoFloats intersections[2];

	//intersections[0] = (prevPos[TrailSize-1] - prevPos[0]).perpendicular().normal();
	//for(i=1;i<(TrailSize-2);++i)
	{
		//The 2 alpha values.
		alpha1 = 1.0f;
		alpha2 = 0.0f;

		//The 4 trail points we need to calculate the correct corners for the
		//line prevPos[i] -> prevPos[i+1].
		/*TwoFloats a0(prevPos[i-1]);
		TwoFloats a1(prevPos[i]);
		TwoFloats a2(prevPos[i+1]);
		TwoFloats a3(prevPos[i+2]);*/

		//The 4 corners of the quad we use to draw the line
		//prevPos[i] -> prevPos[i+1]. The arrangement is:
		//3--2
		//|..|
		//|..|
		//0--1
		TwoFloats corners[4];

		//Start with the 'bottom-left' corner.
		/*intersections[0] = intersections[1];
		corners[0] = a1 - (intersections[0] * size);

		//Now the 'top-left' corner.
		corners[3] = a1 + (intersections[0] * size);

		//Now the 'bottom-right' corner.
		intersections[1] = (a3-a2).perpendicular().normal();
		corners[1] = a2 - (intersections[1] * size);

		//Now the 'top-left' corner.
		corners[2] = a2 + (intersections[1] * size);*/

		corners[0].x = prevPos[0].x;
		corners[0].y = prevPos[0].y + size;
		corners[1].x = prevPos[TrailSize-1].x;
		corners[1].y = prevPos[TrailSize-1].y + size;
		corners[2].x = prevPos[TrailSize-1].x;
		corners[2].y = prevPos[TrailSize-1].y - size;
		corners[3].x = prevPos[0].x;
		corners[3].y = prevPos[0].y - size;

		//Now draw the line.
		glBegin(GL_QUADS);
		{
			glColor4f(colour.x, colour.y, colour.z, alpha1);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 0.0f);
			glVertex2f(corners[0].x, corners[0].y);
			glColor4f(colour.x, colour.y, colour.z, alpha2);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 1.0f);
			glVertex2f(corners[1].x, corners[1].y);
			glColor4f(colour.x, colour.y, colour.z, alpha2);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 1.0f);
			glVertex2f(corners[2].x, corners[2].y);
			glColor4f(colour.x, colour.y, colour.z, alpha1);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 0.0f);
			glVertex2f(corners[3].x, corners[3].y);
		}
		glEnd();
	}

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	glBindTexture(GL_TEXTURE_2D, 0);
}
