
#include "../hud.h"
#include "../cl_util.h"
#include "../parsemsg.h"
#include "r_studioint.h"
#include "com_model.h"
#include "fxsys.h"
#include "fxparticlesystem.h"


/**********************
* Default Particle System Emitter
**********************/

// Constructor
CFXParticleEmitter::CFXParticleEmitter()
{	Reset();
}
CFXParticleEmitter::CFXParticleEmitter(CFXParticleSystem *pParticleSystem)
{	Reset();
	setParticleSystem(pParticleSystem);
}

// Reset
void CFXParticleEmitter::Reset( void )
{	setParticleSystem(NULL);
	setMaxParticles(0);
	setCollision(FXPCOLLISION_NONE);
	setRenderType(kRenderNormal);
	setDrawStyle(FXDRAWSTYLE_NORMAL);
	setCulling(TRI_FRONT);
	setEmitRate(0);
	setParticlesThisSecond(0);
	setPPPClock(0);
	// reset particle attributes
	for(int i = 0; i < 2; i++)
	{	for(int x = 0; x < 4; x++)
		{	m_flRGBAStart[i][x] = 0;
			m_flRGBAEnd[i][x] = 0;
		}
		m_vecVelocity[i] = vec3_t(0,0,0);
		m_flLife[i] = 0;
		m_flGravity[i] = 0;
		m_flStartSize[i] = 0;
		m_flEndSize[i] = 0;
		m_flBounceDamp[i] = 0;
		m_flAirResistance[i] = 0;
	}
}

// Parse Data
void CFXParticleEmitter::parseData( CFXStorage *pData )
{	
	/*
	* Set particle system data
	*/
	// Max Particles
	setMaxParticles(pData->getFXDataInt("max_particles"));

	// Collision
	std::string strCollision = pData->getFXDataString("collision");
	if(strCollision == FXPARTICLEPARSE_NONE)
		setCollision(FXPCOLLISION_NONE);
	else if(strCollision == FXPARTICLEPARSE_STOP)
		setCollision(FXPCOLLISION_STOP);
	else if(strCollision == FXPARTICLEPARSE_BOUNCE)
	{	setCollision(FXPCOLLISION_BOUNCE);
	}

	// Render type
	std::string strRender = pData->getFXDataString("draw_type");
	if(strRender == FXPARTICLEPARSE_REND_NORM)
		setRenderType(kRenderNormal);
	else if(strRender == FXPARTICLEPARSE_REND_ADD)
		setRenderType(kRenderTransAdd);
	else if(strRender == FXPARTICLEPARSE_REND_SOLIDTRANS)
		setRenderType(kRenderTransTexture);

	// Render style
	std::string strStyle = pData->getFXDataString("draw_style");
	if(strStyle == FXPARTICLEPARSE_STYLE_NORM)
		setDrawStyle(FXDRAWSTYLE_NORMAL);
	else if(strStyle == FXPARTICLEPARSE_STYLE_ELONGATE)
		setDrawStyle(FXDRAWSTYLE_ELONGATE);

	// Culling
	std::string strCulling = pData->getFXDataString("culling");
	if(strCulling == FXPARTICLEPARSE_CULL_FRONT)
		setCulling(TRI_FRONT);
	else if(strCulling == FXPARTICLEPARSE_CULL_NONE)
		setCulling(TRI_NONE);

	// Particles per second
	std::string strEmitType = pData->getFXDataString("emit_type");
	if(strEmitType == FXPARTICLEPARSE_EMIT_BURST)
	{	setEmitType(FXEMITTYPE_BURST);
		setEmitAmount(pData->getFXDataInt("emit_amount"));
	}
	else
	{	setEmitType(FXEMITTYPE_FLOW);
	}
	setEmitRate(pData->getFXDataFloat("emit_rate"));

	// Sprites
	CFXData *pSprites = pData->getFXData("sprite");
	CFXPrecache *pSpritePrecache = NULL;
	if(pSprites)
	{	// Single sprite, just add to list
		if(pSprites->getType() == FXDATA_SINGLE)
		{	pSpritePrecache = g_ParseMain.getPrecache(pSprites->getStringValue());
			if(pSpritePrecache)
			{	m_SpriteList.push_back((*pSpritePrecache->getSPR()));
			}
		}
		// List of sprites, iterate through them and add all to list
		else if(pSprites->getType() == FXDATA_ARRAY)
		{	int iTotalSprites = pSprites->getArraySize();
			for(int i = 0; i < iTotalSprites; i++)
			{	CFXData *pSprite = pSprites->getArrayAtIndex(i);
				if(pSprite && (pSprite->getType() == FXDATA_SINGLE))
				{	pSpritePrecache = g_ParseMain.getPrecache(pSprite->getStringValue());
					if(pSpritePrecache)
					{	m_SpriteList.push_back((*pSpritePrecache->getSPR()));
					}
				}
			}
		}
	}
	/*
	* Individual particle data
	*/
	// velocity
	pData->getFXDataVectorRange(m_vecVelocity,"velocity");
	// life
	pData->getFXDataFloatRange(m_flLife,"life");
	// gravity
	pData->getFXDataFloatRange(m_flGravity,"gravity");
	// start size
	pData->getFXDataFloatRange(m_flStartSize,"start_size");
	// end size
	pData->getFXDataFloatRange(m_flEndSize,"end_size");
	// bounce damp
	pData->getFXDataFloatRange(m_flBounceDamp,"bounce_damp");
	// air resistance
	pData->getFXDataFloatRange(m_flAirResistance,"air_resistance");
	// color/alpha
	float flSAlpha[2];
	float flEAlpha[2];
	pData->getFXDataFloatRange(flSAlpha,"start_alpha");
	pData->getFXDataFloatRange(flEAlpha,"end_alpha");
	pData->getFXDataColorRange(m_flRGBAStart[0],m_flRGBAStart[1],"start_color");
	pData->getFXDataColorRange(m_flRGBAEnd[0],m_flRGBAEnd[1],"end_color");
	// combine color and alpha to be managed in single array
	m_flRGBAStart[0][3] = flSAlpha[0];
	m_flRGBAStart[1][3] = flSAlpha[1];
	m_flRGBAEnd[0][3] = flEAlpha[0];
	m_flRGBAEnd[1][3] = flEAlpha[1];
}

