/*
	VERSION:	2.2
	2.2			Detect when the assigned emitter-moviClip ceases to exist
					unload() added
					uses EventDispatcher class
					"onComplete" event
	2.1			Bakes-in & automatically sets-up:  _global.AVOID_LOOP_PANIC  (avoidLoopPanic.as)
	2.0			The "image" can be raw BitmapData,  instead of just a movieClip linkage.
	1.9			The contents of "customData" are populated directly into each new particle
	
	USAGE:
		emitter_mc.emitter_obj = new ParticleSystem( emitter_mc, {image:"test_particle"} );
		
		emitter_mc.emitter_obj = new ParticleSystem( emitter_mc, {image:myBitmap_pic} );
	
	USAGE:  (to stop particles)
		emitter_mc.emitter_obj.unload();
		delete emitter_mc.emitter_obj;
	
	FUNCTIONS:
		deleteAllParticles();			Deletes all the particle movieClips created by this emitter.  (useful before removing the emitter itself)
	
	NOTE:
		Particles are created OUTSIDE of the emitter.
		Data IS added to the particles themselves.
		particleData			A generic object containing arbitrary data for the particle movieClip to use.  (This allows passing data to advanced effects within dedicated particles)
		particle_vec			This vector represents the direction & velocity of the particle.  (as a Point object)
		particleLife				This is the particle's remaining life  (in frames)
		particleFullLife			This is the particle's total life-span  (in frames)
		particleFullScale		This is the particle's normal scale (as a percent), when it's not shrinking or growing
	
	NOTE:
		Particle depths are random  (0 - 9999), 
		but won't replace existing objects.
	
	NOTE:
		This system assumes you're running at 30 FPS.
		To change it, do this:
		clearInterval( particleSystem.loopInterval );
		particleSystem.loopInterval = setInterval( particleSystem, "loop", 1000/frameRate );
		( "particleSystem" is a reference to the particle system itself )
*/



class ParticleSystem extends mx.events.EventDispatcher
{
	// default settings
	var particlesPerFrame;	// How many particles to create per frame.  This can also be a decimal to skip frames.
	var speed;							// How many pixels to move per frame
	var speedVariation;			// A random variation added or subtracted from the speed.  (ie:  speed:4  variation:2  result: 2-6 )
	var life;								// How long each particle lasts.
	var lifeVariation;			// A random variation added or subtracted from the life.  (ie:  life:10  variation:10  result: 5-15 )
	var sprayTime;					// How long to emit particles
	var scale;							// A size multiplier.  (1 = full size)
	var scaleVariation;			// A random variation added or subtracted from the scale.  (ie:  scale:4  variation:2  result: 2-6 )
	var growFrames;					// How many frames to scale up near the beginning of a particle's life
	var shrinkFrames;				// How many frames to scale down near the end of a particle's life
	var fadeInFrames;				// How many frames to fade-in near the beginning of a particle's life
	var fadeOutFrames;			// How many frames to fade-out near the end of a particle's life
	var customData;					// Arbitrary data passed to the particle itself. This allows custom effects to recieve parameters.
	var image;							// The image used for each particle
	var angle;							// The direction (in degrees) to spray particles in.  (0 = right, 90 = up, 180 = left, 270 = down)
	var spread;							// A random variation added or subtracted from the angle.  (ie:  angle:90  variation:2  result: 89-91 )
	
	// internal data
	var particle_array;
	var particlesToMake;
	var emitter_mc;
	var loopInterval;
	var sprayTimer;
	var AVOID_LOOP_PANIC;
	var setTimeout;
	var clearTimeout;
	
	// event function
	public var onComplete;					// externally-defined function called when all particles have died
	
	
	
