//Tips for making particles:
//For the particle textures, I suggest using 2 channel images, with an alpha channel that
//just dublicates the luminosity channel.
//This makes the blending between particles nicer.
//Such images can be easy created in paint shop by going to Masks->new->from image,
//then Masks->Save to Alpha Channel, then pasting a copy of the origional image
//into the window, and saving to a single alpha channel image format (like sgi).
//Warning, paintshop seems to have some problem with saving 2 channel sgi images.  
//You can work around it by increasing the color depth to 24.

//While it would have been possible for me to have made a fairly comprehensive
//generlized particle swarm class, there are so many weird ways that one might
//want to tweak it to get a specific effect that the general class would have
//have become a byzanten mess, both to write and to use.
//Thus, I am actively advocating cut and paste programming for the purpose of
//creating specilized particle swarm classes.  I have made a couple of pretty nice, modestly
//general swarm classes of my own.  Copy the one that seems most like what you want to to, 
//and hack whatever special features you want into it.  I know, it goes against everything
//that the great Bjarne teaches us, but in this situation, I really do think it is the
//right way to do it.

#ifndef PARTICLE_H
#define PARTICLE_H

#include <gl_sgg.h>

namespace effects {

//In order to make bill boarded textures in an arbitrary coordinate system,
//we need to have easy access to the basis vectors of the cordinate space.
namespace bill_boarding {
	extern GLfloat * iHat;
	extern GLfloat * jHat;
	extern GLfloat * kHat;
}


using sven::float3;
using nehe::neheTexture;
using namespace sgg;
using namespace gl_ex;

using namespace std_ex;



//this is the particle swarms base class.
//in the interest of speed, I am keeping particle swarms non-polymorphic.

//All particle swarms are generally assumed to have the following functions:

//void refresh()
//void draw() (default provided by ParticleSwarmCommon)
//some interesting constructor (usually taking in at least a position variable).

//As a general rule, Particle textures are assumed to be grayscale, and particles
//to have a color, though if you wanted you could always just load a colored texture
//and set the color of all particles to 0xffffff.
struct ParticleSwarmCommon {	
protected:
	struct Particle : public GLimage {
		float3 p,v;
		long age;
		sven::rgba color;

		Particle(float3 p, float3 v, sven::rgba color, GLimage i) 
			:  GLimage(i), p(p), v(v), color(color), age(0) {}
		
		void flat_draw() {
			glPushAttrib(GL_CURRENT_BIT);
			glColor(color);
			glPushMatrix();
			glTranslatefv(p.v);
			GLimage::draw();
			glPopMatrix();
			glPopAttrib();
		}

		void bb_draw();

		//required because of a problem in MSVC's STL implementation.
		struct mem_flat_draw { void operator()(Particle p) { p.flat_draw(); } };
		struct mem_bb_draw { void operator()(Particle p) { p.bb_draw(); } };
	};
	
	vector<Particle> particles;

public:

	static GLfloat pSpeed;

	void flat_draw() {
		glPushAttrib(GL_ENABLE_BIT);
		glDisable(GL_LIGHTING);
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		foreach(particles,Particle::mem_flat_draw());
		glPopAttrib();
	}

	void bb_draw() {
		glPushAttrib(GL_ENABLE_BIT);
		glDisable(GL_LIGHTING);
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		foreach(particles,Particle::mem_bb_draw());
		glPopAttrib();
	}

	void refresh() {
		for(vector<Particle>::iterator i=particles.begin();i!=particles.end();i++) {
			i->age++;
			i->p+=i->v;
		}
	}
};

//the float4's are in the range 0-255
struct cmap_point {
	float first;
	float4 second;
	bool operator<(const cmap_point &b) {
		return first<b.first;
	}
	cmap_point() : first(0.0f), second(0.0f,0.0f,0.0f,0.0f) {}
	cmap_point(float f, float4 color) : first(f), second(color) {}
};

class Linear_Color_Map {
	vector<cmap_point> points;	
	vector<float4> slopes;

	int getLast(float normalized_age) {
		if(normalized_age>1.f) normalized_age=1.f;
		if(normalized_age<0.f) normalized_age=0.f;

		for(int i=0;i<points.size();i++) {
			if(normalized_age<points[i].first) 
			{
				return i-1;
			}
		}
		return i-1;
	}

public:

	vector<cmap_point> get_points() { return points; }

	Linear_Color_Map(vector<cmap_point> ps) :
	points(ps) {
		if(!points.size()) {
			points.push_back(cmap_point(0.f,float4(255,255,255,255)));
		}

		sort(points.begin(),points.end());

		vector<cmap_point>::iterator i;

		points[0].first=0.f;
		for(i=points.begin();i!=points.end();i++) {
			if(i->first>1.f) i->first=1.f;
		}

		for(i=points.begin();i!=points.end();i++) {
			slopes.push_back( 
				((i+1)->second - i->second) /
				((i+1)->first  - i->first) );
		}

		if(!slopes.size()) slopes.push_back(float4(0,0,0,0));
	}