// Get Range Vector
vec3_t CFXParticleEmitter::getRange( vec3_t *range )
{	if(!range)
	{	return vec3_t(0,0,0);
	}
	if(range[0] == range[1])
	{	return range[0];
	}
	vec3_t vecNewVec;
	vecNewVec.x = gEngfuncs.pfnRandomFloat(range[0].x,range[1].x);
	vecNewVec.x = gEngfuncs.pfnRandomFloat(range[0].y,range[1].y);
	vecNewVec.x = gEngfuncs.pfnRandomFloat(range[0].z,range[1].z);
	return vecNewVec;
}

// Get Range Int
int	CFXParticleEmitter::getRange( int *range )
{	if(!range)
	{	return 0;
	}
	if(range[0] == range[1])
	{	return range[0];
	}
	return gEngfuncs.pfnRandomLong(range[0], range[1]);
}

// Get Range Float
float CFXParticleEmitter::getRange( float *range )
{	if(!range)
	{	return 0;
	}
	if(range[0] == range[1])
	{	return range[0];
	}
	return gEngfuncs.pfnRandomFloat(range[0], range[1]);
}

// Get random sprite from list
HSPRITE CFXParticleEmitter::getRandSprite( void )
{	if(m_SpriteList.empty())
	{	return 0;
	}
	return m_SpriteList[gEngfuncs.pfnRandomLong(0,m_SpriteList.size()-1)];
}

// Get sprite count
int CFXParticleEmitter::getSpriteNum( void )
{	return (int)m_SpriteList.size();
}

/*
* Emit Particles
*/

// Cancel new particle
// (remove it from the particle list and add it to the free list)
void CFXParticleEmitter::cancelNewParticle( CFXParticle *pParticle )
{	if(!getParticleSystem())
		return;
	getParticleSystem()->freeParticle(pParticle);
}

// Set Sprite
bool CFXParticleEmitter::setParticleSprite( CFXParticle *pParticle )
{	pParticle->setSprite(getRandSprite());
	if(pParticle->getSprite())
		return true;
	return false;
}

// Set Life
bool CFXParticleEmitter::setParticleLife( CFXParticle *pParticle )
{	float flLife = getRange(m_flLife);
	pParticle->setCurrentLife(flLife);
	pParticle->setStartLife(flLife);
	// Particle spawned with no life, dont even spawn
	if(flLife < 0)
		return false;
	return true;
}

