/***************************************************************************
 *   Copyright (C) 2004 by David Sansome                                   *
 *   me@davidsansome.com                                                   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <stdlib.h>
#include <kapplication.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kconfig.h>
#include <qcheckbox.h>
#include <qcolor.h>
#include <kglobal.h>
#include <qtimer.h>
#include <kconfig.h>
#include <qlayout.h> 
#include <sys/time.h> 
#include <math.h>
#include <qlabel.h> 
#include "skyrocket.h"
#include "skyrocket_particle.h"
#include "skyrocket_data.h"

extern int libopenal_is_present;


extern "C"
{
	const char *kss_applicationName = "skyrocket.kss";
	const char *kss_description = I18N_NOOP( "Skyrocket" );
	const char *kss_version = "0.1";
	
	KSkyrocketScreenSaver *kss_create( WId id )
	{
		return new KSkyrocketScreenSaver( id );
	}
	
	QDialog *kss_setup()
	{
		return new KSkyrocketSetup();
	}
}


KSkyrocketScreenSaver::KSkyrocketScreenSaver( WId id )
 : KScreenSaver( id )
{
	readSettings();
	
	_effect = new SkyrocketWidget( NULL, 0, params);
	embed( _effect );
	_effect->show();
}

KSkyrocketScreenSaver::KSkyrocketScreenSaver( WId id, SkyrocketParams p )
 : KScreenSaver( id ), params(p)
{
	_effect = new SkyrocketWidget( NULL, 0, params);
	embed( _effect );
	_effect->show();
}



KSkyrocketScreenSaver::~KSkyrocketScreenSaver()
{
	delete _effect;
}


void KSkyrocketScreenSaver::readSettings()
{
	KConfig* config = KGlobal::config();
	config->setGroup("Settings");
	
	params.dMaxrockets = config->readNumEntry( "Maxrockets", 8 );
	params.dSmoke = config->readNumEntry( "Smoke", 5 );
	params.dExplosionsmoke = config->readNumEntry( "Explosionsmoke", 0 );
	params.dWind = config->readNumEntry( "Wind", 20 );
	params.dAmbient = config->readNumEntry( "Ambient", 10 );
	params.dStardensity = config->readNumEntry( "Stardensity", 20 );
	params.dFlare = config->readNumEntry( "Flare", 20 );
	params.dMoonglow = config->readNumEntry( "Moonglow", 20 );
	params.dSound = config->readNumEntry( "Sound", 100 );
	
	params.dMoon = config->readBoolEntry( "Moon", true );
	params.dClouds = config->readBoolEntry( "Clouds", true );
	params.dIllumination = config->readBoolEntry( "Illumination", true );
	params.dEarth = config->readBoolEntry( "Earth", true );
}


//----------------------------------------------------------------------------


SkyrocketWidget::SkyrocketWidget( QWidget* parent, const char* name, SkyrocketParams p )
 : QGLWidget(parent, name), numRockets(0), numFlares(0), elapsedTime(0.0f), params(p)
{
	struct timeval now;
	gettimeofday (&now, NULL);
	srand((now.tv_sec & 65535) * 1000 + now.tv_usec / 1000);
	
	loadSkyrocketData();
	currentWidget = this;
	
	if (!libopenal_is_present)
		params.dSound = 0;
	
	lookAt[0] = rsVec(0.0f, 1200.0f, 0.0f);
	lookAt[1] = rsVec(0.0f, 1200.0f, 0.0f);
	lookAt[2] = rsVec(0.0f, 1200.0f, 0.0f);
	
	currentParticle = particles.begin();
	
	_timer = new QTimer( this );
	_frameTime = 1000 / 60;
	connect( _timer, SIGNAL(timeout()), this, SLOT(nextFrame()) );
}


SkyrocketWidget::~SkyrocketWidget()
{
	freeSkyrocketData();
}


particle* SkyrocketWidget::addParticle()
{
	static particle* tempPart;

	if(currentParticle != particles.end())
	{
		currentParticle->depth = 10000;
		tempPart = currentParticle->thisParticle();
		currentParticle ++;
		return(tempPart);
	}
	else
	{
		particles.push_back(particle(this));
		tempPart = &(particles.back());
		tempPart->depth = 10000;
		currentParticle = particles.end();
		return(tempPart);
	}
}


void SkyrocketWidget::removeParticle(std::list<particle>::iterator curpart)
{
	curpart->depth = MINDEPTH;
}


void SkyrocketWidget::sortParticles()
{
	particles.sort();
	currentParticle = particles.begin();
	while(currentParticle->depth > MINDEPTH && currentParticle != particles.end())
		currentParticle ++;
}


// Rockets and explosions illuminate smoke
// Only explosions illuminate clouds
void SkyrocketWidget::illuminate(particle* ill)
{
	float temp;
	// desaturate illumination colors
	rsVec newrgb(ill->rgb[0] * 0.6f + 0.4f, ill->rgb[1] * 0.6f + 0.4f, ill->rgb[2] * 0.6f + 0.4f);

	// Smoke illumination
	if((ill->type == ROCKET) || (ill->type == FOUNTAIN))
	{
		float distsquared;
		std::list<particle>::iterator smk = particles.begin();
		while(smk != particles.end()){
			if(smk->depth > MINDEPTH && smk->type == SMOKE)
			{
				distsquared = (ill->xyz[0] - smk->xyz[0]) * (ill->xyz[0] - smk->xyz[0])
					+ (ill->xyz[1] - smk->xyz[1]) * (ill->xyz[1] - smk->xyz[1])
					+ (ill->xyz[2] - smk->xyz[2]) * (ill->xyz[2] - smk->xyz[2]);
				if(distsquared < 40000.0f)
				{
					temp = (40000.0f - distsquared) * 0.000025f;
					temp = temp * temp * ill->bright;
					smk->rgb[0] += temp * newrgb[0];
					if(smk->rgb[0] > 1.0f)
						smk->rgb[0] = 1.0f;
					smk->rgb[1] += temp * newrgb[1];
					if(smk->rgb[1] > 1.0f)
						smk->rgb[1] = 1.0f;
					smk->rgb[2] += temp * newrgb[2];
					if(smk->rgb[2] > 1.0f)
						smk->rgb[2] = 1.0f;
				}
			}
			smk++;
		}
	}
	if(ill->type == EXPLOSION)
	{
		float distsquared;
		std::list<particle>::iterator smk = particles.begin();
		while(smk != particles.end())
		{
			if(smk->depth > MINDEPTH && smk->type == SMOKE)
			{
				distsquared = (ill->xyz[0] - smk->xyz[0]) * (ill->xyz[0] - smk->xyz[0])
					+ (ill->xyz[1] - smk->xyz[1]) * (ill->xyz[1] - smk->xyz[1])
					+ (ill->xyz[2] - smk->xyz[2]) * (ill->xyz[2] - smk->xyz[2]);
				if(distsquared < 640000.0f)
				{
					temp = (640000.0f - distsquared) * 0.0000015625f;
					temp = temp * temp * ill->bright;
					smk->rgb[0] += temp * newrgb[0];
					if(smk->rgb[0] > 1.0f)
						smk->rgb[0] = 1.0f;
					smk->rgb[1] += temp * newrgb[1];
					if(smk->rgb[1] > 1.0f)
						smk->rgb[1] = 1.0f;
					smk->rgb[2] += temp * newrgb[2];
					if(smk->rgb[2] > 1.0f)
						smk->rgb[2] = 1.0f;
				}
			}
			smk++;
		}
	}

	// cloud illumination
	if(ill->type == EXPLOSION && params.dClouds)
	{
		int north, south, west, east;  // limits of cloud indices to inspect
		int halfmesh = CLOUDMESH / 2;
		float distsquared;
		// remember clouds have 20000-foot radius from world.h, hence 0.00005
		// Hardcoded values like this are evil, but oh well
		south = int((ill->xyz[2] - 1600.0f) * 0.00005f * float(halfmesh)) + halfmesh;
		north = int((ill->xyz[2] + 1600.0f) * 0.00005f * float(halfmesh) + 0.5f) + halfmesh;
		west = int((ill->xyz[0] - 1600.0f) * 0.00005f * float(halfmesh)) + halfmesh;
		east = int((ill->xyz[0] + 1600.0f) * 0.00005f * float(halfmesh) + 0.5f) + halfmesh;
		for(int i=west; i<=east; i++)
		{
			for(int j=south; j<=north; j++)
			{
				distsquared = (clouds[i][j][0] - ill->xyz[0]) * (clouds[i][j][0] - ill->xyz[0])
					+ (clouds[i][j][1] - ill->xyz[1]) * (clouds[i][j][1] - ill->xyz[1])
					+ (clouds[i][j][2] - ill->xyz[2]) * (clouds[i][j][2] - ill->xyz[2]);
				if(distsquared < 2560000.0f)
				{
					temp = (2560000.0f - distsquared) * 0.000000390625f;
					temp = temp * temp * ill->bright;
					clouds[i][j][6] += temp * newrgb[0];
					if(clouds[i][j][6] > 1.0f)
						clouds[i][j][6] = 1.0f;
					clouds[i][j][7] += temp * newrgb[1];
					if(clouds[i][j][7] > 1.0f)
						clouds[i][j][7] = 1.0f;
					clouds[i][j][8] += temp * newrgb[2];
					if(clouds[i][j][8] > 1.0f)
						clouds[i][j][8] = 1.0f;
				}
			}
		}
	}
}


// pulling of other particles
void SkyrocketWidget::pulling(particle* suck)
{
	rsVec diff;
	float pulldistsquared;
	float pullconst = (1.0f - suck->life) * 0.01f * elapsedTime;

	std::list<particle>::iterator puller = particles.begin();
	while(puller != particles.end())
	{
		if(puller->depth > MINDEPTH)
		{
			diff = suck->xyz - puller->xyz;
			pulldistsquared = diff[0]*diff[0] + diff[1]*diff[1] + diff[2]*diff[2];
			if(pulldistsquared < 250000.0f && pulldistsquared != 0.0f && puller->type != SUCKER)
			{
				diff.normalize();
				puller->vel += diff * ((250000.0f - pulldistsquared) * pullconst);
			}
		}
		puller ++;
	}
}


// pushing of other particles
void SkyrocketWidget::pushing(particle* shock)
{
	rsVec diff;
	float pushdistsquared;
	float pushconst = (1.0f - shock->life) * 0.002f * elapsedTime;

	std::list<particle>::iterator pusher = particles.begin();
	while(pusher != particles.end())
	{
		if(pusher->depth > MINDEPTH)
		{
			diff = pusher->xyz - shock->xyz;
			pushdistsquared = diff[0]*diff[0] + diff[1]*diff[1] + diff[2]*diff[2];
			if(pushdistsquared < 640000.0f && pushdistsquared != 0.0f && pusher->type != SHOCKWAVE)
			{
				diff.normalize();
				pusher->vel += diff * ((640000.0f - pushdistsquared) * pushconst);
			}
		}
		pusher ++;
	}
}


// vertical stretching of other particles (x, z sucking; y pushing)
void SkyrocketWidget::stretching(particle* stretch)
{
	rsVec diff;
	float stretchdistsquared, temp;
	float stretchconst = (1.0f - stretch->life) * 0.002f * elapsedTime;

	std::list<particle>::iterator stretcher = particles.begin();
	while(stretcher != particles.end())
	{
		if(stretcher->depth > MINDEPTH)
		{
			diff = stretch->xyz - stretcher->xyz;
			stretchdistsquared = diff[0]*diff[0] + diff[1]*diff[1] + diff[2]*diff[2];
			if(stretchdistsquared < 640000.0f && stretchdistsquared != 0.0f && stretcher->type != STRETCHER)
			{
				diff.normalize();
				temp = (640000.0f - stretchdistsquared) * stretchconst;
				stretcher->vel[0] += diff[0] * temp * 5.0f;
				stretcher->vel[1] -= diff[1] * temp;
				stretcher->vel[2] += diff[2] * temp * 5.0f;
			}
		}
		stretcher ++;
	}
}


// Makes list of lens flares.  Must be a called even when action is paused
// because camera might still be moving.
void SkyrocketWidget::makeFlareList()
{
	rsVec cameraDir, partDir;

	cameraDir = lookAt[0] - lookFrom[0];
	cameraDir.normalize();
	std::list<particle>::iterator curlight = particles.begin();
	while(curlight->depth > MINDEPTH && curlight != particles.end() && numFlares < MAXFLARES)
	{
		if(curlight->type == EXPLOSION)
		{
			double winx, winy, winz;
			gluProject(curlight->xyz[0], curlight->xyz[1], curlight->xyz[2],
				modelMat, projMat, viewport,
				&winx, &winy, &winz);
			partDir = curlight->xyz - cameraPos;
			if(partDir.dot(cameraDir) > 1.0f)  // is light source in front of camera?
			{
				allflares[numFlares][0] = 0;
				allflares[numFlares][1] = (float(winx) / float(xsize)) * aspectRatio;
				allflares[numFlares][2] = float(winy) / float(ysize);
				allflares[numFlares][3] = curlight->rgb[0];
				allflares[numFlares][4] = curlight->rgb[1];
				allflares[numFlares][5] = curlight->rgb[2];
				rsVec vector = curlight->xyz - cameraPos;  // find distance attenuation factor
				float distatten = (10000.0f - vector.length()) * 0.0001f;
				if(distatten < 0.0f)
					distatten = 0.0f;
				allflares[numFlares][6] = curlight->bright * float(params.dFlare) * 0.01f * distatten;
				numFlares++;
			}
		}
		if(curlight->type == SUCKER || curlight->type == SHOCKWAVE
			|| curlight->type == STRETCHER || curlight->type == BIGMAMA)
		{
			double winx, winy, winz;
			float temp;
			gluProject(curlight->xyz[0], curlight->xyz[1], curlight->xyz[2],
				modelMat, projMat, viewport,
				&winx, &winy, &winz);
			partDir = curlight->xyz - cameraPos;
			if(partDir.dot(cameraDir) > 1.0f)  // is light source in front of camera?
			{
				allflares[numFlares][0] = 1;  // superFlare
				allflares[numFlares][1] = (float(winx) / float(xsize)) * aspectRatio;
				allflares[numFlares][2] = float(winy) / float(ysize);
				allflares[numFlares][3] = curlight->rgb[0];
				allflares[numFlares][4] = curlight->rgb[1];
				allflares[numFlares][5] = curlight->rgb[2];
				rsVec vector = curlight->xyz - cameraPos;  // find distance attenuation factor
				float distatten = (20000.0f - vector.length()) * 0.00005f;
				if(distatten < 0.0f)
					distatten = 0.0f;
				temp = 1.0f - (curlight->bright - 0.5f) * float(params.dFlare) * 0.02f;
				allflares[numFlares][6] = (1.0f - temp * temp * temp * temp) * distatten;
				numFlares++;
			}
		}
		curlight++;
	}
}


void SkyrocketWidget::paintGL()
{
	int i, j;
	static float cameraTime[3]  // time, elapsed time, step (1.0 - 0.0)
		= {20.0f, 0.0f, 0.0f};

	// super fast easter egg
	static int superFast = myRandi(2000);
	if(!superFast)
		elapsedTime *= 5.0f;

	// build viewing matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0f, aspectRatio, 1.0f, 40000.0f);
	glGetDoublev(GL_PROJECTION_MATRIX, projMat);

	// update camera
	static int first = 1;
	if(first)
	{
		lookFrom[1] = rsVec(myRandf(3000.0f) - 1500.0f, 400.0f, myRandf(3000.0f) - 1500.0f);
		lookFrom[2] = rsVec(myRandf(1000.0f) + 5000.0f, 5.0f, myRandf(4000.0f) - 2000.0f);
		first = 0;
	}
	cameraTime[1] += elapsedTime;
	cameraTime[2] = cameraTime[1] / cameraTime[0];
	if(cameraTime[2] >= 1.0f)  // reset camera sequence
	{
		// reset timer
		cameraTime[0] = myRandf(25.0f) + 5.0f;
		cameraTime[1] = 0.0f;
		cameraTime[2] = 0.0f;
		// choose new positions
		lookFrom[2] = lookFrom[1];  // last = target
		lookFrom[1][0] = myRandf(6000.0f) - 3000.0f;  // new target position
		lookFrom[1][1] = myRandf(1700.0f) + 5.0f;
		lookFrom[1][2] = myRandf(6000.0f) - 3000.0f;
		lookAt[2] = lookAt[1];  // last = target
		lookAt[1][0] = myRandf(1000.0f) - 500.0f;  // new target position
		lookAt[1][1] = myRandf(1100.0f) + 200.0f;
		lookAt[1][2] = myRandf(1000.0f) - 500.0f;
		if(!myRandi(3))  // cut to a new view
		{
			lookFrom[2][0] = myRandf(6000.0f) - 3000.0f;  // new last position
			lookFrom[2][1] = myRandf(1700.0f) + 5.0f;
			lookFrom[2][2] = myRandf(6000.0f) - 3000.0f;
			lookAt[2][0] = myRandf(1000.0f) - 500.0f;  // new last position
			lookAt[2][1] = myRandf(1100.0f) + 200.0f;
			lookAt[2][2] = myRandf(1000.0f) - 500.0f;
		}
	}
	// change camera position and angle
	float cameraStep = 0.5f * (1.0f - cosf(cameraTime[2] * PI));
	lookFrom[0] = lookFrom[2] + ((lookFrom[1] - lookFrom[2]) * cameraStep);
	lookAt[0] = lookAt[2] + ((lookAt[1] - lookAt[2]) * cameraStep);
	// update variables used for sound and lens flares
	cameraVel = lookFrom[0] - cameraPos;
	cameraPos = lookFrom[0];

	// Build modelview matrix
	// Don't use gluLookAt() because it's easier to find the billboard matrix
	// if we know the heading and pitch
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	static float heading, pitch;
	float radius = sqrtf((lookAt[0][0] - lookFrom[0][0]) * (lookAt[0][0] - lookFrom[0][0])
		+ (lookAt[0][2] - lookFrom[0][2]) * (lookAt[0][2] - lookFrom[0][2]));
	pitch = R2D * atan2f(lookAt[0][1] - lookFrom[0][1], radius);
	heading = R2D * atan2f(lookFrom[0][0] - lookAt[0][0], lookFrom[0][2] - lookAt[0][2]);
	glRotatef(-pitch, 1, 0, 0);
	glRotatef(-heading, 0, 1, 0);
	glTranslatef(-lookFrom[0][0], -lookFrom[0][1], -lookFrom[0][2]);
	// get modelview matrix for flares
	glGetDoublev(GL_MODELVIEW_MATRIX, modelMat);

	// update billboard rotation matrix for particles
	glPushMatrix();
	glLoadIdentity();
	glRotatef(heading, 0, 1, 0);
	glRotatef(pitch, 1, 0, 0);
	glGetDoublev(GL_MODELVIEW_MATRIX, billboardMat);
	glPopMatrix();

	// clear the screen
	glClear(GL_COLOR_BUFFER_BIT);

	// update world
	updateWorld();

	// darken smoke
	std::list<particle>::iterator darkener = particles.begin();
	
	static float ambientlight = float(params.dAmbient) * 0.01f;
	while(darkener != particles.end())
	{
		if(darkener->depth <= MINDEPTH)
			darkener = particles.end();
		else
		{
			if(darkener->type == SMOKE)
				darkener->rgb[0] = darkener->rgb[1] = darkener->rgb[2] = ambientlight;
			darkener++;
		}
	}

	// Change rocket firing rate
	static float rocketTimer = 0.0f;
	static float rocketTimeConst = 10.0f / float(params.dMaxrockets);
	static float changeRocketTimeConst = 20.0f;
	changeRocketTimeConst -= elapsedTime;
	if(changeRocketTimeConst <= 0.0f)
	{
		float temp = myRandf(4.0f);
		rocketTimeConst = (temp * temp) + (10.0f / float(params.dMaxrockets));
		changeRocketTimeConst = myRandf(30.0f) + 10.0f;
	}
	// add new rocket to list
	rocketTimer -= elapsedTime;
	if(rocketTimer <= 0.0f)
	{
		if(numRockets < params.dMaxrockets)
		{
			particle* rock = addParticle();
			if(myRandi(30))  // Usually launch a rocket
			{
				rock->initRocket();
				if(!myRandi(3000))  // big one!
				{
					if(myRandi(2))
						rock->explosiontype = 19;  // sucker and shockwave
					else
						rock->explosiontype = 20;  // stretcher and bigmama
				}
				else
				{
					// Distribution of regular explosions
					if(myRandi(2))  // 0 - 2 (all types of spheres)
					{
						if(!myRandi(10))
							rock->explosiontype = 2;
						else
							rock->explosiontype = myRandi(2);
					}
					else
					{
						if(!myRandi(3))  //  ring, double sphere, sphere and ring
							rock->explosiontype = myRandi(3) + 3;
						else{
							if(myRandi(2))  // 6, 7, 8, 9, 10, 11
							{
								if(myRandi(2))
									rock->explosiontype = myRandi(2) + 6;
								else
									rock->explosiontype = myRandi(4) + 8;
							}
							else
							{
								if(myRandi(2))  // 12, 13, 14
									rock->explosiontype = myRandi(3) + 12;
								else  // 15 - 18
									rock->explosiontype = myRandi(4) + 15;
							}
						}
					}
				}
				numRockets++;
			}
			else  // sometimes make fountains and spinners instead of rockets
			{
				rock->initFountain();
				i = myRandi(2);
				for(j=0; j<i; j++)
				{
					rock = addParticle();
					rock->initFountain();
				}
			}
		}
		if(params.dMaxrockets)
			rocketTimer = myRandf(rocketTimeConst);
		else
			rocketTimer = 60.0f;  // arbitrary number since no rockets ever fire
	}

	// update particles
	numRockets = 0;
	std::list<particle>::iterator curpart = particles.begin();
	while(curpart != particles.end())
	{
		if(curpart->depth <= MINDEPTH)
			curpart = particles.end();
		else
		{
			curpart->update();
			if(curpart->type == ROCKET)
				numRockets++;
			curpart->findDepth();
			if(curpart->life <= 0.0f || curpart->xyz[1] < 0.0f)
			{
				if(curpart->type == ROCKET)  // become explosion
				{
					if(curpart->xyz[1] <= 0.0f)  // move above ground for explosion
					{
						curpart->xyz[1] = 0.1f;
						curpart->vel[1] *= -0.7f;
					}
					if(curpart->explosiontype == 18)
						curpart->initSpinner();
					else
						curpart->initExplosion();
				}
				else
				{
					if(curpart->type == POPPER)
					{
						switch(curpart->explosiontype)
						{
						case STAR:
							curpart->explosiontype = 100;
							curpart->initExplosion();
							break;
						case STREAMER:
							curpart->explosiontype = 101;
							curpart->initExplosion();
							break;
						case METEOR:
							curpart->explosiontype = 102;
							curpart->initExplosion();
							break;
						case POPPER:
							curpart->type = STAR;
							curpart->rgb.set(1.0f, 0.8f, 0.6f);
							curpart->t = curpart->tr = 0.2f;
						}
					}
					else
					{
						if(curpart->type == SUCKER || curpart->type == STRETCHER)  // become big explosion
						{
							if(curpart->type == SUCKER)
								curpart->initShockwave();
							else
								curpart->initBigmama();
						}
						else  // remove particles from list
							removeParticle(curpart);
					}
				}
			}
			curpart++;
		}
	}
	sortParticles();

	// the world
	drawWorld();

	// draw particles
	glEnable(GL_BLEND);
	std::list<particle>::iterator drawer = particles.begin();
	while(drawer != particles.end())
	{
		if(drawer->depth <= MINDEPTH)
			drawer = particles.end();
		else
		{
			drawer->draw();
			drawer ++;
		}
	}

	// draw lens flares
	if(params.dFlare)
	{
		makeFlareList();
		for(i=0; i<numFlares; i++)
		{
			if(allflares[i][0] == 0)
				flare(allflares[i][1], allflares[i][2], allflares[i][3], 
					allflares[i][4], allflares[i][5], allflares[i][6]);
			else
				superFlare(allflares[i][1], allflares[i][2], allflares[i][3], 
					allflares[i][4], allflares[i][5], allflares[i][6]);
		}
		numFlares = 0;
	}

	// do sound stuff
	if(params.dSound)
	{
		float listenerOri[6];
		listenerOri[0] = float(-modelMat[2]);
		listenerOri[1] = float(-modelMat[6]);
		listenerOri[2] = float(-modelMat[10]);
		listenerOri[3] = float(modelMat[1]);
		listenerOri[4] = float(modelMat[5]);
		listenerOri[5] = float(modelMat[9]);
		updateSound(cameraPos.v, cameraVel.v, listenerOri);
	}
}


void SkyrocketWidget::resizeGL( int w, int h )
{
	xsize = w;
	ysize = h;
	centerx = w / 2;
	centery = h / 2;
	glViewport(0, 0, w, h);
	glGetIntegerv(GL_VIEWPORT, viewport);
	aspectRatio = float(w) / float(h);

	//glMatrixMode(GL_MODELVIEW_MATRIX);

	// Set OpenGL state
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);
	glFrontFace(GL_CCW);
	glEnable(GL_CULL_FACE);

	// Initialize data structures
	initFlares();
	if(params.dSmoke)
		initSmoke();
	initWorld();
	initShockwave();
	if(params.dSound)
		initSound();
}


// Window initialization
void SkyrocketWidget::initializeGL()
{
	_timer->start( _frameTime, true );
}


void SkyrocketWidget::nextFrame()
{
	static float times[5] = { 0.03f, 0.03f, 0.03f, 0.03f, 0.03f };
	static int timeindex = 0;
	static int lastTime = -1;
	int thisTime;
	struct timeval now;
#ifdef BENCHMARK
	static int a = 1;
#endif

	/*
	 * update time 
	 */
	gettimeofday (&now, NULL);
	thisTime = (now.tv_sec & 65535) * 1000 + now.tv_usec / 1000;

	if (lastTime == -1)
		lastTime = thisTime;

	if (thisTime >= lastTime) {
		times[timeindex] = float (thisTime - lastTime) * 0.001f;
	} else {		// else use elapsedTime from last frame
		times[timeindex] = elapsedTime;
	}
	lastTime = thisTime;

