/////////////////////////////////////////////////////
//	File Name:	ParticleEngine.cpp
//	Date:		6/29/05
//	Author:		Will Dull
//	Purpose:	The particle engine system
//				implementation.
/////////////////////////////////////////////////////

#include "precompiled.h"
#include "ParticleEngine.h"

CParticleEngine *CParticleEngine::m_pInstance = 0;
CParticleEngine::CParticleEngine(void)
{
	m_pHead = NULL;
	totalParticles = 0;
}

CParticleEngine::~CParticleEngine(void)
{
}

void CParticleEngine::cleanup()
{
	tEmitterNode *temp = m_pHead;
	totalParticles = 0;
	while(temp)
	{
		tEmitterNode *pTemp = temp->next;
		if(!temp->prev)
			m_pHead = temp->next;
		else
			temp->prev->next = temp->next;

		if(pTemp)
			temp->next->prev = temp->prev;

		delete temp->pEmitter;
		delete temp;
		temp = pTemp;
	}

	DeleteInstance();
}

CParticleEngine *CParticleEngine::GetInstance(void)
{
	// Check to see if one hasn't been made yet.
	if (m_pInstance == 0)
		m_pInstance = new CParticleEngine;

	// Return the address of the instance.
	return m_pInstance;
}

void CParticleEngine::DeleteInstance()
{
	if (m_pInstance)
		delete m_pInstance;
	m_pInstance = 0;
}

bool CParticleEngine::initParticleSystem()
{
	// Texture Loading
	CTexture pTex;
	pTex.SetName("art/textures/particles/sprite.tga");

	u32 flags = 0;
	if(!(g_Renderer.LoadTexture(&pTex, flags)))
		return false;

	g_Renderer.SetTexture(0, &pTex);
	idTexture[DEFAULTTEXT] = pTex;

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

	glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
	//glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
	//glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_BLEND);
	//glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE);

	return true;
}

bool CParticleEngine::addEmitter(CEmitter *emitter, int type, int ID)
{
	emitter->setTexture(&idTexture[type]);
	if(m_pHead == NULL)
	{
		tEmitterNode *temp = new tEmitterNode;
		temp->pEmitter = emitter;
		temp->prev = NULL;
		temp->next = NULL;
		temp->ID = ID;
		m_pHead = temp;
		return true;
	}
	else
	{
		tEmitterNode *temp = new tEmitterNode;
		temp->pEmitter = emitter;
		temp->next = m_pHead;
		temp->prev = NULL;
		temp->ID = ID;
		m_pHead->prev = temp;
		m_pHead = temp;
		return true;
	}
}

CEmitter* CParticleEngine::findEmitter(int ID)
{
	tEmitterNode *temp = m_pHead;
	while(temp)
	{
		if(temp->ID < 0 || temp->ID > MAX_EMIT)
			continue;

		// NOTE: In the event that there are two different
		// emitters with the same ID, this will only
		// return the first one that it finds. So
		// make sure your emitter has a unique ID
		// if you want to use this function.
		if(temp->ID == ID)
			return temp->pEmitter;

		temp = temp->next;
	}

	// NOTE: Just in case this happens, it's VERY important
	// that you wrap this function in a if statement
	// to check for this condition because you could
	// end up with a crash if you try to change an
	// emitter when you couldn't find it and returned
	// NULL instead.
	return NULL;
}

void CParticleEngine::updateEmitters()
{
	tEmitterNode *temp = m_pHead;
	totalParticles = 0;
	while(temp)
	{
		// are we ready for deletion?
		if(temp->pEmitter->getIsFinished())
		{
			// store a pointer to the next node
			tEmitterNode *pTemp = temp->next;

			// check for the head
			if(!temp->prev)
				m_pHead = pTemp;
			else
				// forward the previous's pointer
				temp->prev->next = pTemp;

			// if there is any next one,
			if(pTemp)
				// fix the backwards pointer
				pTemp->prev = temp->prev;

			delete temp->pEmitter;
			delete temp;
			temp = pTemp;
		}
		else
		{
			temp->pEmitter->updateEmitter();

			// Add current emitter to particle count
			totalParticles += temp->pEmitter->getParticleCount();
			temp = temp->next;
		}
	}
}

void CParticleEngine::renderParticles()
{
	EnterParticleContext();

	tEmitterNode *temp = m_pHead;
	while(temp)
	{
		temp->pEmitter->renderEmitter();
		temp = temp->next;
	}

	LeaveParticleContext();
}

void CParticleEngine::destroyAllEmitters(bool fade)
{
	tEmitterNode *temp = m_pHead;
	while(temp)
	{
		if(fade)
		{
			temp->pEmitter->setEmitterLife(0);
			temp = temp->next;
		}
		else
		{
			// store a pointer to the next node
			tEmitterNode *pTemp = temp->next;

			// check for the head
			if(!temp->prev)
				m_pHead = pTemp;
			else
				// forward the previous's pointer
				temp->prev->next = pTemp;

			// if there is any next one,
			if(pTemp)
				// fix the backwards pointer
				pTemp->prev = temp->prev;

			delete temp->pEmitter;
			delete temp;
			temp = pTemp;
		}
	}
	m_pHead = NULL;
	updateEmitters();
}

void CParticleEngine::EnterParticleContext(void)
{
	//glEnable(GL_DEPTH_TEST);               // Enable depth testing for hidden surface removal.
	glDepthMask(false);
	//glDisable(GL_LIGHTING);
	glEnable(GL_TEXTURE_2D);               // Enable texture mapping.
	glPushMatrix();
	glEnable(GL_BLEND);
}

void CParticleEngine::LeaveParticleContext(void)
{
	//glDisable(GL_DEPTH_TEST);
	glDepthMask(true);
	//glEnable(GL_LIGHTING);
	glDisable(GL_TEXTURE_2D);
	glPopMatrix();
	glDisable(GL_BLEND);
}
