/*
Solar Conquest
Copyright (C) 2006 Greg Beaman

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 2 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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

class CParticleNode
{
public:
	CTextureListNode* texture;
	int framesX;
	int framesY;
	int curFrameX;
	int curFrameY;
	int lastLine;
	float frameDelay;
	
	float colorR;
	float colorG;
	float colorB;
	float colorA;
	
	float lifeTime;
	float maxLife;
	float radius;
	float angle;
	float speed;
	
	bool cached;
	
	Vector position;
	Vector startPosition;
	
	CParticleNode* nextObj;
	CParticleNode* prevObj;
	
	CParticleNode();
	~CParticleNode();
	
	void ResetNode();
	void Update(float elapsedTime);
	
	void SetTexture(CTextureListNode* tex, int fx, int fy, int lastFrameLine);
};

CParticleNode::CParticleNode()
{
	nextObj = NULL;
	prevObj = NULL;
	cached = false;

	ResetNode();
}

CParticleNode::~CParticleNode()
{
}

void CParticleNode::ResetNode()
{
	texture = NULL;
	colorR = colorG = colorB = colorA = 0;
	speed = 0;
}

void CParticleNode::Update(float elapsedTime)
{
	lifeTime -= elapsedTime;

	if (speed != 0)
	{
		position.x += (speed * elapsedTime) * GetSin(angle);
		position.y += (speed * elapsedTime) * GetCos(angle);
	}
	
	//Update the current frame in the animation
	if (texture)
	{
		frameDelay -= elapsedTime;
		if (frameDelay <= 0)
		{
			float playRate;
			if (lastLine == -1)
				playRate = maxLife/(framesX*framesY);
			else
				playRate = maxLife/(framesX*lastLine);
			
			curFrameX++;
			if (curFrameX >= framesX)
			{
				curFrameX = 0;
				curFrameY++;
			} 
			frameDelay = playRate;
		}
	}
}

void CParticleNode::SetTexture(CTextureListNode* tex, int fx, int fy, int lastFrameLine)
{
	if (!tex)
		return;
	texture = tex;
	framesX = fx;
	framesY = fy;
	if (lastFrameLine <= 0)
		lastLine = -1;
	else
		lastLine = lastFrameLine;
	
	curFrameX = 0;
	curFrameY = 0;
	frameDelay = 0;
}

class CParticleList
{
private:
	CParticleNode* firstNode;

	CParticleList* cache;
	
public:
	CParticleList();
	~CParticleList();
	
	CParticleNode* AddParticle();
	void DeleteParticle(CParticleNode* node);
	void DeleteAllParticles();
	
	//cache functions
	void SetupCache();
	void InsertParticle(CParticleNode* node);
	CParticleNode* PullParticleFromList();
	
	void Render(float cameraX, float cameraY, float elapsedTime, bool textured); //We update particles as we render them, to save time.
	
	int GetParticleCount();
	int GetCacheCount();
};

CParticleList::CParticleList()
{
	firstNode = NULL;
	cache = NULL;
}

CParticleList::~CParticleList()
{
	DeleteAllParticles();
	if (cache)
		delete cache;
	cache = NULL;
}

void CParticleList::SetupCache()
{
	cache = new CParticleList();
		
	if (cache)
	{
		CParticleNode* node;
		for (int i=0; i < 8000; i++)
//		for (int i=0; i < 0; i++)
		{
			node = cache->AddParticle();
			if (node)
				node->cached = true;
		}
	}
}

CParticleNode* CParticleList::AddParticle()
{
//	printf("Add particle\n");
//	if (!cache)
//		printf("...is the cache\n");
	CParticleNode* newObj;
	if (cache)
	{
		newObj = cache->PullParticleFromList();
		if (newObj)
		{
			//If a particle was in the cache, insert it here
			newObj->ResetNode();
			newObj->nextObj = firstNode;
			newObj->prevObj = NULL;
			if (firstNode)
				firstNode->prevObj = newObj;
			firstNode = newObj;
			return newObj;
		}
		else
		{
			//Make a new particle
			newObj = new CParticleNode();
			if (!newObj)
				return NULL;
			newObj->nextObj = firstNode;
			if (firstNode)
				firstNode->prevObj = newObj;
			firstNode = newObj;
			
			return newObj;
		}
	}
	else
	{
		//No cache, make a new particle
		newObj = new CParticleNode();
		if (!newObj)
			return NULL;
		newObj->nextObj = firstNode;
		if (firstNode)
			firstNode->prevObj = newObj;
		firstNode = newObj;
		
		return newObj;
	}
}

void CParticleList::DeleteParticle(CParticleNode* node)
{
//	printf("Delete particle\n");
//	if (!cache)
//		printf("...is the cache\n");
	if (!node)
		return;

	if (node->prevObj)
		node->prevObj->nextObj = node->nextObj;
	else
		firstNode = node->nextObj;
	if (node->nextObj)
		node->nextObj->prevObj = node->prevObj;
	
	//If a particle that's part of the cache, move it back in, else delete
	if (node->cached && cache)
		cache->InsertParticle(node);
	else
		delete node;
}

void CParticleList::DeleteAllParticles()
{
//	printf("Deleting all particle\n");
//	if (!cache)
//		printf("...is the cache\n");
	
	CParticleNode* curObj;
	CParticleNode* tmpObj;
	curObj = firstNode;
	while (curObj)
	{
		tmpObj = curObj->nextObj;
		//If a particle that's part of the cache, move it back in, else delete
		if (curObj->cached && cache)
			cache->InsertParticle(curObj);
		else
			delete curObj;
		curObj = tmpObj;
	}
	firstNode = NULL;
}

CParticleNode* CParticleList::PullParticleFromList()
{
//	printf("Pulling particle\n");
//	if (!cache)
//		printf("...is the cache\n");
	if (firstNode)
	{
		//Move the first node to the next one, and return the original first node
		CParticleNode* node = firstNode;
		firstNode = firstNode->nextObj;
		return node;
	}
	return NULL; //No particles here!
}

void CParticleList::InsertParticle(CParticleNode* node)
{
//	printf("Inserting particle\n");
//	if (!cache)
//		printf("...is the cache\n");
	if (!node)
	{
//		printf("......null node\n");
		return;
	}

	node->prevObj = NULL;
	node->nextObj = firstNode;
	if (firstNode)
		firstNode->prevObj = node;
	firstNode = node;
}

void CParticleList::Render(float cameraX, float cameraY, float elapsedTime, bool textured)
{
	if (textured)
	{
		glEnable(GL_TEXTURE_2D);
	}

	float pxWidth;
	float pxHeight;
	float frameSizeX;
	float frameSizeY;
	int lastTexId = -1;
	CParticleNode* node = firstNode;
	while (node)
	{
		node->Update(elapsedTime);
		if (node->lifeTime > 0)
		{
			if (Distance(cameraX+(g_screenWidth/2),cameraY+(g_screenHeight/2),node->position.x,node->position.y) <= ((g_screenWidth)+node->radius))
			{
				//Render
				glColor4f(node->colorR,node->colorG,node->colorB,node->colorA);
				if (!textured && !node->texture)
				{
					if (node->radius < 3)
					{
						glBegin(GL_TRIANGLE_STRIP);
							glVertex2f((node->position.x+(node->radius/2))-cameraX,(node->position.y-(node->radius/2))-cameraY);
							glVertex2f((node->position.x-(node->radius/2))-cameraX,(node->position.y-(node->radius/2))-cameraY);
							glVertex2f((node->position.x+(node->radius/2))-cameraX,(node->position.y+(node->radius/2))-cameraY);
							glVertex2f((node->position.x-(node->radius/2))-cameraX,(node->position.y+(node->radius/2))-cameraY);
						glEnd();

						glBegin(GL_LINES);
							glVertex2f(node->position.x-cameraX,node->position.y-cameraY);
							glVertex2f(node->startPosition.x-cameraX,node->startPosition.y-cameraY);
						glEnd();
					}
					else
					{
						glBegin(GL_TRIANGLE_FAN);
							glVertex2f(node->position.x-cameraX,node->position.y-cameraY);
//							for (int i=0; i <= 8; i++)
//								glVertex2f((GetSin(ToRadians(360/8)*i)*node->radius)+(node->position.x-cameraX),(GetCos(ToRadians(360/8)*i)*node->radius)+(node->position.y-cameraY));
							{
							for (int i=0; i <= 5; i++)
								glVertex2f((GetSin(ToRadians(360/5)*i)*node->radius)+(node->position.x-cameraX),(GetCos(ToRadians(360/5)*i)*node->radius)+(node->position.y-cameraY));
							}
						glEnd();
					}
				}
				else if (textured && node->texture)
				{
					if (lastTexId != node->texture->GetTextureId())
					{
						lastTexId = node->texture->GetTextureId();
						glBindTexture(GL_TEXTURE_2D,lastTexId);
						pxWidth = ((float)1)/((float)node->texture->GetWidth());
						pxHeight = ((float)1)/((float)node->texture->GetHeight());
						frameSizeX = pxWidth*(node->texture->GetWidth()/node->framesX);
						frameSizeY = pxHeight*(node->texture->GetHeight()/node->framesY);
					}
					glBegin(GL_TRIANGLE_STRIP);
						int curFrameX = node->curFrameX;
						int curFrameY = node->curFrameY;
						glTexCoord2f((curFrameX+1)*frameSizeX,(curFrameY)*frameSizeY);
						glVertex2f((node->position.x+(node->radius/2))-cameraX,(node->position.y-(node->radius/2))-cameraY);
						glTexCoord2f((curFrameX)*frameSizeX,(curFrameY)*frameSizeY);
						glVertex2f((node->position.x-(node->radius/2))-cameraX,(node->position.y-(node->radius/2))-cameraY);
						glTexCoord2f((curFrameX+1)*frameSizeX,(curFrameY+1)*frameSizeY);
						glVertex2f((node->position.x+(node->radius/2))-cameraX,(node->position.y+(node->radius/2))-cameraY);
						glTexCoord2f((curFrameX)*frameSizeX,(curFrameY+1)*frameSizeY);
						glVertex2f((node->position.x-(node->radius/2))-cameraX,(node->position.y+(node->radius/2))-cameraY);
					glEnd();
				}
	/*			glBegin(GL_LINE_STRIP);
					glVertex2f(node->position.x-cameraX,node->position.y-cameraY);
					for (int i=0; i <= 8; i++)
						glVertex2f((GetSin(ToRadians(360/8)*i)*node->radius)+(node->position.x-cameraX),(GetCos(ToRadians(360/8)*i)*node->radius)+(node->position.y-cameraY));
				glEnd();*/
			}
			node = node->nextObj;
		}
		else
		{
			//Remove
			CParticleNode* nNode = node->nextObj;
			DeleteParticle(node);
			node = nNode;
		}
	}
	
	if (textured)
	{
		glDisable(GL_TEXTURE_2D);
	}
}