#ifdef BENCHMARK
	times[timeindex] = 0.03f;
#endif

	// average last 5 frame times together
	elapsedTime = 0.2f * (times[0] + times[1] + times[2] + times[3] + times[4]);

	timeindex++;
	if (timeindex >= 5)
		timeindex = 0;
	
	updateGL();
	_timer->start( _frameTime, true );
}

//----------------------------------------------------------------------------


KSkyrocketSetup::KSkyrocketSetup( QWidget* parent, const char* name )
        : KDialogBase( parent, name, true, i18n("Setup Skyrocket Screen Saver"),
          Ok|Cancel|Help|Default|Apply, Ok, true )
{
	setButtonText( Help, i18n( "A&bout" ) );
	
	QWidget *main = makeMainWidget();
	
	QHBoxLayout* top    = new QHBoxLayout(main, 0, spacingHint());
	
	setupWidget = new SkyrocketSetup(main);
	slotDefault();
	top->addWidget(setupWidget);
	
	// Preview
	preview = new QWidget( main );
	preview->setFixedSize( 220, 170 );
	preview->setBackgroundColor( black );
	preview->show();    // otherwise saver does not get correct size
	_saver = new KSkyrocketScreenSaver( preview->winId() );
	top->addWidget(preview);
	
	// Now that we have _saver...
	setupWidget->maxrockets->setValue( _saver->params.dMaxrockets );
	setupWidget->smoke->setValue( _saver->params.dSmoke );
	setupWidget->explosionsmoke->setValue( _saver->params.dExplosionsmoke );
	setupWidget->ambient->setValue( _saver->params.dAmbient );
	setupWidget->flare->setValue( _saver->params.dFlare );
	setupWidget->sound->setValue( _saver->params.dSound );
	setupWidget->wind->setValue( _saver->params.dWind );
	setupWidget->stardensity->setValue( _saver->params.dStardensity );
	setupWidget->moonglow->setValue( _saver->params.dMoonglow );
	
	setupWidget->moon->setChecked( _saver->params.dMoon );
	setupWidget->earth->setChecked( _saver->params.dEarth );
	setupWidget->clouds->setChecked( _saver->params.dClouds );
	setupWidget->illumination->setChecked( _saver->params.dIllumination );
	
	setupWidget->soundLabel1->setEnabled(libopenal_is_present == 1);
	setupWidget->soundLabel2->setEnabled(libopenal_is_present == 1);
	setupWidget->soundLabel3->setEnabled(libopenal_is_present == 1);
	setupWidget->sound->setEnabled(libopenal_is_present == 1);
	if (libopenal_is_present == 1)
		setupWidget->soundDisabledLabel->hide();
	
	setMinimumSize( sizeHint() );
	
	timer = new QTimer(this);
	connect(timer, SIGNAL(timeout()), SLOT(updatePreview()));
	
	connect(setupWidget->maxrockets, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->smoke, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->explosionsmoke, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->ambient, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->flare, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->sound, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->wind, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->stardensity, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->moonglow, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	
	connect(setupWidget->moon, SIGNAL(stateChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->earth, SIGNAL(stateChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->clouds, SIGNAL(stateChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->illumination, SIGNAL(stateChanged(int)), SLOT(resetTimer()));
}


KSkyrocketSetup::~KSkyrocketSetup()
{
	delete _saver;
}


void KSkyrocketSetup::slotHelp()
{
	KMessageBox::about(this, 
		i18n("<h3>Skyrocket 1.0</h3>\n<p>Copyright (c) 2004 Terence M. Welsh<br>\n<a href=\"http://www.reallyslick.com/\">http://www.reallyslick.com/</a></p>\n\n<p>Ported to KDE by David Sansome</p>"),
		QString::null, KMessageBox::AllowLink);
}


void KSkyrocketSetup::slotOk()
{
	saveSettings();
	accept();
}

void KSkyrocketSetup::slotApply()
{
	saveSettings();
	updatePreview();
}

void KSkyrocketSetup::saveSettings()
{
	KConfig* config = KGlobal::config();
	config->setGroup("Settings");
	
	config->writeEntry("Maxrockets", setupWidget->maxrockets->value() );
	config->writeEntry("Smoke", setupWidget->smoke->value() );
	config->writeEntry("Explosionsmoke", setupWidget->explosionsmoke->value() );
	config->writeEntry("Wind", setupWidget->wind->value() );
	config->writeEntry("Ambient", setupWidget->ambient->value() );
	config->writeEntry("Stardensity", setupWidget->stardensity->value() );
	config->writeEntry("Flare", setupWidget->flare->value() );
	config->writeEntry("Moonglow", setupWidget->moonglow->value() );
	config->writeEntry("Sound", setupWidget->sound->value() );
	
	config->writeEntry("Moon", setupWidget->moon->isChecked() );
	config->writeEntry("Clouds", setupWidget->clouds->isChecked() );
	config->writeEntry("Illumination", setupWidget->illumination->isChecked() );
	config->writeEntry("Earth", setupWidget->earth->isChecked() );
	
	config->sync();
}


void KSkyrocketSetup::updatePreview()
{
	delete _saver;
	
	SkyrocketParams p;
	p.dMaxrockets = setupWidget->maxrockets->value();
	p.dSmoke = setupWidget->smoke->value();
	p.dExplosionsmoke = setupWidget->explosionsmoke->value();
	p.dWind = setupWidget->wind->value();
	p.dAmbient = setupWidget->ambient->value();
	p.dStardensity = setupWidget->stardensity->value();
	p.dFlare = setupWidget->flare->value();
	p.dMoonglow = setupWidget->moonglow->value();
	p.dSound = setupWidget->sound->value();
	
	p.dMoon = setupWidget->moon->isChecked();
	p.dClouds = setupWidget->clouds->isChecked();
	p.dIllumination = setupWidget->illumination->isChecked();
	p.dEarth = setupWidget->earth->isChecked();
	
	_saver = new KSkyrocketScreenSaver( preview->winId(), p );
}

void KSkyrocketSetup::resetTimer()
{
	timer->start(750, true);
}

void KSkyrocketSetup::slotDefault()
{
	setupWidget->maxrockets->setValue( 8 );
	setupWidget->smoke->setValue( 5 );
	setupWidget->explosionsmoke->setValue( 0 );
	setupWidget->wind->setValue( 20 );
	setupWidget->ambient->setValue( 10 );
	setupWidget->stardensity->setValue( 20 );
	setupWidget->flare->setValue( 20 );
	setupWidget->moonglow->setValue( 20 );
	setupWidget->sound->setValue( 100 );
	
	setupWidget->moon->setChecked( true );
	setupWidget->clouds->setChecked( true );
	setupWidget->illumination->setChecked( true );
	setupWidget->earth->setChecked( true );
}

#include "skyrocket.moc"
