// Animation Shim by Paul Irish
window.requestAnimFrame = (function () {
	return  window.requestAnimationFrame       || 
			window.webkitRequestAnimationFrame || 
			window.mozRequestAnimationFrame    || 
			window.oRequestAnimationFrame      || 
			window.msRequestAnimationFrame     || 
			function (callback, element) {
				window.setTimeout(callback, 1000 / 60);
			};
})();


// This pattern sure gets frickin annoying, wrapper required
function randomRange(min, max) { return ((Math.random()*(max-min)) + min); }





/*
 * Particle Class
 *
 */

var Particle = function (_graphic) {

	// Management
	this.myAge		= 0;
	this.live		= true;
	this.span		= 100;

	// Position and movement
	this.pos		= [0, 0];
	this.vel		= [0, 0];
	this.acc		= [0, 0];

	// Display Properties
	this.size		= 0;
	this.theta		= 0;
	this.alpha		= 1;

	// Delta values
	this.dsize		= 1;
	this.dtheta		= 0;
	this.dalpha		= 0;

	// Render function - whichever is required
	this.mode		= 'source-over';
	this.graphic	= _graphic;
	this.draw		= (this.graphic) ? this.drawGraphic : this.drawPlain;

}

Particle.prototype = {

	age			: function () {

		if (!this.live) { return; }

		this.myAge += 1;

		if (this.myAge > this.span) { this.live = false; return; }

		this.size		*= this.dsize;
		
		this.alpha		+= this.dalpha;
		if(this.alpha < 0) { this.alpha = 0; }

		var p = this.myAge / this.span * Math.PI * 2;

		this.theta		+= this.dtheta;

		this.vel[0]		*= this.acc[0];
		this.vel[1]		*= this.acc[1];

		this.pos[0]		+= this.vel[0];
		this.pos[1]		+= this.vel[1];

	},

	drawGraphic	: function (_cx) {

		if (!this.live) { return; }

        _cx.save();  
	         
        _cx.translate(		this.pos[0],	this.pos[1]		);
        _cx.scale(			this.size, 		this.size		);
		_cx.rotate(			this.theta						);
        _cx.translate(this.graphic.width / -2, this.graphic.width / -2 );

        _cx.globalAlpha				 = this.alpha; 
        _cx.globalCompositeOperation = this.mode;

        _cx.drawImage(this.graphic, 0, 0);

        _cx.restore();

	}


};


/* 
 * Particle Emitter Class
 *
 */