// Set RGBA
void CFXParticleEmitter::setParticleRGBA( CFXParticle *pParticle )
{	float *RGBA = pParticle->getRGBA();
	float *startRGBA = pParticle->getStartRGBA();
	float *endRGBA = pParticle->getEndRGBA();
	for(int i=0;i<4;i++)
	{	RGBA[i]=startRGBA[i]=gEngfuncs.pfnRandomFloat(m_flRGBAStart[0][i],m_flRGBAStart[1][i]);
		endRGBA[i]=gEngfuncs.pfnRandomFloat(m_flRGBAEnd[0][i],m_flRGBAEnd[1][i]);
	}
}

// Set Size
void CFXParticleEmitter::setParticleSize( CFXParticle *pParticle )
{	float flStartSize = getRange(m_flStartSize);
	float flEndSize = getRange(m_flEndSize);
	pParticle->setSize(flStartSize);
	pParticle->setStartSize(flStartSize);
	pParticle->setEndSize(flEndSize);
}

// Set Origin and Velocity
void CFXParticleEmitter::setParticleOriginVelocity( CFXParticle *pParticle )
{	// Set velocity
	vec3_t vecVelocity;
	for(int i = 0; i < 3; i++)
	{	vecVelocity[i] = gEngfuncs.pfnRandomFloat(m_vecVelocity[0][i],m_vecVelocity[1][i]);
	}
	pParticle->setVelocity(vecVelocity);

	// Set Origin
	if(getParticleSystem())
	{	// not relative
		if(getParticleSystem()->getRelative() == FXPARTSYS_RELATIVE_NONE)
			pParticle->setOrigin(getParticleSystem()->getEmitOrigin());
		// origin is relative, particle (0,0,0) position == system origin
		else
			pParticle->setOrigin(vec3_t(0,0,0));
	}
}

// Set Collision
void CFXParticleEmitter::setParticleCollision( CFXParticle *pParticle )
{	// Set collision type
	pParticle->setCollision(getCollision());
}

// Set resistance (bounce damp, gravity, air resistance)
void CFXParticleEmitter::setParticleResistance( CFXParticle *pParticle )
{	pParticle->setGravity(getRange(m_flGravity));
	pParticle->setBounceDampen(getRange(m_flBounceDamp));
	pParticle->setAirResistance(getRange(m_flAirResistance));
}

// Emit Particle
CFXParticle *CFXParticleEmitter::emitParticle( CFXParticle *pParticle )
{	// Make sure the particle has a valid sprite and life span before setting the rest of the params
	if(!setParticleSprite(pParticle) || !setParticleLife(pParticle))
	{	cancelNewParticle(pParticle);
		return NULL;
	}

	// Set particle params
	setParticleRGBA( pParticle );
	setParticleSize( pParticle );
	setParticleResistance( pParticle );
	setParticleOriginVelocity( pParticle );
	setParticleCollision( pParticle );

	return pParticle;
}

// Get particles to emit this frame
int CFXParticleEmitter::particlesToEmitThisFrame( void )
{	if(getEmitType() == FXEMITTYPE_FLOW)
	{	int iCalcEmitted = 0;
		int iShouldEmit = 0;
		float flDeltaTime = 0;
		bool bResetParticlesThisSecond = false;

		// This system doesn't emit particles
		if(getEmitRate() <= 0)
		{	return 0;
		}

		// Clock wasn't set yet
		if(getPPPClock() == 0)
		{	setPPPClock(gEngfuncs.GetClientTime());
			setParticlesThisSecond(0);
			return 0;
		}

		// Get delta time
		flDeltaTime = gEngfuncs.GetClientTime()-getPPPClock();
		// Passed one second
		if(flDeltaTime > 1)
		{	// Increase clock by a tick and cap delta time for current tick
			incPPPClock();
			flDeltaTime = 1;
			bResetParticlesThisSecond = true;
		}
		// calculate the amount of particles that should have been emitted by this point, and then calculate
		// how many particles we need to emit to catch up to that ideal amount
		iCalcEmitted = floor(flDeltaTime*getEmitRate());
		iShouldEmit = iCalcEmitted-getParticlesThisSecond();
		if(iShouldEmit<0)
			iShouldEmit=0;

		// Reset particles this second (new tick)
		if(bResetParticlesThisSecond)
		{	setParticlesThisSecond(0);
		}
		// Update emitted particles 
		else
		{	setParticlesThisSecond(iCalcEmitted);
		}

		return iShouldEmit;
	}
	// Burst particle system
	else if(getEmitType() == FXEMITTYPE_BURST)
	{	// Output X particles per interval
		if(gEngfuncs.GetClientTime()-getPPPClock() > 0)
		{	setPPPClock(gEngfuncs.GetClientTime()+getEmitRate());
			return getEmitAmount();
		}
	}
	return 0;
}

