(function initJParticle( $ ){

	"use strict";

	var createParticlesSandbox, Utils;

	Utils = {};

	/*
	 * Create jParticle animation.
	 * @param {Object} options Few jParticle options.
	 * @return {Object} jQuery object for chaining.
	 */
	$.fn.jParticle = function jParticle( options ){
	
		this.each(function( _, el ){

			if ( typeof el.sandbox === 'object' ) {

				$( el ).removeJParticle();
			}

			el.sandbox = createParticlesSandbox( el, options );
		});
	
		return this;
	};

	/*
	 * Remove jParticle canvas.
	 * @return {Object} jQuery object for chaining.
	 */
	$.fn.removeJParticle = function removeJParticle(){

		this.each(function( _, el ){

			if ( el.sandbox ) {

				el.sandbox.remove();
				delete el.sandbox;
			}
		});

		return this;
	};

	/*
	 * Freeze jParticle animation.
	 * @return {Object} jQuery object for chaining.
	 */
	$.fn.freezeJParticle = function freezeJParticle(){

		this.each(function( _, el ){

			if ( el.sandbox ) {

				el.sandbox.freeze();
			}
		});

		return this;
	};

	/*
	 * Unfreeze jParticle animation.
	 * @return {Object} jQuery object for chaining.
	 */
	$.fn.unfreezeJParticle = function unfreezeJParticle(){

		this.each(function( _, el ){

			if ( el.sandbox ) {

				el.sandbox.unfreeze();
			}
		});

		return this;
	};

	/*
	 * Create a particles sandbox instance.
	 * @param {Object} element Element for the sandbox.
	 * @param {Object} params Few sandbox's params.
	 * @return {Object} Particles sandbox object.
	 */
	createParticlesSandbox = function createParticlesSandbox( element, params ){

		var ParticlesSandbox, createParticle;

		ParticlesSandbox = {};
        
		ParticlesSandbox.canvas = {};
		ParticlesSandbox.mouse = {};
		ParticlesSandbox.particles = [];

		ParticlesSandbox.isAnimated = false;

		/*
		 * Initialize the sandbox
		 * @param {Object} element Element for the sandbox.
		 * @param {Object} params Few sandbox's params.
		 */
		ParticlesSandbox.initialize = function initialize( element, params ){

			ParticlesSandbox.initParams( params );
			ParticlesSandbox.initHTML( element );
			ParticlesSandbox.initParticles();
			ParticlesSandbox.initEvents();
			ParticlesSandbox.initAnimation();
		};

		/*
		 * Initialize sandbox's params.
		 * @param {Object} params Few sandbox's params.
		 */
		ParticlesSandbox.initParams = function initParams( params ){

			if ( params && params.color && (!params.particle || ( params.particle && !params.particle.color ) ) ) {

				if ( !params.particle ) {

					params.particle = {};
				}

				params.particle.color = params.color;
			}

			ParticlesSandbox.params = $.extend({
				particlesNumber: 100,
				linkDist: 50,
				createLinkDist: 150,
				disableLinks: false,
				disableMouse: false,
				color: 'white',
				width: null,
				height: null,
				linksWidth: 1
			}, params );
		};

		/*
		 * Initialize the sandbox's html.
		 * @param {Object} element Element for the sandbox.
		 */
		ParticlesSandbox.initHTML = function initHTML( element ){

			var canvas;

			canvas = ParticlesSandbox.canvas;

			canvas.container = $( element );
			canvas.element = $('<canvas/>');

			canvas.context = canvas.element.get(0).getContext('2d');

			canvas.container.append( canvas.element );

			canvas.element.css( 'display', 'block' );

			canvas.element.get(0).width = ( ParticlesSandbox.params.width ) ? ParticlesSandbox.params.width : canvas.container.width();
			canvas.element.get(0).height = ( ParticlesSandbox.params.height ) ? ParticlesSandbox.params.height : canvas.container.height();

			canvas.element.css( 'background', ParticlesSandbox.params.background );
		};

		/*
		 * Resize canvas.
		 */
		ParticlesSandbox.resize = function resize( width, height ){

			if ( width ) {

				canvas.element.get(0).width = width;
			}

			if ( height ) {

				canvas.element.get(0).height = height;
			}
		};

		/*
		 * Create all particles in the sandbox.
		 */
		ParticlesSandbox.initParticles = function initParticles(){

			var i, count;

			i = 0;
			count = ParticlesSandbox.params.particlesNumber;

			for ( ; i < count; i += 1 ) {

				ParticlesSandbox.particles.push( createParticle(
					ParticlesSandbox.canvas.element.get(0),
					ParticlesSandbox.params.particle
				) );
			}
		};

		/*
		 * Initialize the sandbox's events.
		 */
		ParticlesSandbox.initEvents = function initEvents(){

			ParticlesSandbox.canvas.element.mouseenter(function mouseEnterCallback(){

				ParticlesSandbox.mouse.hoverCanvas = true;

				if ( !ParticlesSandbox.isAnimated ) {

					ParticlesSandbox.draw();
				}
			});

			ParticlesSandbox.canvas.element.mouseleave(function mouseLeaveCallback(){

				ParticlesSandbox.mouse.hoverCanvas = false;
			});

			ParticlesSandbox.canvas.element.mousemove(function mouseMoveCallback(e){

				ParticlesSandbox.mouse = $.extend( ParticlesSandbox.mouse, Utils.getMousePosition( e, ParticlesSandbox.canvas.element[0] ) );
			});
		};

		/*
		 * Initialize the sandbox's animation.
		 */
		ParticlesSandbox.initAnimation = function initAnimation(){

			window.requestAnimFrame = 
				window.requestAnimationFrame || 
				window.webkitRequestAnimationFrame ||
				window.mozRequestAnimationFrame || 
				window.ORequestAnimationFrame || 
				window.msRequestAnimationFrame ||

				function requestAnimFrame( callback ){

					setTimeOut( callback, 1000/60 );
				};

			ParticlesSandbox.isAnimated = true;

			ParticlesSandbox.draw();
		};

		/*
		 * Draw the sandbox canvas.
		 */
		ParticlesSandbox.draw = function draw(){

			var i, j, count, canvas, particle, particle2;

			i = 0;
			count = ParticlesSandbox.particles.length;
			canvas = ParticlesSandbox.canvas;

			canvas.context.clearRect( 0, 0, canvas.element.get(0).width, canvas.element.get(0).height );
		
			for ( ; i < count; i += 1 ) {

				particle = ParticlesSandbox.particles[i];

				if ( ParticlesSandbox.isAnimated ) {

					particle.update();
				}

				particle.draw();

				if ( !ParticlesSandbox.params.disableMouse && ParticlesSandbox.mouse.hoverCanvas ) {
					ParticlesSandbox.drawLink(
						particle.getPosition('x'),
						particle.getPosition('y'), 
						ParticlesSandbox.mouse.x,
						ParticlesSandbox.mouse.y
					);
				}

				if ( !ParticlesSandbox.params.disableLinks ) {

					for ( j = i+1; j < count; j += 1 ) {

						particle2 = ParticlesSandbox.particles[j];

						ParticlesSandbox.drawLink( 
							particle.getPosition('x'),
							particle.getPosition('y'), 
							particle2.getPosition('x'),
							particle2.getPosition('y') 
						);
					}
				}
			}

			ParticlesSandbox.requestID = window.requestAnimFrame( ParticlesSandbox.draw );
		};

		/*
		 * Draw a link between two particles.
		 * @param {int} x First object abscissa coords.
		 * @param {int} y First object ordered coords.
		 * @param {int} x2 Second object abscissa coords.
		 * @param {int} y2 Second object ordered coords.
		 */
		ParticlesSandbox.drawLink = function drawLink( x, y, x2, y2 ){

			var context;

			if ( Utils.getDistance( x, y, x2, y2 ) <= ParticlesSandbox.params.createLinkDist ) {

				context = ParticlesSandbox.canvas.context;

				context.save();

				context.beginPath();
				context.lineWidth = ParticlesSandbox.params.linksWidth; 
				context.moveTo( x, y );
				context.lineTo( x2, y2 );
				context.globalAlpha = ParticlesSandbox.getOpacityLink( x, y, x2, y2 );
				context.strokeStyle = ParticlesSandbox.params.color;
				context.lineCap = 'round';
				context.stroke();
				context.closePath();

				context.restore();
			}
		};

		/*
		 * Get opacity for link two particles.
		 * @param {int} x First object abscissa coords.
		 * @param {int} y First object ordered coords.
		 * @param {int} x2 Second object abscissa coords.
		 * @param {int} y2 Second object ordered coords.
		 * @return {int} 0 <= opacity <= 1
		 */
		ParticlesSandbox.getOpacityLink = function getOpacityLink( x, y, x2, y2 ){

			var dist, opacity, linkDist, createLinkDist;

			dist = Utils.getDistance( x, y, x2, y2 );
			linkDist = ParticlesSandbox.params.linkDist;
			createLinkDist = ParticlesSandbox.params.createLinkDist;

			if ( dist <= linkDist ) {

				opacity = 1;
			} else if ( dist >  createLinkDist ) {

				opacity = 0;
			} else {

				opacity = 1 - ( ( ( dist - linkDist ) * 100 ) / ( createLinkDist - linkDist ) ) / 100;
			}

			return opacity;
		};

		/*
		 * Freeze the animation.
		 */
		ParticlesSandbox.freeze = function freeze(){

			if ( ParticlesSandbox.isAnimated ) {

				ParticlesSandbox.isAnimated = false;
			}
		};

		/*
		 * Unfreeze the animation.
		 */
		ParticlesSandbox.unfreeze = function unfreeze(){

			if ( !ParticlesSandbox.isAnimated ) {

				ParticlesSandbox.isAnimated = true;
			}
		};

		/*
		 * Remove the animation's canvas.
		 */
		ParticlesSandbox.remove = function remove(){

			ParticlesSandbox.canvas.element.remove();
		};

		/*
		 * Create a particle instance.
		 * @param {Object} canvas DOM element.
		 * @param {Object} params Few particle's params.
		 * @return {Object} Particle object.
		 */
		createParticle = function createParticle( canvas, params ){

			var Particle;

			Particle = {};

			Particle.canvas = {};
			Particle.vector = {};

			/*
			 * Initialize the particle.
			 * @param {Object} canvas DOM element.
			 * @param {Object} params Few particle's params.
			 */
			Particle.initialize = function initialize( canvas, params ){

				Particle.params = $.extend({
					color: 'white',
					minSize: 2,
					maxSize: 4,
					speed: 60
				}, params );

				Particle.setCanvasContext( canvas );

				Particle.initSize();
				Particle.initPosition();
				Particle.initVectors();
			};

			/*
			 * Initialize particle's position.
			 */
			Particle.initPosition = function initPosition(){

				Particle.x = Utils.getRandNumber( 0 + Particle.radius, Particle.canvas.element.width - Particle.radius );
				Particle.y = Utils.getRandNumber( 0 + Particle.radius, Particle.canvas.element.height - Particle.radius );
			};

			/*
			 * Initialize particle's size.
			 */
			Particle.initSize = function initSize(){

				Particle.size = Utils.getRandNumber( Particle.params.minSize, Particle.params.maxSize );
				Particle.radius = Particle.size / 2;
			};

			/*
			 * Initialize particle's vectors for speed.
			 */
			Particle.initVectors = function initVectors(){

				do {
					Particle.vector.x = Utils.getRandNumber( -Particle.params.speed / 60, Particle.params.speed / 60, false );
					Particle.vector.y = Utils.getRandNumber( -Particle.params.speed / 60, Particle.params.speed / 60, false );

				} while ( Particle.vector.x == 0 || Particle.vector.y == 0 )
			};

			/*
			 * Set the context to draw particles.
			 * @param {Object} canvas Canvas.
			 */
			Particle.setCanvasContext = function setCanvasContext( canvas ){

				var context;

				Particle.canvas.element = canvas;
				context = canvas.getContext('2d');

				if ( typeof context === 'object' && typeof context.canvas === 'object' ) {

					Particle.canvas.context = context;
				} else {

					throw "Error: Can't set canvas context to Particle because context isn't a CanvasRenderingContext2D object.";
				}
			};

			/*
			 * Draw particle.
			 */
			Particle.draw = function draw(){

				var context = Particle.canvas.context;

				context.beginPath(); 
				context.arc( Particle.x, Particle.y, Particle.size /2, 0, Math.PI*2 ); 
				context.fillStyle = Particle.params.color;
				context.fill();
				context.closePath();
			};

			/*
			 * Update the particle's position.
			 */
			Particle.update = function update(){

				Particle.x += Particle.vector.x;
				Particle.y += Particle.vector.y;

				if ( 0 > ( Particle.x - Particle.radius ) || ( Particle.x + Particle.radius ) > Particle.canvas.element.width ) {

					Particle.vector.x = -Particle.vector.x;
				}

				if ( 0 > ( Particle.y - Particle.radius ) || ( Particle.y + Particle.radius ) > Particle.canvas.element.height ) {

					Particle.vector.y = -Particle.vector.y;
				}
			};

			/*
			 * Return position of particle.
			 * @param {string} axis Optionnal axis.
			 * @return {int|Object} Return object if axis is not defined, else return int.
			 */
			Particle.getPosition = function getPosition( axis ){

				if ( typeof axis === 'string' && ( axis != 'x' && axis != 'y' ) ) {

					axis = null;
				}

				return ( typeof( axis ) === 'string' ) ? Particle[ axis ] : { x: Particle.x, y: Particle.y };
			};

			Particle.initialize( canvas, params );

			return {
				getPosition: Particle.getPosition,
				update: Particle.update,
				draw: Particle.draw
			};
		};

		ParticlesSandbox.initialize( element, params );

		return {
			remove: ParticlesSandbox.remove,
			freeze: ParticlesSandbox.freeze,
			unfreeze: ParticlesSandbox.unfreeze,
			resize: ParticlesSandbox.resize
		};
	};

	/*
	 * Get rand number between x and y.
	 * @param {int} x Minimal number.
	 * @param {int} y Maximal number.
	 * @param {Boolean} round True is value shouldn't be round.
	 * @return {int} Rand number.
	 */
	Utils.getRandNumber = function getRandNumber( x, y, round ){

		var value;

		if( x == null ) {

			x = 0;
		}

		if( y == null ) {

			y = 10;
		}

		if( round == null ) {

			round = true;
		}

		value = Math.random() * ( y - x ) + x;

		return ( round ) ? Math.round( value ) : value;
	};

	/*
	 * Get distance between to cartesian points.
	 * @param {int} x First object abscissa coords.
	 * @param {int} y First object ordered coords.
	 * @param {int} x2 Second object abscissa coords.
	 * @param {int} y2 Second object ordered coords.
	 * @return {int} Distance.
	 */
	Utils.getDistance = function getDistance( x, y, x2, y2 ){

		return Math.sqrt( Math.pow( x2 - x, 2 ) + Math.pow( y2 - y, 2 ) );
	};

	/*
	 * Get mouse position.
	 * @param {Object} event The HTML DOM events.
	 * @param {Object} element The DOM element.
	 * @return {Object} x/y position.
	 */
	Utils.getMousePosition = function getMousePosition( event, element ){

		var rect;

		if ( typeof element === 'undefined' ) {

			element = $('body')[0];
		}

		rect = element.getBoundingClientRect();

		return {
			x: event.clientX - rect.left,
			y: event.clientY - rect.top
		};
	};

})( jQuery )