var ParticleEmitter = function (_canvas, _positionX, _positionY, _emitterWidth, _emitterHeight, _emitterSpeed, _particleConfig) {

	/* 
	 * Fallbacks
	 *
	 */

	var $canvas	= $(_canvas),
		canvas	= $canvas.get(0);

	// if !canvas {
	//
	// 		flashcanvas here
	//
	// } 

	var cx = canvas.getContext('2d');
		cx.globalCompositeOperation = 'source-over';

	/*
	 * Properties
	 *
	 */

	var allParticles 		= [],									// All particles in the system
		framesElapsed 		= 0,									// Track number of frames processed
	
		emitter				= {
			pos				: [_positionX, _positionY ], 			// Position of the emitter on screen	
			dim				: [_emitterWidth, _emitterHeight], 		// Emission zone	
		},

		// Emitter config
		opt_showEmitter		=	false,								// Draw the emitter visibly on the screen
		opt_emissionRate	=	2,									// How many new particles per frame
		opt_particleLimit	=	300,								// Limit number of particles at once
		opt_gravity			=	0.01,								// Gravity multiplier
		
	
		// Particle Config		
		particleConfig_size			=	1,							// Radius of blob (in units)
		particleConfig_theta		=	0,							// Initial rotation
		particleConfig_alpha		=	1,							// Opacity
		particleConfig_jitter		=	0.2,						// Randomness of aging
		particleConfig_life			=	250,						// Total lifespan in frames
		particleConfig_graphic		=	false,						// Images reference, if false, just draw a circle
		particleConfig_composite	=	'source-over',				// Global compositing mode
		particleConfig_init			=	null,						// Function run at particle birth

		particleConfig_dsize		=	1,							// Scale multiplier
		particleConfig_dtheta		=	0,							// Rotation (additive)
		particleConfig_dalpha		=	0,							// Additive opacity delta
		particleConfig_custom		=	null;						// Function run on particle aging
	


	/* 
	 * Methods
	 *
	 */

	// Draw emitter on screen
	function showEmitter () {

		opt_showEmitter = true;

	}

	// Change compositing mode
	function setCompositing (_compMode) {

		particleConfig_composite = _compMode;

	}

	// Bind custom birth configuration function
	function setParticleCustomInit (_initFunc) {

		particleConfig_init = _initFunc;

	}

	// Bind custom aging function 
	function setParticleCustomAge (_ageFunc) {

		particleConfig_custom = _ageFunc;

	}


	// Create Particle
	function newParticle (_pos, _vel, _acc) {

		// apply default values
		var thisParticle = new Particle (particleConfig_graphic);

		thisParticle.pos		= (_pos) ? _pos : [
			randomRange(emitter.pos[0] - emitter.dim[0] / 2, emitter.pos[0] + emitter.dim[0] / 2),	
			randomRange(emitter.pos[1] - emitter.dim[1] / 2, emitter.pos[1] + emitter.dim[1] / 2)
		];

		// Lifecycle
		thisParticle.span		= particleConfig_life;

		// Display Properties
		thisParticle.theta		= particleConfig_theta;
		thisParticle.alpha		= particleConfig_alpha;
		thisParticle.size 		= particleConfig_size;

		// Delta values
		thisParticle.dsize		= particleConfig_dsize;
		thisParticle.dtheta		= particleConfig_dtheta;
		thisParticle.dalpha		= particleConfig_dalpha;

		// Render function - whichever is required
		thisParticle.mode		= particleConfig_composite;

		// Run custom initialiser
		if (particleConfig_init !== null) {

			particleConfig_init.apply(thisParticle);

		}

		// Save new particle to scene
		allParticles.push(thisParticle);
	}

	// Draw everything
	function drawAllParticles() {

		var currentParticle;

		for (var i = 0; i < allParticles.length; i++) {

			currentParticle = allParticles[i];

			currentParticle.age();

			if (particleConfig_custom) {

				particleConfig_custom.apply(currentParticle);

			}

			currentParticle.draw(cx);
		}

	}


	// Draw the particle emitter
	function drawEmitter () {

		cx.fillStyle = "rgba(50, 200, 200, 0.5)";
		cx.fillRect(
				emitter.pos[0] - emitter.dim[0] / 2, 
				emitter.pos[1] - emitter.dim[1] / 2,
				emitter.dim[0], 
				emitter.dim[1]
			);

	}

	/*
	 * Render loop
	 *
	 */

	function renderFrame() {

		// Emit new particles
		for (var i = 0, max = opt_emissionRate; i < max; i++) { newParticle(); }

		// Clear
		cx.clearRect(0, 0, canvas.width, canvas.height);

		// Draw emitter itself if required
		if (opt_showEmitter) { drawEmitter(); }

		// Draw live particles
		drawAllParticles();

		// Cull particles above the emitter limit
		while (allParticles.length > opt_particleLimit) { allParticles.shift(); }
		
		// Count frame
		framesElapsed++;

		// Next frame
		requestAnimFrame(renderFrame, canvas);

	}


	function start(_graphic) {

		// If a graphic was supplied with init call
		if (_graphic) {

			var img = new Image();

			img.onload = function () {

				// Set graphic for use
				particleConfig_graphic = img;

				// scale '1' to be one real unit, not one multiple of the image's size
				particleConfig_size = particleConfig_size / img.width;
				renderFrame();

			}

			img.src = _graphic; 
		
		} else {

			renderFrame();

		}
	
	}



	/*
	 * Expose public methods
	 *
	 */

	return {

		start			:	start,

		newParticle		:	newParticle,

		setCompositing	: 	setCompositing,
		setCustomInit	:	setParticleCustomInit,
		setCustomAge	:	setParticleCustomAge,

		showEmitter		:	showEmitter

	};

}