	function ParticleSystem(newEmitter_mc, newSettings, newCustomData)
	{
		// avoidLoopPanic.as  VER:		1.2
		function make_avoidLoopPanic( thresh ){
			var thresh = thresh || 40;
			var stackCounter = 0;
			var delay = 0;
			var timeout = null;
			
			function resetStackCounter(){
				stackCounter = 0;
			}// resetStackCounter()
			
			return function( callback ){
				stackCounter++;
				if(stackCounter > thresh){
					if(timeout!==null)	clearTimeout(timeout);
					setTimeout(function(){
						resetStackCounter();
						callback();
					}, delay);
				}else{
					callback();
					// if any time passes, reset the recursion counter
					if(timeout!==null)	clearTimeout(timeout);
					timeout = setTimeout( function(){
						resetStackCounter();
					}, 0 );
				}
			}// return AVOID_LOOP_PANIC()
		}// make_avoidLoopPanic()
		if(!_global.AVOID_LOOP_PANIC){
			_global.AVOID_LOOP_PANIC = make_avoidLoopPanic();
		}
		AVOID_LOOP_PANIC = _global.AVOID_LOOP_PANIC;
		delete make_avoidLoopPanic;
		
		
		
		// initialize internal data
		unload = once(unload);
		particle_array = new Array();
		particlesToMake = 0;
		
		// resolve & apply settings
		this.emitter_mc = newEmitter_mc;
		this.particlesPerFrame = (newSettings.particlesPerFrame != undefined) ? newSettings.particlesPerFrame : 1;
		this.speed = (newSettings.speed != undefined) ? newSettings.speed : 1;
		this.speedVariation = (newSettings.speedVariation != undefined) ? newSettings.speedVariation : 0;
		this.life = (newSettings.life != undefined) ? newSettings.life : 30;
		this.lifeVariation = (newSettings.lifeVariation != undefined) ? newSettings.lifeVariation : 0;
		this.sprayTime = (newSettings.sprayTime) ? newSettings.sprayTime : null;
		this.scale = (newSettings.scale != undefined) ? newSettings.scale : 1;
		this.scaleVariation = (newSettings.scaleVariation != undefined) ? newSettings.scaleVariation : 0;
		this.growFrames = (newSettings.growFrames != undefined) ? newSettings.growFrames : 0;
		this.shrinkFrames = (newSettings.shrinkFrames != undefined) ? newSettings.shrinkFrames : 0;
		this.fadeInFrames = (newSettings.fadeInFrames != undefined) ? newSettings.fadeInFrames : 0;
		this.fadeOutFrames = (newSettings.fadeOutFrames != undefined) ? newSettings.fadeOutFrames : 0;
		this.customData = {};
		if(newSettings.customData)
			this.customData = newSettings.customData;
		if(newCustomData)
			this.customData = newCustomData;
		this.image = (newSettings.image != undefined) ? newSettings.image : "test_particle";
		this.angle = (newSettings.angle != undefined) ? newSettings.angle : 0;
		this.spread = (newSettings.spread != undefined) ? newSettings.spread : 360;
		
		// begin the loop
		//var FPS = 1000/30;
		var FPS = 30;
		loopInterval = setInterval(this, "loop", FPS);
	}// CONSTRUCTOR()
	
	
	
	function loop()	{
		if(!this.emitter_mc._name){
			unload();
			return;
		}// if:  particle emitter no longer exists
		
		//trace("particle loop()");
		var makeNewParticles = this.makeNewParticles;
		var moveAllParticles = this.moveAllParticles;
		var checkSprayTime = this.checkSprayTime;
		var context = this;
		
		AVOID_LOOP_PANIC(function(){
			makeNewParticles.apply( context );
			moveAllParticles.apply( context );
			checkSprayTime.apply( context );
		});
		
		/*
		makeNewParticles();
		moveAllParticles();
		checkSprayTime();
		*/
	}	// loop()
	
	
	public function unload(){
		loop = null;
		deleteAllParticles();
		clearInterval(loopInterval);
	}// unload()
	
	
	