int CParticleList::GetParticleCount()
{
	int count = 0;
	CParticleNode* node = firstNode;
	while (node)
	{
		count++;
		node = node->nextObj;
	}
	return count;
}

int CParticleList::GetCacheCount()
{
	if (cache)
		return cache->GetParticleCount();
	else
		return -1;
}

CParticleList* g_particles = NULL;

void MakeParticleExplosion(Vector position, float radius, float r, float g, float b, float a)
{
	if (!g_textureList)
		return;
		
	CParticleNode* particle;
	particle = g_particles->AddParticle();
	if (particle)
	{
		particle->position = position;
		particle->radius = radius;
		particle->angle = 0;
		particle->speed = 0.0;
		particle->lifeTime = particle->maxLife = 0.4; //0.2
		particle->colorR = r;
		particle->colorG = g;
		particle->colorB = b;
		particle->colorA = a;
			
		particle->SetTexture(g_textureList->FindNode(1),8,8,5);
	}

/*
	CParticleNode* particle;
	float angle;
	float randFactor;
	for (int i=0; i < SHIP_DEBRIS_DETAIL; i++)
	{
		angle = (6.28/SHIP_DEBRIS_DETAIL)*i;
		randFactor = rand()%radius;
		particle = g_particles->AddParticle();
		if (particle)
		{
			particle->position = AddVectors(position,CreateVector(sin(angle)*randFactor,cos(angle)*randFactor));
			particle->radius = 2;
			particle->angle = angle;
			particle->speed = 100.0;
			particle->lifeTime = particle->maxLife = 0.2;
			particle->colorR = r;
			particle->colorG = g;
			particle->colorB = b;
			particle->colorA = a;
			
			particle->SetTexture(g_textureList->FindNode(1),8,8,5);
		}
	}
*/
}

void MakeCapitalShipExplosion(Vector position, int radius, float r, float g, float b, float a)
{
	CParticleNode* particle;
	float angle;
	float randFactor;
	//was 200
	for (int i=0; i < 800; i++)
	{
		angle = (6.28/SHIP_DEBRIS_DETAIL)*i;
		randFactor = rand()%radius;
		particle = g_particles->AddParticle();
		if (particle)
		{
			particle->position = AddVectors(position,CreateVector(sin(angle)*randFactor,cos(angle)*randFactor));
			particle->startPosition = position;
			particle->radius = 2;
			particle->angle = angle;
			particle->speed = 100.0; //50
			particle->lifeTime = 5.0;
			float factor = ((rand()%100)*0.01);
			particle->colorR = r * factor;
			particle->colorG = g * factor;
			particle->colorB = b * factor;
			particle->colorA = a;
		}
	}
}