#pragma once

#include <memory.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "../munga/time.h"
#include "l4d3d.h"

#define COLOR_POINT_COUNT		10
#define MAX_PARTICLE_EFFECTS	32
#define EFFECT_GROUPING_EPSILON	1.0f

#define L4BASICVERTEX_FVF (D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_DIFFUSE | D3DFVF_NORMAL)

struct L4BASICVERTEX
{
	float x, y, z;
	float nx, ny, nz;
	DWORD color;
	float u, v;
};

union L4COLOR
{
	DWORD argb;
	struct
	{
		unsigned char b;
		unsigned char g;
		unsigned char r;
		unsigned char a;
	};
};

struct COLOR_POINT
{
	bool active;
	Scalar time;
	L4COLOR color;
};

struct PARTICLE_EFFECT  
{
	int id;
	struct
	{
		float left, top, right, bottom;
	} textureBounds;
	bool rotate;
	float fragSize;
	float velocity;
	COLOR_POINT colors[COLOR_POINT_COUNT];
	float gravity;
	float varianceX;
	float varianceY;
	float varianceZ;
	int fragCount;
	float fragLifetime;
	float maxRepeat;
};

struct INDIE_EFFECT : public PARTICLE_EFFECT
{
	int maxIssue;
	float releasePeriod;
	float duration;
};

class ParticleEngine;
class ParticleEmitter;

class Particle
{
	friend class ParticleEngine;

public:
	Particle(PARTICLE_EFFECT *effect);

	void Execute(Scalar dT);
	inline bool IsAlive()	{ return (mEffect && ( mAge < mEffect->fragLifetime)); }
	inline Particle *Next()	{ return mNextParticle; }
	inline Particle *Prev()	{ return mPrevParticle; }

private:
	float mX, mY, mZ;
	L4COLOR mColor;
	D3DXVECTOR3 mVelocity;
	D3DXVECTOR3 mAcceleration;
	D3DXMATRIX mTextureTransform;
	
	PARTICLE_EFFECT *mEffect;
	Scalar mAge;

	Particle *mNextParticle;
	Particle *mPrevParticle;

	friend int compare(const Particle *, const Particle *, const D3DXMATRIX *);
	friend void mergesort(Particle **, Particle **, const D3DXMATRIX *);
};

class ParticleEmitter
{
public:
	ParticleEmitter();

	bool IsActive()		{ return mActive; }

	void SetEffect(int effect);
	void SetEffect(INDIE_EFFECT *effect)	{ mEffect = effect; }
	float GetMaxRepeat()						{ return mEffect->maxRepeat; }
	void Execute();
	void Fire()					{ mActive = true; }
	void Start()
	{
		mActive = true; 
		mActivated = (Scalar)Now();
	}
	void SetPosition(float x, float y, float z)
	{
		mPosition.x = x;
		mPosition.y = y;
		mPosition.z = z;
	}

private:
	PARTICLE_EFFECT *mEffect;
	bool mActive;
	Scalar mActivated;
	D3DXVECTOR3 mPosition;
};

class ParticleEngine
{
	friend class ParticleEmitter;

public:
	static void Destroy();
	static void Initialize(LPDIRECT3DDEVICE9 device);
	static void InstallEffect(int effectNumber, PARTICLE_EFFECT effect);
	static void RenderParticles(const D3DXMATRIX *view_matrix, Scalar timeSlice);

private:
	static void CreateParticle(D3DXVECTOR3 position, PARTICLE_EFFECT *effect);
	static int BuildParticleVertices(const Particle *p, L4BASICVERTEX *verts, D3DXMATRIX *view_matrix);
	static void ExecuteParticles(const D3DXMATRIX *view_matrix, Scalar timeSlice);
	
	static LPDIRECT3DDEVICE9 mDevice;
	
	static PARTICLE_EFFECT mInstalledEffects[];
	static LPDIRECT3DTEXTURE9 mParticleTexture;
	
	static bool mActiveParticles;
	static LPDIRECT3DVERTEXBUFFER9 mVertBuffer;

	static Particle *mParticlesHead;
	static Particle *mParticlesTail;
	static long mTotalParticleCount;
	static long mMaxParticleCount;
};