	function makeNewParticles()
	{
		particlesToMake += particlesPerFrame;
		for (var p = 0; p < Math.floor(particlesToMake); p++)
		{
			var newParticle = makeParticle();
			if (newParticle)
				particle_array.push(newParticle);
			var particleWasMade = true;
		}
		// for:  each particle to make
		if (particleWasMade)
			particlesToMake = 0;
	}	// makeNewParticles()
	
	
	
	function makeParticle()
	{
		// depth
		var newDepth = Math.floor(Math.random() * 9999);
		while (emitter_mc._parent.getInstanceAtDepth(newDepth) != undefined)
		{
			newDepth = Math.floor(Math.random() * 9999);
		}
		// name
		var newName = "particle_" + newDepth;
		// vector
		var newSpeed = speed + (Math.random() * speedVariation) - (speedVariation / 2);
		var degrees = -angle % 360;
		degrees += Math.random() * spread - (spread / 2);
		var radian = degrees * (2 * Math.PI) / 360;
		var particle_vec = flash.geom.Point.polar(newSpeed, radian);
		// life-span
		var particleLife = life + (Math.random() * lifeVariation) - (lifeVariation / 2);
		// scale
		var particleScale = scale + (Math.random() * scaleVariation) - (scaleVariation / 2);
		particleScale *= 100;		// convert to a percent
		
		// place the particle
		if(image instanceof flash.display.BitmapData)
		{// if:  particle's "image" is raw BitmapData
			var newParticle = emitter_mc._parent.createEmptyMovieClip( newName, newDepth );
			// display & center the bitmap image within the new particle
			var image_mc = newParticle.createEmptyMovieClip("image_mc", 0);
			image_mc._x = -image.width/2;
			image_mc._y = -image.height/2;
			image_mc.attachBitmap( image, 0 );
			var particleSettings = {particleData:customData, particle_vec:particle_vec, particleLife:particleLife, particleFullLife:particleLife, particleFullScale:particleScale};
			for(var nam in particleSettings){
				newParticle[nam] = particleSettings[nam];
			}
		}// if:  particle's "image" is raw BitmapData
		else
		{// if:  particle's "image" is a movieClip linkage
			var newParticle = emitter_mc._parent.attachMovie(image, newName, newDepth, {particleData:customData, particle_vec:particle_vec, particleLife:particleLife, particleFullLife:particleLife, particleFullScale:particleScale});
		}// if:  particle's "image" is a movieClip linkage
		
		for(var nam in customData){
			newParticle[nam] = customData[nam];
		}
		// initial position
		newParticle._x = emitter_mc._x;
		newParticle._y = emitter_mc._y;
		// apply the scale
		newParticle._xscale = newParticle._yscale = particleScale;
		if (growFrames != 0)
			newParticle._xscale = newParticle._yscale = 0;
		
		return newParticle;
	}	// makeParticle()
	
	
	
	function moveAllParticles()
	{
		for (var p = 0; p < particle_array.length; p++)
		{
			var thisParticle = particle_array[p];
			thisParticle.particleLife--;
			if (checkForDeath(p))
			{// particle has died
				p--;				// don't increment or you'll skip the next particle
			}
			else
			{// particle still lives
				moveParticle(p);
				growParticle(p);
				shrinkParticle(p);
				fadeInParticle(p);
				fadeOutParticle(p);
			}// if:  particle still lives
		}// for:  each particle
	}// moveAllParticles()
	
	
	
	function moveParticle(p)
	{
		var thisParticle = particle_array[p];
		thisParticle._x += thisParticle.particle_vec.x;
		thisParticle._y += thisParticle.particle_vec.y;
	}	// moveParticle()
	
	
	