// Emit Particles
void CFXParticleEmitter::emitParticles( void )
{	int iRemainingParticles = getMaxParticles() - getParticleSystem()->getParticleCount();
	
	// Make sure we're not over the max particle allocation
	if(getMaxParticles() ? iRemainingParticles > 0 : true)
	{	
		// Get particles to emit this frame
		int iParticles = particlesToEmitThisFrame();

		// Make sure we're not going to bypass the maximum particles
		if(!getMaxParticles() && (iParticles>iRemainingParticles))
			iParticles = iRemainingParticles;

		// Emit particles
		if(getParticleSystem()->canEmit())
		{	for(int i = 0; i < iParticles; i++)
				emitParticle(getParticleSystem()->getNewParticle());
		}
	}
}


/**********************
* Ring Radiate particle system
**********************/
// Reset
void CFXParticleEmitterRingRadiate::Reset( void )
{	// Call default reset
	CFXParticleEmitter::Reset();

	setVelocityType(0);
	for(int i = 0; i < 2; i++)
	{	m_flVelSpeed[i] = 0;
		m_flVelSpeedZ[i] = 0;
		m_flRadius[i] = 0;
	}
}

// Parse Data
void CFXParticleEmitterRingRadiate::parseData( CFXStorage *pData )
{	// Call default emitter parse data
	CFXParticleEmitter::parseData(pData);
	
	// Velocity type
	std::string strVelocityType = pData->getFXDataString("velocity_type");
	if(strVelocityType == FXPARTICLEPARSE_VELT_NORM)
		setVelocityType(FXPARTSYS_VELT_NORMAL);
	else
		setVelocityType(FXPARTSYS_VELT_OUTWARD);

	// Set Speed
	if(getVelocityType()==FXPARTSYS_VELT_OUTWARD)
	{	pData->getFXDataFloatRange(m_flVelSpeed,"velocity_speed");
		pData->getFXDataFloatRange(m_flVelSpeedZ,"velocity_speed_z");
	}

	pData->getFXDataFloatRange(m_flRadius,"radius");
}

// Set Origin and Velocity
void CFXParticleEmitterRingRadiate::setParticleOriginVelocity( CFXParticle *pParticle )
{	vec3_t vecVelocity = vec3_t(0,0,0);
	vec3_t vecOriginOffset = vec3_t(0,0,0);

	switch(getVelocityType())
	{	// Radiating starting from ring origin, but use arbitrary velocity attribute
	case FXPARTSYS_VELT_NORMAL:
		// Set Velocity
		for(int i = 0; i < 3; i++)
		{	vecVelocity[i] = gEngfuncs.pfnRandomFloat(m_vecVelocity[0][i],m_vecVelocity[1][i]);
		}
		pParticle->setVelocity(vecVelocity);

		// Calculate origin offset
		vecOriginOffset.x = cos(gEngfuncs.pfnRandomFloat(-M_PI,M_PI));
		vecOriginOffset.y = sin(gEngfuncs.pfnRandomFloat(-M_PI,M_PI));
		vecOriginOffset.z = 0;
		vecOriginOffset = vecOriginOffset.Normalize();
		vecOriginOffset = vecOriginOffset * getRange(m_flRadius);
		// Set Origin
		if(getParticleSystem())
		{	// not relative
			if(getParticleSystem()->getRelative() == FXPARTSYS_RELATIVE_NONE)
				pParticle->setOrigin(getParticleSystem()->getEmitOrigin()+vecOriginOffset);
			// origin is relative, particle (0,0,0) position == system origin
			else
				pParticle->setOrigin(vecOriginOffset);
		}

		break;
		// Radiate outwards in a ring fashion
	case FXPARTSYS_VELT_OUTWARD:
		// Calculate Velocity
		vecVelocity.x = cos(gEngfuncs.pfnRandomFloat(-M_PI,M_PI));
		vecVelocity.y = sin(gEngfuncs.pfnRandomFloat(-M_PI,M_PI));
		vecVelocity.z = 0;
		vecVelocity = vecVelocity.Normalize();
		// Calc origin offset
		vecOriginOffset = vecVelocity*getRange(m_flRadius);
		// Set Velocity
		vecVelocity = vecVelocity * getRange(m_flVelSpeed);
		vecVelocity.z = getRange(m_flVelSpeedZ);
		pParticle->setVelocity(vecVelocity);

		// Set Origin
		if(getParticleSystem())
		{	// not relative
			if(getParticleSystem()->getRelative() == FXPARTSYS_RELATIVE_NONE)
				pParticle->setOrigin(getParticleSystem()->getEmitOrigin()+vecOriginOffset);
			// origin is relative, particle (0,0,0) position == system origin
			else
				pParticle->setOrigin(vecOriginOffset);
		}

		break;
	}
}