	rgba operator()(float normalized_age) {
		int last=getLast(normalized_age);
		return points[last].second+slopes[last]*(normalized_age-points[last].first);
	}

	friend ostream& operator<<(ostream &out, Linear_Color_Map &m) {
		out << "<color_map>\n";
		for(vector<cmap_point>::const_iterator i = m.points.begin();i!=m.points.end();i++) {
			out << i->first << " :: ( " 
				<< i->second[0] << " , " 
				<< i->second[1] << " , " 
				<< i->second[2] << " , " 
				<< i->second[3] << " )\n"; 
		}
		out << "<\\color_map>\n";
		return out;
	}

	friend Linear_Color_Map load_Linear_Color_Map(string::const_iterator &start, string::const_iterator finish);

};



template <class Color_Map = Linear_Color_Map>
struct StandardEmitter : public ParticleSwarmCommon {
	
	static vector<cmap_point> std_color_map() {
			vector<cmap_point> cm;
			cm.push_back(cmap_point(0.f,float4(255,255,255,100)));
			cm.push_back(cmap_point(.3f,float4(255,0,255,140)));
			cm.push_back(cmap_point(1.f,float4(0,0,255,10)));
			return cm;
	}

	//things that users are encouraged to mess with
	Color_Map color_fun;	
	float dir;

	float3 center;
	float g;
	int emit_rate;
	int max_age;
	float speed;

private:
	string texture_file;
	neheTexture texture;
public:
	void set_texture(string new_texture) {
		texture_file=new_texture;
		texture=neheTexture(new_texture);
	}

	string get_texture_file() { return texture_file; }

	bool done; //the 'off' switch

	//notice that this default does not work if you use a non-default color map type.  seems fair, no?
	StandardEmitter(
		float3 center=float3(0,0,0), 
		float dir=PI,
		int emit_rate=2,
		float speed=.5,
		int max_age=120,
		float g = .05, 
		string texture_file = "particle_images\\particle8.rgb",
		Color_Map color_fun=Linear_Color_Map(std_color_map())
		)

		
		: texture(texture_file), center(center), done(false), texture_file(texture_file),
		dir(dir), g(g), color_fun(color_fun), emit_rate(emit_rate), max_age(max_age),
		speed(speed) {}
	
	void push_particles(int n) {
		using namespace sgg;
		using namespace nehe;
		for(int i=0;i<n;i++) {	
			float theta = randFloat(2*(float)PI);
			particles.push_back(
				Particle(
					center+float3(randFloat(.1f),randFloat(.1f),0.f), 
					float3(cos(theta)*speed*pSpeed,sin(theta)*speed*pSpeed,0.f), 
					color_fun(0), 
					GLimage(.2f,.2f,texture) )); 
		}
	}

	void refresh() {
		float3 gv=float3(cos(dir)*-pSpeed*g,sin(dir)*-pSpeed*g,0);
		push_particles(emit_rate);

		for(int i=0;i<particles.size();) {
			particles[i].age++;
			particles[i].p+=particles[i].v;
			particles[i].v+=gv;
			particles[i].color=color_fun(particles[i].age/(float)max_age);
			particles[i].scale(.003f);

			if( particles[i].age>max_age ) {
				particles.erase(particles.begin()+i);
			}
			else i++;
		}
	}

	friend ostream& operator<<(ostream &out, StandardEmitter<Color_Map> &e) {
		out << "<standard_emitter>\n" << endl;
		out << "dir: " << e.dir << endl;
		out << "g: " << e.g << endl;
		out << "emit_rate: " << e.emit_rate << endl;
		out << "speed: " << e.speed << endl;
		out << "texture_file: \"" << e.texture_file << "\"" << endl;
		out << "color_fun:\n\n" << e.color_fun << endl;
		out << "<\\standard_emitter>" << endl;

		return out;
	}

	friend StandardEmitter<Color_Map> load_StandardEmitter(string::const_iterator &start, string::const_iterator finish);
};

//a general wrapper to take the basic (fast) Swarm classes, and turn
//them into SGG::Screen3v compatable (less fast) polymorphic classes.
template <class Swarm>
struct BasicSwarm3v : public Swarm, public Drawable, public Refreshable,
					public KillFlag {
	BasicSwarm3v(Swarm b=Swarm()) : Swarm(b) {}
	void refresh() { 
		Swarm::refresh(); 
		if(done) killme=true; 
	}
	void draw() { Swarm::flat_draw(); }
};


}

#endif