	function checkSprayTime()
	{
		if(sprayTime != null)
		{// if:  sprayTime has been defined
			// if particle emitter is run again,  restart the spray timer
			if(particlesPerFrame>0  &&  sprayTimer == null)
				sprayTimer = sprayTime;
			
			if(sprayTimer==0)
			{// if:  sprayTimer hits Zero
				particlesPerFrame=0;		// stop emitting particles
				sprayTimer = null;
			}// if:  sprayTimer hits Zero
			else if(sprayTimer>0)
			{// if:  sprayTimer is still counting
				sprayTimer--;
			}// if:  sprayTimer is still counting
		}// if:  sprayTime has been defined
	}// checkSprayTime()
	
	
	
	function checkForDeath(p)
	{
		var thisParticle = particle_array[p];
		if (thisParticle.particleLife <= 0)
		{// if:  this particle is dead 
			// remove it from the particle_array
			particle_array.splice(p, 1);
			// remove this particle
			thisParticle.removeMovieClip();
			// announce when all particles have died
			if(particle_array.length==0){
				this.dispatchEvent({type:"onComplete", emitter_mc:emitter_mc, emitter_obj:this});
				this.onComplete();
			}
			// report that this particle has died
			return true;
		}// if:  this particle is dead 
		else
		{// if:  no death occurred
			return false;
		}// if:  no death occurred
	}	// checkForDeath()
	
	
	
	function growParticle(p)
	{
		if (growFrames)
		{// if:  growing occurrs 
			var thisParticle = particle_array[p];
			if (thisParticle.particleLife > (thisParticle.particleFullLife - growFrames))
			{// if:  particle's life is within the growing period 
				var period = thisParticle.particleFullLife - thisParticle.particleLife;
				var periodScale = period / growFrames;
				// 0-1
				var newScale = thisParticle.particleFullScale * periodScale;
				thisParticle._xscale = thisParticle._yscale = newScale;
			}// if:  particle's life is within the growing period 
		}// if:  growing occurrs 
	}// growParticle()
	
	
	
	function shrinkParticle(p)
	{
		if (shrinkFrames)
		{// if:  shrinking occurrs 
			var thisParticle = particle_array[p];
			if (thisParticle.particleLife <= shrinkFrames)
			{// if:  particle's life is within the shrinking period 
				var shrinkInterval = 100 / shrinkFrames;
				thisParticle._xscale -= shrinkInterval;
				thisParticle._yscale -= shrinkInterval;
			}// if:  particle's life is within the shrinking period 
		}// if:  shrinking occurrs 
	}// shrinkParticle()
	
	
	
	function fadeInParticle(p)
	{
		if (fadeInFrames)
		{// if:  fade_in occurrs 
			var thisParticle = particle_array[p];
			if (thisParticle.particleLife > (thisParticle.particleFullLife - fadeInFrames))
			{// if:  particle's life is within the fade-in period 
				var period = thisParticle.particleFullLife - thisParticle.particleLife;
				var periodFade = period / fadeInFrames;
				// 0-1
				var newFade = 100 * periodFade;
				thisParticle._alpha = newFade;
			}// if:  particle's life is within the fade-in period 
		}// if:  fade_in occurrs 
	}// fadeInParticle()
	
	
	
	function fadeOutParticle(p)
	{
		if (fadeOutFrames)
		{// if:  fade_out occurrs 
			var thisParticle = particle_array[p];
			if (thisParticle.particleLife <= fadeOutFrames)
			{// if:  particle's life is within the fade-out period 
				var fadeInterval = 100 / fadeOutFrames;
				thisParticle._alpha -= fadeInterval;
			}// if:  particle's life is within the fade-out period 
		}// if:  fade_out occurrs 
	}// fadeOutParticle()
	
	
	
	function deleteAllParticles()
	{
		for (var p in particle_array)
		{
			particle_array[p].removeMovieClip();
		}
	}// deleteAllParticles()
	
	
	
	function once( func ){
		var done = false;
		
		return function () {
			return done ? void 0 : ((done = true), func.apply(this, arguments));
		}
	}// once()
}// ParticleSystem