/**********************
* Rise particle system
**********************/
// Reset
void CFXParticleEmitterRise::Reset( void )
{	// Call default reset
	CFXParticleEmitter::Reset();

	setRadius(0);
}

// Parse Data
void CFXParticleEmitterRise::parseData( CFXStorage *pData )
{	// Call default emitter parse data
	CFXParticleEmitter::parseData(pData);

	// Radius
	setRadius(pData->getFXDataFloat("radius"));
}


// Set Origin and Velocity
void CFXParticleEmitterRise::setParticleOriginVelocity( CFXParticle *pParticle )
{	vec3_t vecVelocity = vec3_t(0,0,0);
	vec3_t vecOriginOffset = vec3_t(0,0,0);

	// Set Velocity
	for(int i = 0; i < 3; i++)
	{	vecVelocity[i] = gEngfuncs.pfnRandomFloat(m_vecVelocity[0][i],m_vecVelocity[1][i]);
	}
	pParticle->setVelocity(vecVelocity);

	// Calculate origin offset
	vecOriginOffset.x = cos(gEngfuncs.pfnRandomFloat(-M_PI,M_PI));
	vecOriginOffset.y = sin(gEngfuncs.pfnRandomFloat(-M_PI,M_PI));
	vecOriginOffset.z = 0;
	vecOriginOffset = vecOriginOffset.Normalize();
	vecOriginOffset = vecOriginOffset * gEngfuncs.pfnRandomFloat(0,getRadius());
	// Set Origin
	if(getParticleSystem())
	{	// not relative
		if(getParticleSystem()->getRelative() == FXPARTSYS_RELATIVE_NONE)
			pParticle->setOrigin(getParticleSystem()->getEmitOrigin()+vecOriginOffset);
		// origin is relative, particle (0,0,0) position == system origin
		else
			pParticle->setOrigin(vecOriginOffset);
	}
}

/**********************
* Scatter particle system
**********************/
// Reset
void CFXParticleEmitterScatter::Reset( void )
{	// Call default reset
	CFXParticleEmitter::Reset();

	setRadius(0);
}

// Parse Data
void CFXParticleEmitterScatter::parseData( CFXStorage *pData )
{	// Call default emitter parse data
	CFXParticleEmitter::parseData(pData);

	// Radius
	setRadius(pData->getFXDataFloat("radius"));
}


// Set Origin and Velocity
void CFXParticleEmitterScatter::setParticleOriginVelocity( CFXParticle *pParticle )
{	vec3_t vecVelocity = vec3_t(0,0,0);
	vec3_t vecOriginOffset = vec3_t(0,0,0);

	// Set Velocity
	for(int i = 0; i < 3; i++)
	{	vecVelocity[i] = gEngfuncs.pfnRandomFloat(m_vecVelocity[0][i],m_vecVelocity[1][i]);
	}
	pParticle->setVelocity(vecVelocity);

	// Calculate origin offset
	vecOriginOffset.x = cos(gEngfuncs.pfnRandomFloat(-M_PI,M_PI));
	vecOriginOffset.y = sin(gEngfuncs.pfnRandomFloat(-M_PI,M_PI));
	vecOriginOffset.z = cos(gEngfuncs.pfnRandomFloat(-M_PI,M_PI));
	vecOriginOffset = vecOriginOffset.Normalize();
	vecOriginOffset = vecOriginOffset * gEngfuncs.pfnRandomFloat(0,getRadius());
	// Set Origin
	if(getParticleSystem())
	{	// not relative
		if(getParticleSystem()->getRelative() == FXPARTSYS_RELATIVE_NONE)
			pParticle->setOrigin(getParticleSystem()->getEmitOrigin()+vecOriginOffset);
		// origin is relative, particle (0,0,0) position == system origin
		else
			pParticle->setOrigin(vecOriginOffset);
	}
}