#include "Explosion.h"

using namespace SPK;
using namespace SPK::GL;

namespace gln {

Explosion::~Explosion() {
	SPKFactory::getInstance().destroyAll();
}

void Explosion::initialize(GLuint textureExplosion, GLuint textureFlash,
		GLuint textureSpark, GLuint texturePoint, GLuint textureWave) {
	///////////////
	// Renderers //
	///////////////
	double scale = 50.0;

	// smoke renderer
	GLQuadRenderer* smokeRenderer = GLQuadRenderer::create();
	smokeRenderer->setTexturingMode(TEXTURE_2D);
	smokeRenderer->setTexture(textureExplosion);
	smokeRenderer->setTextureBlending(GL_MODULATE);
	smokeRenderer->setAtlasDimensions(2, 2); // uses 4 different patterns in the texture
	smokeRenderer->setBlending(BLENDING_ALPHA);
	smokeRenderer->enableRenderingHint(DEPTH_WRITE, false);
	smokeRenderer->setShared(true);
	smokeRenderer->setScale(scale, scale);

	// flame renderer
	GLQuadRenderer* flameRenderer = GLQuadRenderer::create();
	flameRenderer->setTexturingMode(TEXTURE_2D);
	flameRenderer->setTexture(textureExplosion);
	flameRenderer->setTextureBlending(GL_MODULATE);
	flameRenderer->setAtlasDimensions(2, 2);
	flameRenderer->setBlending(BLENDING_ADD);
	flameRenderer->enableRenderingHint(DEPTH_WRITE, false);
	flameRenderer->setShared(true);
	flameRenderer->setScale(scale, scale);

	// flash renderer
	GLQuadRenderer* flashRenderer = GLQuadRenderer::create();
	flashRenderer->setTexturingMode(TEXTURE_2D);
	flashRenderer->setTexture(textureFlash);
	flashRenderer->setTextureBlending(GL_REPLACE);
	flashRenderer->setBlending(BLENDING_ADD);
	flashRenderer->enableRenderingHint(DEPTH_WRITE, false);
	flashRenderer->setShared(true);
	flashRenderer->setScale(scale, scale);

	// spark 1 renderer
	GLQuadRenderer* spark1Renderer = GLQuadRenderer::create();
	spark1Renderer->setTexturingMode(TEXTURE_2D);
	spark1Renderer->setTexture(textureSpark);
	spark1Renderer->setTextureBlending(GL_REPLACE);
	spark1Renderer->setBlending(BLENDING_ADD);
	spark1Renderer->enableRenderingHint(DEPTH_WRITE, false);
	spark1Renderer->setOrientation(DIRECTION_ALIGNED); // sparks are oriented function o their velocity
	spark1Renderer->setScale(0.05f * scale, 1.0f * scale); // thin rectangles
	spark1Renderer->setShared(true);

	// spark 2 renderer
	GLRenderer* spark2Renderer = NULL;
	if (GLPointRenderer::loadGLExtPointSprite()
			&& GLPointRenderer::loadGLExtPointParameter()) // uses point sprite if possible
			{
		//GLPointRenderer::setPixelPerUnit(45.0f * PI / 180.f, scale * 1024);
		GLPointRenderer* pointRenderer = GLPointRenderer::create();
		pointRenderer->setType(POINT_SPRITE);
		pointRenderer->setTexture(texturePoint);
		pointRenderer->setTextureBlending(GL_MODULATE);
		pointRenderer->enableWorldSize(true);
		pointRenderer->setSize(0.02f * scale);
		spark2Renderer = pointRenderer;
	} else {
		GLQuadRenderer* quadRenderer = GLQuadRenderer::create();
		quadRenderer->setTexturingMode(TEXTURE_2D);
		quadRenderer->setTexture(texturePoint);
		quadRenderer->setTextureBlending(GL_MODULATE);
		quadRenderer->setScale(0.02f * scale, 0.02f * scale);
		spark2Renderer = quadRenderer;
	}
	spark2Renderer->setBlending(BLENDING_ADD);
	spark2Renderer->enableRenderingHint(DEPTH_WRITE, false);
	spark2Renderer->setShared(true);

	// wave renderer
	GLQuadRenderer* waveRenderer = GLQuadRenderer::create();
	waveRenderer->setTexturingMode(TEXTURE_2D);
	waveRenderer->setTexture(textureWave);
	waveRenderer->setTextureBlending(GL_MODULATE);
	waveRenderer->setBlending(BLENDING_ALPHA);
	waveRenderer->enableRenderingHint(DEPTH_WRITE, false);
	waveRenderer->enableRenderingHint(ALPHA_TEST, true); // uses the alpha test
	waveRenderer->setAlphaTestThreshold(0.0f);
	waveRenderer->setOrientation(FIXED_ORIENTATION); // the orientatin is fixed
	waveRenderer->lookVector.set(0.0f, 1.0f, 0.0f);
	waveRenderer->upVector.set(1.0f, 0.0f, 0.0f); // we dont really care about the up axis
	waveRenderer->setShared(true);
	waveRenderer->setScale(scale, scale);

	////////////
	// Models //
	////////////

	Interpolator* interpolator = NULL; // pointer to an interpolator that is used to retrieve interpolators

	// smoke model
	Model* smokeModel = Model::create(
			FLAG_RED | FLAG_GREEN | FLAG_BLUE | FLAG_ALPHA | FLAG_SIZE
					| FLAG_ANGLE | FLAG_TEXTURE_INDEX, FLAG_SIZE | FLAG_ANGLE,
			FLAG_SIZE | FLAG_ANGLE | FLAG_TEXTURE_INDEX, FLAG_ALPHA);
	smokeModel->setParam(PARAM_RED, 0.2f);
	smokeModel->setParam(PARAM_GREEN, 0.2f);
	smokeModel->setParam(PARAM_BLUE, 0.2f);
	smokeModel->setParam(PARAM_SIZE, 0.6f, 0.8f, 1.0f, 1.4f);
	smokeModel->setParam(PARAM_TEXTURE_INDEX, 0.0f, 4.0f);
	smokeModel->setParam(PARAM_ANGLE, 0.0f, M_PI * 0.5f, 0.0f, M_PI * 0.5f);
	smokeModel->setLifeTime(2.5f, 3.0f);
	smokeModel->setShared(true);

	interpolator = smokeModel->getInterpolator(PARAM_ALPHA);
	interpolator->addEntry(0.0f, 0.0f);
	interpolator->addEntry(0.4f, 0.4f, 0.6f);
	interpolator->addEntry(0.6f, 0.4f, 0.6f);
	interpolator->addEntry(1.0f, 0.0f);

	// flame model
	Model* flameModel = Model::create(
			FLAG_RED | FLAG_GREEN | FLAG_BLUE | FLAG_ALPHA | FLAG_SIZE
					| FLAG_ANGLE | FLAG_TEXTURE_INDEX,
			FLAG_ANGLE | FLAG_RED | FLAG_GREEN | FLAG_BLUE,
			FLAG_ANGLE | FLAG_TEXTURE_INDEX, FLAG_SIZE | FLAG_ALPHA);
	flameModel->setParam(PARAM_RED, 1.0f, 0.2f);
	flameModel->setParam(PARAM_GREEN, 0.5f, 0.2f);
	flameModel->setParam(PARAM_BLUE, 0.2f, 0.2f);
	flameModel->setParam(PARAM_TEXTURE_INDEX, 0.0f, 4.0f);
	flameModel->setParam(PARAM_ANGLE, 0.0f, M_PI * 0.5f, 0.0f, M_PI * 0.5f);
	flameModel->setLifeTime(1.5f, 2.0f);
	flameModel->setShared(true);

	interpolator = flameModel->getInterpolator(PARAM_SIZE);
	interpolator->addEntry(0.0f, 0.25f);
	interpolator->addEntry(0.02f, 0.6f, 0.8f);
	interpolator->addEntry(1.0f, 1.0f, 1.4f);

	interpolator = flameModel->getInterpolator(PARAM_ALPHA);
	interpolator->addEntry(0.5f, 1.0f);
	interpolator->addEntry(1.0f, 0.0f);

	// flash model
	Model* flashModel = Model::create(FLAG_ALPHA | FLAG_SIZE | FLAG_ANGLE,
			FLAG_NONE, FLAG_ANGLE, FLAG_ALPHA | FLAG_SIZE);
	flashModel->setParam(PARAM_ANGLE, 0.0f, 2.0f * M_PI);
	flashModel->setLifeTime(0.5f, 0.5f);
	flashModel->setShared(true);

	interpolator = flashModel->getInterpolator(PARAM_SIZE);
	interpolator->addEntry(0.0f, 0.25f);
	interpolator->addEntry(0.1f, 1.0f, 2.0f);

	interpolator = flashModel->getInterpolator(PARAM_ALPHA);
	interpolator->addEntry(0.0f, 1.0f);
	interpolator->addEntry(0.4f, 0.0f);

	// spark 1 model
	Model* spark1Model = Model::create(FLAG_SIZE | FLAG_ALPHA, FLAG_ALPHA,
			FLAG_SIZE);
	spark1Model->setParam(PARAM_ALPHA, 1.0f, 0.0f);
	spark1Model->setParam(PARAM_SIZE, 0.2f, 0.4f);
	spark1Model->setLifeTime(0.2f, 1.0f);
	spark1Model->setShared(true);

	// spark 2 model
	Model* spark2Model = Model::create(
			FLAG_RED | FLAG_GREEN | FLAG_BLUE | FLAG_ALPHA,
			FLAG_GREEN | FLAG_BLUE | FLAG_ALPHA, FLAG_GREEN);
	spark2Model->setParam(PARAM_ALPHA, 1.0f, 0.0f);
	spark2Model->setParam(PARAM_RED, 1.0f);
	spark2Model->setParam(PARAM_GREEN, 1.0f, 1.0f, 0.3f, 1.0f);
	spark2Model->setParam(PARAM_BLUE, 0.7f, 0.3f);
	spark2Model->setLifeTime(1.0f, 3.0f);
	spark2Model->setShared(true);

	// wave model
	Model* waveModel = Model::create(FLAG_ALPHA | FLAG_SIZE,
			FLAG_SIZE | FLAG_ALPHA);
	waveModel->setParam(PARAM_SIZE, 0.0f, 4.0f);
	waveModel->setParam(PARAM_ALPHA, 0.2f, 0.0f);
	waveModel->setLifeTime(0.8f, 0.8f);
	waveModel->setShared(true);

	//////////////
	// Emitters //
	//////////////

	// This zone will be used by several emitters
	Sphere* explosionSphere = Sphere::create(Vector3D(0.0f, 0.0f, 0.0f),
			0.4f * scale);
	Sphere* explosionSphere2 = Sphere::create(Vector3D(0.0f, 0.0f, 0.0f),
			0.4f * 2.5 * scale);

	// smoke emitter
	RandomEmitter* smokeEmitter = RandomEmitter::create();
	smokeEmitter->setZone(Sphere::create(Vector3D(0.0f, 0.0f, 0.0f), 0.6f),
			false);
	smokeEmitter->setFlow(-1);
	smokeEmitter->setTank(1);
	smokeEmitter->setForce(0.02f, 10.04f);

	// flame emitter
	NormalEmitter* flameEmitter = NormalEmitter::create();
	flameEmitter->setZone(explosionSphere);
	flameEmitter->setFlow(-1);
	flameEmitter->setTank(1);
	flameEmitter->setForce(0.06f, 10.1f);

	// flash emitter
	StaticEmitter* flashEmitter = StaticEmitter::create();
	flashEmitter->setZone(Sphere::create(Vector3D(0.0f, 0.0f, 0.0f), 0.1f));
	flashEmitter->setFlow(-1);
	flashEmitter->setTank(2);

	// spark 1 emitter
	NormalEmitter* spark1Emitter = NormalEmitter::create();
	spark1Emitter->setZone(explosionSphere2);
	spark1Emitter->setFlow(-1);
	spark1Emitter->setTank(5);
	spark1Emitter->setForce(2.0f, 3.0f);

	// spark 2 emitter
	NormalEmitter* spark2Emitter = NormalEmitter::create();
	spark2Emitter->setZone(explosionSphere);
	spark2Emitter->setFlow(-1);
	spark2Emitter->setTank(2);
	spark2Emitter->setForce(0.4f, 0.8f);

	// wave emitter
	StaticEmitter* waveEmitter = StaticEmitter::create();
	waveEmitter->setZone(Point::create());
	waveEmitter->setFlow(-1);
	waveEmitter->setTank(1);

	////////////
	// Groups //
	////////////

	// smoke group
	Group* smokeGroup = Group::create(smokeModel, 15);
	smokeGroup->addEmitter(smokeEmitter);
	smokeGroup->setRenderer(smokeRenderer);

	// flame group
	Group* flameGroup = Group::create(flameModel, 15);
	flameGroup->addEmitter(flameEmitter);
	flameGroup->setRenderer(flameRenderer);

	// flash group
	Group* flashGroup = Group::create(flashModel, 3);
	flashGroup->addEmitter(flashEmitter);
	flashGroup->setRenderer(flashRenderer);

	// spark 1 group
	Group* spark1Group = Group::create(spark1Model, 20);
	spark1Group->addEmitter(spark1Emitter);
	spark1Group->setRenderer(spark1Renderer);

	// spark 2 group
	Group* spark2Group = Group::create(spark2Model, 400);
	spark2Group->addEmitter(spark2Emitter);
	spark2Group->setRenderer(spark2Renderer);

	// wave group
	Group* waveGroup = Group::create(waveModel, 1);
	waveGroup->addEmitter(waveEmitter);
	waveGroup->setRenderer(waveRenderer);

	////////////
	// System //
	////////////

	System* system = System::create();
	system->addGroup(waveGroup);
	system->addGroup(smokeGroup);
	system->addGroup(flameGroup);
	system->addGroup(flashGroup);
	system->addGroup(spark1Group);
	system->addGroup(spark2Group);

	// Gets a pointer to the base
	system_id = system->getSPKID();

	// Sets the update step
	// clamp the step to 100 ms
	System::setClampStep(true, 0.1f);
	// use an adaptive step from 1ms to 10ms (1000fps to 100fps)
	System::useAdaptiveStep(0.001f, 0.01f);
}

// Renders the scene
void Explosion::render() {
	GLRenderer::saveGLStates();
	// Renders all the particle systems
	for (std::list<System*>::const_iterator it = particleSystems.begin();
			it != particleSystems.end(); ++it)
		(*it)->render();
	GLRenderer::restoreGLStates();
}

void Explosion::add(double x, double y, double z) {
	Vector3D pos(x, y, z);
	// Creates a copy of the base system
	System* system = SPK_Copy(System,system_id);

	// Locates the system at the given position
	system->setTransformPosition(pos);

	// updates the world transform of system and its children
	system->updateTransform();
	particleSystems.push_back(system);
}

// This function is used to sort the systems from front to back
struct cmpDistToCamera {
	Vector3D camera;
	bool operator()(System* &system0, System* &system1) {
		return getSqrDist(system0->getWorldTransformPos(), camera)
				> getSqrDist(system1->getWorldTransformPos(), camera);
	}
};

void Explosion::update(double dt, double cx, double cy, double cz) {
	cmpDistToCamera cmp;
	cmp.camera.x = cx;
	cmp.camera.y = cy;
	cmp.camera.z = cz;
	particleSystems.sort(cmp);

	std::list<System*>::iterator it = particleSystems.begin();
	while (it != particleSystems.end()) {
		// Updates the particle systems
		if (!(*it)->update(dt * 0.001f)) {
			// If a system is sleeping, destroys it
			SPK_Destroy(*it);
			// And erases its entry in the container
			it = particleSystems.erase(it);
		} else
			++it;
	}
}

} // namespace grln
