/***************************************************************************
 *   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 <sys/time.h> 
#include <qtimer.h>
#include <kconfig.h>
#include <qspinbox.h>
#include <qlayout.h> 
#include <math.h>
#include <qlabel.h> 
#include "helios.h"
#include "helios_data.h"


extern "C"
{
	const char *kss_applicationName = "helios.kss";
	const char *kss_description = I18N_NOOP( "Helios" );
	const char *kss_version = "0.1";
	
	KHeliosScreenSaver *kss_create( WId id )
	{
		return new KHeliosScreenSaver( id );
	}
	
	QDialog *kss_setup()
	{
		return new KHeliosSetup();
	}
}


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

KHeliosScreenSaver::KHeliosScreenSaver( WId id, HeliosParams p )
 : KScreenSaver( id ), params(p)
{
	_effect = new HeliosWidget( NULL, 0, params);
	embed( _effect );
	_effect->show();
}



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


void KHeliosScreenSaver::readSettings()
{
	KConfig* config = KGlobal::config();
	config->setGroup("Settings");
	
	params.dIons = config->readNumEntry( "Ions", 1500 );
	params.dSize = config->readNumEntry( "Size", 10 );
	params.dEmitters = config->readNumEntry( "Emitters", 3 );
	params.dAttracters = config->readNumEntry( "Attracters", 3 );
	params.dSpeed = config->readNumEntry( "Speed", 10 );
	params.dCameraspeed = config->readNumEntry( "Cameraspeed", 10 );
	params.dBlur = config->readNumEntry( "Blur", 10 );
	
	params.dSurface = config->readBoolEntry( "Surface", true );
	params.dWireframe = config->readBoolEntry( "Wireframe", false );
}


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


emitter::emitter()
{
	pos = rsVec(myRandf(1000.0f) - 500.0f, myRandf(1000.0f) - 500.0f, myRandf(1000.0f) - 500.0f);
}

attracter::attracter()
{
	pos = rsVec(myRandf(1000.0f) - 500.0f, myRandf(1000.0f) - 500.0f, myRandf(1000.0f) - 500.0f);
}

ion::ion()
{
	float temp;

	pos = rsVec(0.0f, 0.0f, 0.0f);
	rgb = rsVec(0.0f, 0.0f, 0.0f);
	temp = myRandf(2.0f) + 0.4f;
	size = float(currentWidget->params.dSize) * temp;
	speed = float(currentWidget->params.dSpeed) * 12.0f / temp;
}

void ion::start()
{
	int i = myRandi(currentWidget->params.dEmitters);
	pos = currentWidget->elist[i].pos;
	float offset = currentWidget->elapsedTime * speed;
	switch(myRandi(14))
	{
	case 0:
		pos[0] += offset;
		break;
	case 1:
		pos[0] -= offset;
		break;
	case 2:
		pos[1] += offset;
		break;
	case 3:
		pos[1] -= offset;
		break;
	case 4:
		pos[2] += offset;
		break;
	case 5:
		pos[2] -= offset;
		break;
	case 6:
		pos[0] += offset;
		pos[1] += offset;
		pos[2] += offset;
		break;
	case 7:
		pos[0] -= offset;
		pos[1] += offset;
		pos[2] += offset;
		break;
	case 8:
		pos[0] += offset;
		pos[1] -= offset;
		pos[2] += offset;
		break;
	case 9:
		pos[0] -= offset;
		pos[1] -= offset;
		pos[2] += offset;
		break;
	case 10:
		pos[0] += offset;
		pos[1] += offset;
		pos[2] -= offset;
		break;
	case 11:
		pos[0] -= offset;
		pos[1] += offset;
		pos[2] -= offset;
		break;
	case 12:
		pos[0] += offset;
		pos[1] -= offset;
		pos[2] -= offset;
		break;
	case 13:
		pos[0] -= offset;
		pos[1] -= offset;
		pos[2] -= offset;
	}

	rgb = currentWidget->newRgb;
}


void ion::update()
{
	int i;
	int startOver = 0;
	static float startOverDistance;
	static rsVec force, tempvec;
	static float length, temp;

	force = rsVec(0.0f, 0.0f, 0.0f);
	for(i=0; i<currentWidget->params.dEmitters; i++)
	{
		tempvec = pos - currentWidget->elist[i].pos;
		length = tempvec.normalize();
		if(length > 11000.0f)
			startOver = 1;
		if(length <= 1.0f)
			temp = 1.0f;
		else
			temp = 1.0f / length;
		tempvec *= temp;
		force += tempvec;
	}
	startOverDistance = speed * currentWidget->elapsedTime;
	for(i=0; i<currentWidget->params.dAttracters; i++)
	{
		tempvec = currentWidget->alist[i].pos - pos;
		length = tempvec.normalize();
		if(length < startOverDistance)
			startOver = 1;
		if(length <= 1.0f)
			temp = 1.0f;
		else
			temp = 1.0f / length;
		tempvec *= temp;
		force += tempvec;
	}

	// Start this ion at an emitter if it gets too close to an attracter
	// or too far from an emitter
	if(startOver)
		start();
	else
	{
		force.normalize();
		pos += (force * currentWidget->elapsedTime * speed);
	}
}

void ion::draw()
{
	glColor3f(rgb[0], rgb[1], rgb[2]);
	glPushMatrix();
		glTranslatef(pos[0] * currentWidget->billboardMat[0] + pos[1] * currentWidget->billboardMat[4] + pos[2] * currentWidget->billboardMat[8],
			pos[0] * currentWidget->billboardMat[1] + pos[1] * currentWidget->billboardMat[5] + pos[2] * currentWidget->billboardMat[9],
			pos[0] * currentWidget->billboardMat[2] + pos[1] * currentWidget->billboardMat[6] + pos[2] * currentWidget->billboardMat[10]);
		glScalef(size, size, size);
		glCallList(1);
	glPopMatrix();
}


HeliosWidget::HeliosWidget( QWidget* parent, const char* name, HeliosParams p )
 : QGLWidget(parent, name), elapsedTime(0.0f), pfd_swap_exchange(false), pfd_swap_copy(false), ionsReleased(0), releaseTime(0.0f), params(p)
{
	loadHeliosData();
	currentWidget = this;
	oldCameraDistance = 0.0f;
	cameraDistance = 0.0f;
	targetCameraDistance = -1000.0f;
	preCameraInterp = PI;
	thisTime = 0;
	lastTime = 0;
	
	radialVel = rsVec(0.0f, 0.0f, 0.0f);
	targetRadialVel = radialVel;
	rotQuat = rsQuat(0.0f, 0.0f, 0.0f, 1.0f);
	newHsl = rsVec(myRandf(1.0f), 1.0f, 1.0f);
	colorInterp = 1.0f;
	colorChange = 0.0f;
	wait = 0.0f;
	preinterp = PI;
	interp = 0.0f;
	interpconst = 0.001f;
	newTarget = 0;
	lastTarget = 0;
	valuetrig = 0.0f;
	
	struct timeval now;
	gettimeofday (&now, NULL);
	srand((now.tv_sec & 65535) * 1000 + now.tv_usec / 1000);
	
	_timer = new QTimer( this );
	_frameTime = 1000 / 60;
	connect( _timer, SIGNAL(timeout()), this, SLOT(nextFrame()) );
}


HeliosWidget::~HeliosWidget()
{
	delete[] elist;
	delete[] alist;
	delete[] ilist;
	if(params.dSurface)
	{
		delete[] spheres;
		delete surface;
		delete volume;
	}
}


void HeliosWidget::setTargets(int whichTarget)
{
	int i;

	switch(whichTarget)
	{
	case 0:  // random
		for(i=0; i<params.dEmitters; i++)
			elist[i].settargetpos(rsVec(rsVec(myRandf(1000.0f) - 500.0f, myRandf(1000.0f) - 500.0f, myRandf(1000.0f) - 500.0f)));
		for(i=0; i<params.dAttracters; i++)
			alist[i].settargetpos(rsVec(rsVec(myRandf(1000.0f) - 500.0f, myRandf(1000.0f) - 500.0f, myRandf(1000.0f) - 500.0f)));
		break;
	case 1:  // line (all emitters on one side, all attracters on the other)
	{
		float position = -500.0f, change = 1000.0f / float(params.dEmitters + params.dAttracters - 1);
		for(i=0; i<params.dEmitters; i++)
		{
			elist[i].settargetpos(rsVec(rsVec(position, position * 0.5f, 0.0f)));
			position += change;
		}
		for(i=0; i<params.dAttracters; i++)
		{
			alist[i].settargetpos(rsVec(rsVec(position, position * 0.5f, 0.0f)));
			position += change;
		}
		break;
	}
	case 2:  // line (emitters and attracters staggered)
	{
		float change;
		if(params.dEmitters > params.dAttracters)
			change = 1000.0f / float(params.dEmitters * 2 - 1);
		else
			change = 1000.0f / float(params.dAttracters * 2 - 1);
		float position = -500.0f;
		for(i=0; i<params.dEmitters; i++)
		{
			elist[i].settargetpos(rsVec(rsVec(position, position * 0.5f, 0.0f)));
			position += change * 2.0f;
		}
		position = -500.0f + change;
		for(i=0; i<params.dAttracters; i++)
		{
			alist[i].settargetpos(rsVec(rsVec(position, position * 0.5f, 0.0f)));
			position += change * 2.0f;
		}
		break;
	}
	case 3:  // 2 lines (parallel)
	{
		float change = 1000.0f / float(params.dEmitters * 2 - 1);
		float position = -500.0f;
		float height = -525.0f + float(params.dEmitters * 25);
		for(i=0; i<params.dEmitters; i++)
		{
			elist[i].settargetpos(rsVec(rsVec(position, height, -50.0f)));
			position += change * 2.0f;
		}
		change = 1000.0f / float(params.dAttracters * 2 - 1);
		position = -500.0f;
		height = 525.0f - float(params.dAttracters * 25);
		for(i=0; i<params.dAttracters; i++)
		{
			alist[i].settargetpos(rsVec(rsVec(position, height, 50.0f)));
			position += change * 2.0f;
		}
		break;
	}
	case 4:  // 2 lines (skewed)
	{
		float change = 1000.0f / float(params.dEmitters * 2 - 1);
		float position = -500.0f;
		float height = -525.0f + float(params.dEmitters * 25);
		for(i=0; i<params.dEmitters; i++)
		{
			elist[i].settargetpos(rsVec(rsVec(position, height, 0.0f)));
			position += change * 2.0f;
		}
		change = 1000.0f / float(params.dAttracters * 2 - 1);
		position = -500.0f;
		height = 525.0f - float(params.dAttracters * 25);
		for(i=0; i<params.dAttracters; i++)
		{
			alist[i].settargetpos(rsVec(rsVec(10.0f, height, position)));
			position += change * 2.0f;
		}
		break;
	}
	case 5:  // random distribution across a plane
		for(i=0; i<params.dEmitters; i++)
			elist[i].settargetpos(rsVec(rsVec(myRandf(1000.0f) - 500.0f, 0.0f, myRandf(1000.0f) - 500.0f)));
		for(i=0; i<params.dAttracters; i++)
			alist[i].settargetpos(rsVec(rsVec(myRandf(1000.0f) - 500.0f, 0.0f, myRandf(1000.0f) - 500.0f)));
		break;
	case 6:  // random distribution across 2 planes
	{
		float height = -525.0f + float(params.dEmitters * 25);
		for(i=0; i<params.dEmitters; i++)
			elist[i].settargetpos(rsVec(rsVec(myRandf(1000.0f) - 500.0f, height, myRandf(1000.0f) - 500.0f)));
		height = 525.0f - float(params.dAttracters * 25);
		for(i=0; i<params.dAttracters; i++)
			alist[i].settargetpos(rsVec(rsVec(myRandf(1000.0f) - 500.0f, height, myRandf(1000.0f) - 500.0f)));
		break;
	}
	case 7:  // 2 rings (1 inside and 1 outside)
	{
		float angle = 0.5f, cosangle, sinangle;
		float change = PIx2 / float(params.dEmitters);
		for(i=0; i<params.dEmitters; i++)
		{
			angle += change;
			cosangle = cosf(angle) * 200.0f;
			sinangle = sinf(angle) * 200.0f;
			elist[i].settargetpos(rsVec(rsVec(cosangle, sinangle, 0.0f)));
		}
		angle = 1.5f;
		change = PIx2 / float(params.dAttracters);
		for(i=0; i<params.dAttracters; i++)
		{
			angle += change;
			cosangle = cosf(angle) * 500.0f;
			sinangle = sinf(angle) * 500.0f;
			alist[i].settargetpos(rsVec(rsVec(cosangle, sinangle, 0.0f)));
		}
		break;
	}
	case 8:  // ring (all emitters on one side, all attracters on the other)
	{
		float angle = 0.5f, cosangle, sinangle;
		float change = PIx2 / float(params.dEmitters + params.dAttracters);
		for(i=0; i<params.dEmitters; i++)
		{
			angle += change;
			cosangle = cosf(angle) * 500.0f;
			sinangle = sinf(angle) * 500.0f;
			elist[i].settargetpos(rsVec(rsVec(cosangle, sinangle, 0.0f)));
		}
		for(i=0; i<params.dAttracters; i++)
		{
			angle += change;
			cosangle = cosf(angle) * 500.0f;
			sinangle = sinf(angle) * 500.0f;
			alist[i].settargetpos(rsVec(rsVec(cosangle, sinangle, 0.0f)));
		}
		break;
	}
	case 9:  // ring (emitters and attracters staggered)
	{
		float change;
		if(params.dEmitters > params.dAttracters)
			change = PIx2 / float(params.dEmitters * 2);
		else
			change = PIx2 / float(params.dAttracters * 2);
		float angle = 0.5f, cosangle, sinangle;
		for(i=0; i<params.dEmitters; i++)
		{
			cosangle = cosf(angle) * 500.0f;
			sinangle = sinf(angle) * 500.0f;
			elist[i].settargetpos(rsVec(rsVec(cosangle, sinangle, 0.0f)));
			angle += change * 2.0f;
		}
		angle = 0.5f + change;
		for(i=0; i<params.dAttracters; i++)
		{
			cosangle = cosf(angle) * 500.0f;
			sinangle = sinf(angle) * 500.0f;
			alist[i].settargetpos(rsVec(rsVec(cosangle, sinangle, 0.0f)));
			angle += change * 2.0f;
		}
		break;
	}
	case 10:  // 2 points
		for(i=0; i<params.dEmitters; i++)
			elist[i].settargetpos(rsVec(rsVec(500.0f, 100.0f, 50.0f)));
		for(i=0; i<params.dAttracters; i++)
			alist[i].settargetpos(rsVec(rsVec(-500.0f, -100.0f, -50.0f)));
		break;
	}
}


float surfaceFunction(float* position)
{
	float value = 0.0f;
	int points = currentWidget->params.dEmitters + currentWidget->params.dAttracters;

	for(int i=0; i<points; i++)
		value += currentWidget->spheres[i].value(position);

	return(value);
}


void HeliosWidget::paintGL()
{
	int i;

	// Camera movements
	// first do translation (distance from center)
	
	float cameraInterp;
	preCameraInterp += float(params.dCameraspeed) * elapsedTime * 0.01f;
	cameraInterp = 0.5f - (0.5f * cosf(preCameraInterp));
	cameraDistance = (1.0f - cameraInterp) * oldCameraDistance
		+ cameraInterp * targetCameraDistance;
	if(preCameraInterp >= PI)
	{
		oldCameraDistance = targetCameraDistance;
		targetCameraDistance = -myRandf(1300.0f) - 200.0f;
		preCameraInterp = 0.0f;
	}
	glLoadIdentity();
	glTranslatef(0.0, 0.0, cameraDistance);

	// then do rotation
	rsVec radialVelDiff = targetRadialVel - radialVel;
	float changeRemaining = radialVelDiff.normalize();
	float change = float(params.dCameraspeed) * 0.0002f * elapsedTime;
	if(changeRemaining > change)
	{
		radialVelDiff *= change;
		radialVel += radialVelDiff;
	}
	else
	{
		radialVel = targetRadialVel;
		if(myRandi(2))
		{
			targetRadialVel = rsVec(myRandf(1.0f), myRandf(1.0f), myRandf(1.0f));
			targetRadialVel.normalize();
			targetRadialVel *= float(params.dCameraspeed) * myRandf(0.002f);
		}
		else
			targetRadialVel = rsVec(0.0f, 0.0f, 0.0f);
	}
	rsVec tempRadialVel = radialVel;
	float angle = tempRadialVel.normalize();
	rsQuat radialQuat;
	radialQuat.make(angle, tempRadialVel[0], tempRadialVel[1], tempRadialVel[2]);
	rotQuat.preMult(radialQuat);
	rsMatrix rotMat;
	rotMat.fromQuat(rotQuat);

	// make billboard matrix for rotating particles when they are drawn
	rotMat.get(billboardMat);

	// Calculate new color
	colorInterp += elapsedTime * colorChange;
	if(colorInterp >= 1.0f)
	{
		if(!myRandi(3) && params.dIons >= 100)  // change color suddenly
			newHsl = rsVec(myRandf(1.0f), 1.0f - (myRandf(1.0f) * myRandf(1.0f)), 1.0f);
		oldHsl = newHsl;
		targetHsl = rsVec(myRandf(1.0f), 1.0f - (myRandf(1.0f) * myRandf(1.0f)), 1.0f);
		colorInterp = 0.0f;
		// amount by which to change colorInterp each second
		colorChange = myRandf(0.005f * float(params.dSpeed)) + (0.002f * float(params.dSpeed));
	}
	else
	{
		float diff = targetHsl[0] - oldHsl[0];
		if(diff < -0.5f || (diff > 0.0f && diff < 0.5f))
			newHsl[0] = oldHsl[0] + colorInterp * diff;
		else
			newHsl[0] = oldHsl[0] - colorInterp * diff;
		diff = targetHsl[1] - oldHsl[1];
			newHsl[1] = oldHsl[1] + colorInterp * diff;
		if(newHsl[0] < 0.0f)
			newHsl[0] += 1.0f;
		if(newHsl[0] > 1.0f)
			newHsl[0] -= 1.0f;
		hsl2rgb(newHsl[0], newHsl[1], 1.0f, newRgb[0], newRgb[1], newRgb[2]);
	}

	// Release ions
	if(ionsReleased < params.dIons)
	{
		releaseTime -= elapsedTime;
		while(ionsReleased < params.dIons && releaseTime <= 0.0f){
			ilist[ionsReleased].start();
			ionsReleased ++;
			// all ions released after 2 minutes
			releaseTime += 120.0f / float(params.dIons);
		}
	}

	// Set interpolation value for emitters and attracters
	wait -= elapsedTime;
	if(wait <= 0.0f)
	{
		preinterp += elapsedTime * float(params.dSpeed) * interpconst;
		interp = 0.5f - (0.5f * cosf(preinterp));
	}
	if(preinterp >= PI)
	{
		// select new taget points (not the same pattern twice in a row)
		lastTarget = newTarget;
		newTarget = myRandi(10);
		if(newTarget == lastTarget)
			newTarget ++;
		setTargets(newTarget);
		preinterp = 0.0f;
		interp = 0.0f;
		wait = 10.0f;  // pause after forming each new pattern
		interpconst = 0.001f;
		if(!myRandi(4))  // interpolate really fast sometimes
			interpconst = 0.1f;
	}


	// Update particles
	for(i=0; i<params.dEmitters; i++)
	{
		elist[i].interppos(interp);
		elist[i].update();
	}
	for(i=0; i<params.dAttracters; i++)
	{
		alist[i].interppos(interp);
		alist[i].update();
	}
	for(i=0; i<ionsReleased; i++)
		ilist[i].update();

	// Calculate surface
	if(params.dSurface)
	{
		for(i=0; i<params.dEmitters; i++)
			spheres[i].setPosition(elist[i].pos[0], elist[i].pos[1], elist[i].pos[2]);
		for(i=0; i<params.dAttracters; i++)
			spheres[params.dEmitters+i].setPosition(alist[i].pos[0], alist[i].pos[1], alist[i].pos[2]);
		std::list<impCrawlPoint> crawlpointlist;
		float center[3];
		for(i=0; i<params.dEmitters+params.dAttracters; i++)
		{
			spheres[i].center(center);
			crawlpointlist.push_back(impCrawlPoint(center));
		}
		surface->reset();
		valuetrig += elapsedTime;
		volume->surfacevalue = 0.45f + 0.05f * cosf(valuetrig);
		volume->make_surface(crawlpointlist);
	}

	// Draw
	// clear the screen
	if(params.dBlur)  // partially
	{
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glColor4f(0.0f, 0.0f, 0.0f, 0.5f - (float(sqrt(sqrt(double(params.dBlur)))) * 0.15495f));
		glPushMatrix();
		glLoadIdentity();
		glBegin(GL_TRIANGLE_STRIP);
			glVertex3f(-5.0f, -4.0f, -3.0f);
			glVertex3f(5.0f, -4.0f, -3.0f);
			glVertex3f(-5.0f, 4.0f, -3.0f);
			glVertex3f(5.0f, 4.0f, -3.0f);
		glEnd();
		glPopMatrix();
	}
	else  // completely
		glClear(GL_COLOR_BUFFER_BIT);

	// Draw ions
	glBlendFunc(GL_ONE, GL_ONE);
	glBindTexture(GL_TEXTURE_2D, 1);
	for(i=0; i<ionsReleased; i++)
		ilist[i].draw();

	// Draw surfaces
	float brightFactor = 0.0f;
	float surfaceColor[3] = {0.0f, 0.0f, 0.0f};
	if(params.dSurface)
	{
		glBindTexture(GL_TEXTURE_2D, 2);
		glEnable(GL_TEXTURE_GEN_S);
		glEnable(GL_TEXTURE_GEN_T);
		// find color for surfaces
		if(params.dIons >= 100)
		{
			if(params.dWireframe)
				brightFactor = 2.0f / (float(params.dBlur + 30) * float(params.dBlur + 30));
			else
				brightFactor = 4.0f / (float(params.dBlur + 30) * float(params.dBlur + 30));
			for(i=0; i<100; i++){
				surfaceColor[0] += ilist[i].rgb[0] * brightFactor;
				surfaceColor[1] += ilist[i].rgb[1] * brightFactor;
				surfaceColor[2] += ilist[i].rgb[2] * brightFactor;
			}
			glColor3fv(surfaceColor);
		}
		else
		{
			if(params.dWireframe)
				brightFactor = 200.0f / (float(params.dBlur + 30) * float(params.dBlur + 30));
			else
				brightFactor = 400.0f / (float(params.dBlur + 30) * float(params.dBlur + 30));
			glColor3f(newRgb[0] * brightFactor, newRgb[1] * brightFactor, newRgb[2] * brightFactor);
		}
		// draw the surface
		glPushMatrix();
			glMultMatrixf(billboardMat);
			if(params.dWireframe)
			{
				glDisable(GL_TEXTURE_2D);
				surface->draw_wireframe();
				glEnable(GL_TEXTURE_2D);
			}
			else
				surface->draw();
		glPopMatrix();
		glDisable(GL_TEXTURE_GEN_S);
		glDisable(GL_TEXTURE_GEN_T);
	}

	// If graphics card does a true buffer swap instead of a copy swap
	// then everything must get drawn on both buffers
	if(params.dBlur && pfd_swap_exchange)
	{
		swapBuffers();
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glColor4f(0.0f, 0.0f, 0.0f, 0.5f - (float(sqrt(sqrt(double(params.dBlur)))) * 0.15495f));
		glPushMatrix();
		glLoadIdentity();
		glBegin(GL_TRIANGLE_STRIP);
			glVertex3f(-5.0f, -4.0f, -3.0f);
			glVertex3f(5.0f, -4.0f, -3.0f);
			glVertex3f(-5.0f, 4.0f, -3.0f);
			glVertex3f(5.0f, 4.0f, -3.0f);
		glEnd();
		glPopMatrix();

		// Draw ions
		glBlendFunc(GL_ONE, GL_ONE);
		glBindTexture(GL_TEXTURE_2D, 1);
		for(i=0; i<ionsReleased; i++)
			ilist[i].draw();

		// Draw surfaces
		if(params.dSurface)
		{
			glBindTexture(GL_TEXTURE_2D, 2);
			glEnable(GL_TEXTURE_GEN_S);
			glEnable(GL_TEXTURE_GEN_T);
			if(params.dIons >= 100)
				glColor3fv(surfaceColor);
			else
				glColor3f(newRgb[0] * brightFactor, newRgb[1] * brightFactor, newRgb[2] * brightFactor);
			glPushMatrix();
				glMultMatrixf(billboardMat);
				if(params.dWireframe)
				{
					glDisable(GL_TEXTURE_2D);
					surface->draw_wireframe();
					glEnable(GL_TEXTURE_2D);
				}
				else
					surface->draw();
			glPopMatrix();
			glDisable(GL_TEXTURE_GEN_S);
			glDisable(GL_TEXTURE_GEN_T);
		}
	}
}


void HeliosWidget::resizeGL( int w, int h )
{
	glViewport(0, 0, w, h);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0, float(w) / float(h), 0.1, 10000.0f);
	glMatrixMode(GL_MODELVIEW);

	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
}


// Window initialization
void HeliosWidget::initializeGL()
{
	resizeGL(width(), height());
	
	int i, j;
	float x, y, temp;
	// Clear the buffers and test for type of buffer swapping
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	swapBuffers();
	glClear(GL_COLOR_BUFFER_BIT);
	unsigned char pixel[1] = {255};
	glRasterPos2i(0, 0);
	glDrawPixels(1, 1, GL_RED, GL_UNSIGNED_BYTE, pixel);
	swapBuffers();
	glReadPixels(0, 0, 1, 1, GL_RED, GL_UNSIGNED_BYTE, pixel);
	if(pixel[0] == 0)  // Color was swapped out of the back buffer
	{
		pfd_swap_exchange = 1;
		pfd_swap_copy = 0;
	}
	else    // Color remains in back buffer
	{
		pfd_swap_exchange = 0;
		pfd_swap_copy = 1;
	}

	// Init light texture
	for(i=0; i<LIGHTSIZE; i++)
	{
		for(j=0; j<LIGHTSIZE; j++)
		{
			x = float(i - LIGHTSIZE / 2) / float(LIGHTSIZE / 2);
			y = float(j - LIGHTSIZE / 2) / float(LIGHTSIZE / 2);
			temp = 1.0f - float(sqrt((x * x) + (y * y)));
			if(temp > 1.0f)
				temp = 1.0f;
			if(temp < 0.0f)
				temp = 0.0f;
			lightTexture[i][j] = (unsigned char)(255.0f * temp * temp);
		}
	}
	glBindTexture(GL_TEXTURE_2D, 1);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	gluBuild2DMipmaps(GL_TEXTURE_2D, 1, LIGHTSIZE, LIGHTSIZE, GL_LUMINANCE, 
		GL_UNSIGNED_BYTE, lightTexture);
	glBindTexture(GL_TEXTURE_2D, 2);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TEXSIZE, TEXSIZE, GL_RGB, 
		GL_UNSIGNED_BYTE, spheremap);
	glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
	glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
	glEnable(GL_TEXTURE_2D);

	// Initialize light display list
	glNewList(1, GL_COMPILE);
		glBindTexture(GL_TEXTURE_2D, 1);
		glBegin(GL_TRIANGLES);
			glTexCoord2f(0.0f, 0.0f);
			glVertex3f(-0.5f, -0.5f, 0.0f);
			glTexCoord2f(1.0f, 0.0f);
			glVertex3f(0.5f, -0.5f, 0.0f);
			glTexCoord2f(1.0f, 1.0f);
			glVertex3f(0.5f, 0.5f, 0.0f);
			glTexCoord2f(0.0f, 0.0f);
			glVertex3f(-0.5f, -0.5f, 0.0f);
			glTexCoord2f(1.0f, 1.0f);
			glVertex3f(0.5f, 0.5f, 0.0f);
			glTexCoord2f(0.0f, 1.0f);
			glVertex3f(-0.5f, 0.5f, 0.0f);
		glEnd();
	glEndList();

	// Initialize particles
	elist = new emitter[params.dEmitters];
	alist = new attracter[params.dAttracters];
	ilist = new ion[params.dIons];

	// Initialize surface
	if(params.dSurface)
	{
		volume = new impCubeVolume;
		volume->init(50, 50, 50, 35.0f);
		volume->function = surfaceFunction;
		surface = new impSurface;
		surface->init(6000);
		volume->surface = surface;
		spheres = new impSphere[params.dEmitters + params.dAttracters];
		float sphereScaleFactor = 1.0f / float(sqrt(double(2 * params.dEmitters + params.dAttracters)));
		for(i=0; i<params.dEmitters; i++)
			spheres[i].setScale(400.0f * sphereScaleFactor);
		for(i=0; i<params.dAttracters; i++)
			spheres[i + params.dEmitters].setScale(200.0f * sphereScaleFactor);
	}
	
	_timer->start( _frameTime, true );
}


void HeliosWidget::nextFrame()
{
	struct timeval now;

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

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

	if (thisTime >= lastTime)
		elapsedTime = (thisTime - lastTime) * 0.001f;
	lastTime = thisTime;
	
	updateGL();
	_timer->start( _frameTime, true );
}

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


KHeliosSetup::KHeliosSetup( QWidget* parent, const char* name )
        : KDialogBase( parent, name, true, i18n("Setup Helios 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 HeliosSetup(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 KHeliosScreenSaver( preview->winId() );
	top->addWidget(preview);
	
	// Now that we have _saver...
	setupWidget->ions->setValue( _saver->params.dIons );
	setupWidget->size->setValue( _saver->params.dSize );
	setupWidget->emitters->setValue( _saver->params.dEmitters );
	setupWidget->attracters->setValue( _saver->params.dAttracters );
	setupWidget->speed->setValue( _saver->params.dSpeed );
	setupWidget->cameraspeed->setValue( _saver->params.dCameraspeed );
	setupWidget->blur->setValue( _saver->params.dBlur );
	
	setupWidget->surface->setChecked( _saver->params.dSurface );
	setupWidget->wireframe->setChecked( _saver->params.dWireframe );
	
	setMinimumSize( sizeHint() );
	
	timer = new QTimer(this);
	connect(timer, SIGNAL(timeout()), SLOT(updatePreview()));
	
	connect(setupWidget->ions, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->size, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->emitters, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->attracters, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->speed, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->cameraspeed, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->blur, SIGNAL(valueChanged(int)), SLOT(resetTimer()));
	
	connect(setupWidget->surface, SIGNAL(stateChanged(int)), SLOT(resetTimer()));
	connect(setupWidget->wireframe, SIGNAL(stateChanged(int)), SLOT(resetTimer()));
}


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


void KHeliosSetup::slotHelp()
{
	KMessageBox::about(this, 
		i18n("<h3>Helios 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 KHeliosSetup::slotOk()
{
	saveSettings();
	accept();
}

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

void KHeliosSetup::saveSettings()
{
	KConfig* config = KGlobal::config();
	config->setGroup("Settings");
	
	config->writeEntry("Ions", setupWidget->ions->value() );
	config->writeEntry("Size", setupWidget->size->value() );
	config->writeEntry("Emitters", setupWidget->emitters->value() );
	config->writeEntry("Attracters", setupWidget->attracters->value() );
	config->writeEntry("Speed", setupWidget->speed->value() );
	config->writeEntry("Cameraspeed", setupWidget->cameraspeed->value() );
	config->writeEntry("Blur", setupWidget->blur->value() );
	
	config->writeEntry("Surface", setupWidget->surface->isChecked() );
	config->writeEntry("Wireframe", setupWidget->wireframe->isChecked() );
	
	config->sync();
}


void KHeliosSetup::updatePreview()
{
	delete _saver;
	
	HeliosParams p;
	p.dIons = setupWidget->ions->value();
	p.dSize = setupWidget->size->value();
	p.dEmitters = setupWidget->emitters->value();
	p.dAttracters = setupWidget->attracters->value();
	p.dSpeed = setupWidget->speed->value();
	p.dCameraspeed = setupWidget->cameraspeed->value();
	p.dBlur = setupWidget->blur->value();
	
	p.dSurface = setupWidget->surface->isChecked();
	p.dWireframe = setupWidget->wireframe->isChecked();
	
	_saver = new KHeliosScreenSaver( preview->winId(), p);
}

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

void KHeliosSetup::slotDefault()
{
	setupWidget->ions->setValue( 1500 );
	setupWidget->size->setValue( 10 );
	setupWidget->emitters->setValue( 3 );
	setupWidget->attracters->setValue( 3 );
	setupWidget->speed->setValue( 10 );
	setupWidget->cameraspeed->setValue( 30 );
	setupWidget->blur->setValue( 10 );
	
	setupWidget->surface->setChecked( true );
	setupWidget->wireframe->setChecked( false );
}

#include "helios.moc"
