function IncMosaic()
{
	// --------------------------------------------------------------------------------------------------------------------
	// Members
	// --------------------------------------------------------------------------------------------------------------------
	
	// Canvas
	this.canvasId;
	this.canvas;
	this.ctx;
	this.imageData;

	/// Images
	this.imageUrls = [];
	this.listeUrls = [];	// The images list to blend
	this.pairImages = [];
	this.imagesLoaded = 0;
	this.imageLoadedIndex = -1;
	this.imageWidth;
	this.imageHeight;
	this.postFirstImageCallback;

	// Effect
	this.waitStartEffectTime = 3000;
	this.waitStartEffectGo;
	this.effectSpeed;
	this.squareCountX;
	this.squareCountY;
	this.srcSquareWidth;
	this.srcSquareHeight;
	this.squareWidth;
	this.squareHeight;
	this.effects = [];
	this.squareEffects = [];
	this.loop;
	this.endEffecFunc;

	// Time
	this.startTime;

	// --------------------------------------------------------------------------------------------------------------------
	// Functions
	// --------------------------------------------------------------------------------------------------------------------

	this.addImageUrl = function(url)
	{
		this.imageUrls.push(url);
	};

	this.start = function(canvasId, effectSpeed, squareCountX, squareCountY, loop, waitStartEffectGo, endEffecFunc, postFirstImageCallback)
	{
		// Canvas ID
		this.canvasId = canvasId;

		// Speed
		this.effectSpeed = 1 / effectSpeed;

		// Set square count
		this.squareCountX = squareCountX;
		this.squareCountY = (squareCountY === undefined) ? squareCountX: squareCountY;

		// End effect behavior
		this.loop = loop;
		this.endEffecFunc = endEffecFunc;

		// Does the effect wait for go
		this.waitStartEffectGo = waitStartEffectGo;

		// callback to call after the load of the first image
		this.postFirstImageCallback = postFirstImageCallback;

		// Init the canvas objects
		this.init();

		// Register effects
		this.registerEffects();

		// Set a new effect transition between 2 images
		this.setNewEffect();
										
		// Main loop
		this.loopCallback();
	};

	this.setNewEffect = function()
	{
		// Set a random pair images
		this.setRandomPairImages();

		// Set random effect on the squares
		this.setRandomSquareEffect();

		// Time
		this.startTime = new Date().getTime();		
	};

	this.UnpauseEffect = function(go)
	{
		this.waitStartEffectGo = !go;
	};

	this.init = function()
	{
		if (this.canvas === undefined || this.canvas === null) {
			// Init canvas objects
			this.canvas = document.getElementById(this.canvasId );
			this.ctx = this.canvas.getContext('2d');
			this.ctx.fillStyle = "#000000";
			this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);			
		}
	};

	this.registerPostNewCanvasSize = function(newCanvasWidth, newCanvasHeight)
	{
		// Recalculate the size of the mosaic squares
		incMosaic.squareWidth = Math.floor(newCanvasWidth / incMosaic.squareCountX);
		incMosaic.squareHeight = Math.floor(newCanvasHeight / incMosaic.squareCountY);
	};

	this.fillListUrl = function()
	{

		if (this.waitStartEffectGo) {
			// Not Random

			for (var i = 0; i < this.imageUrls.length; ++i) {
				this.listeUrls.push(this.imageUrls[i]);
			}

		} else {

			// Random

			var urls = [];
			for (var i = 1; i < this.imageUrls.length; ++i) {
				urls.push(this.imageUrls[i]);
			}

			// The first images is always the first in this array 
			this.listeUrls.push(this.imageUrls[0]);

			while (urls.length > 0) {
				var randInd = this.randomInt(0, urls.length);
				this.listeUrls.push(urls[randInd]);
				urls.remove(randInd);
			}
		}
	};

	this.setRandomPairImages = function()
	{
		if (this.listeUrls.length == 0) {
			this.fillListUrl();
		}

		if (this.pairImages.length == 0) {
			this.imagesLoaded = 0;
			this.pairImages.push(this.getImageFromUrl(this.listeUrls[0], 0));
			this.pairImages.push(this.getImageFromUrl(this.listeUrls[1], 1));
			this.listeUrls.remove(0);
		} else {
			this.imagesLoaded = 1;
			this.pairImages[0] = this.pairImages[1]; // Swap
			this.pairImages[1] = this.getImageFromUrl(this.listeUrls[0], 999);
		}
		this.listeUrls.remove(0);

		// Todo preload the next image
	};

	this.setRandomSquareEffect = function()
	{
		this.squareEffects.length = 0;
		for (var i = 0; i < this.squareCountX; ++i) {
			for (var j = 0; j < this.squareCountY; ++j) {
				var fullEffect = this.effects[this.randomInt(0, this.effects.length)].copy();
				fullEffect.x = i;
				fullEffect.y = j;
				this.squareEffects.push(fullEffect);				
			}			
		}
	};	
		
	this.loopCallback = function()
	{
		var self = incMosaic;

		if (self.imagesLoaded === 2) {
			// Redraw
			self.redraw();
		} else if (self.imageLoadedIndex === 0) {
			// Draw first image
			self.drawFirstImage();
			self.imageLoadedIndex = -1;
		}

		// Loop
		requestAnimationFrame(self.loopCallback);
	};

	this.drawFirstImage = function()
	{
		// Draw the first image
		this.ctx.drawImage(this.pairImages[0], 0, 0, this.srcSquareWidth*this.squareCountX, this.srcSquareHeight*this.squareCountY, 0, 0, this.squareWidth*this.squareCountX, this.squareHeight*this.squareCountY);

		if (this.postFirstImageCallback !== null) {
			this.postFirstImageCallback();
			this.postFirstImageCallback = null;
		}
	};

	this.redraw = function()
	{
		// Get time
		var time = new Date().getTime();

		var timeToWait = (this.startTime + this.waitStartEffectTime)*this.effectSpeed;
		if (time < timeToWait || this.waitStartEffectGo) {
			this.drawFirstImage();
			return;
		}

		// Update effects
		var effectsContinue = false;
		for (var i = 0; i < this.squareEffects.length; ++i) {			
			// Update
			var fullEffect = this.squareEffects[i];
			effectsContinue = fullEffect.update(time) || effectsContinue;

			for (var j = 0; j < 2; ++j) {
				var effectInfo = fullEffect.effectInfos[j];
				if (effectInfo !== null) {
					// Draw square
					this.drawSquare(fullEffect, this.pairImages[j], effectInfo);	
				}
			}
		}

		if (!effectsContinue) {
			if (this.endEffecFunc !== undefined && this.endEffecFunc !== null) {
				// Call the end callback
				var func = this.endEffecFunc;
				if (!this.loop) {
					this.endEffecFunc = null;
				}
				func();
			}
			if (this.loop) {
				// The effect loop
				this.setNewEffect();
			}
		}	
	};

	this.drawSquare = function(fullEffect, image, effectInfo)
	{
		this.ctx.save();

		// Global alpha
		this.ctx.globalAlpha = effectInfo.alpha;

		// Compute src and dest square position
		var srcPosX = fullEffect.x * this.srcSquareWidth;
		var srcPosY = fullEffect.y * this.srcSquareHeight;		
		var destPosX = fullEffect.x * this.squareWidth;
		var destPosY = fullEffect.y * this.squareHeight;		

		// Draw colored rectangle
		this.ctx.fillStyle = "rgba(" + effectInfo.color.r + "," + effectInfo.color.v + "," + effectInfo.color.b + "," + effectInfo.color.a + ")";
		this.ctx.fillRect(destPosX, destPosY, this.squareWidth, this.squareHeight);

		// Draw image
		this.ctx.drawImage(image, srcPosX, srcPosY, this.srcSquareWidth, this.srcSquareHeight, destPosX, destPosY, this.squareWidth, this.squareHeight);

		this.ctx.restore();
	};

	this.registerEffects = function()
	{
		var count = 7;
		var color = 64;
		var speed = this.effectSpeed;

		// Create semi random effects
		var range1 = 3000 * speed;
		var range2 = 5000 * speed;
		this.registerEffectsTime(count, color, range1, range2);

		// Create semi random effects
		range1 = 7000 * speed;
		range2 = 15000 * speed;
		this.registerEffectsTime(count, color, range1, range2);

		// Create semi random effects
		range1 = 17000 * speed;
		range2 = 23000 * speed;
		this.registerEffectsTime(count, color, range1, range2);
	};

	this.registerEffectsTime = function(count, color, range1, range2)
	{
		for (var i = 0; i < count; ++i) {
			var time1 = this.randomInt(range1, range2);
			var time2 = this.randomInt(range1, range2);
			var c1 = this.randomInt(0, color);

			var effectParam1 = new IncEffectParams(new IncColor(c1, c1, c1, 0), new IncAnim(1, time1), 1, new IncAnim(-1, time1));
			var effect1 = new IncSquareEffect_Alpha(effectParam1, createjs.Ease.quadOut, createjs.Ease.quadOut);
			
			var effectParam2 = new IncEffectParams(new IncColor(c1, c1, c1, 1), new IncAnim(0, time2), 0, new IncAnim(1, time2));
			var effect2 = new IncSquareEffect_Alpha(effectParam2, createjs.Ease.quadIn, createjs.Ease.quadIn, time1 / 2.5);

			var fullEffect1 = new IncFullEffect(effect1, effect2, time1, time2);
			this.effects.push(fullEffect1);
		}
	};

	// --------------------------------------------------------------------------------------------------------------------
	// Tools
	// --------------------------------------------------------------------------------------------------------------------

	this.randomInt = function(min, max)
	{
		return Math.floor(this.randomFloat(min, max));
	};

	this.randomFloat = function(min, max)
	{
		return Math.random() * (max - min) + min;
	};

	this.getImageFromUrl = function(url, index)
	{
		var self = incMosaic;
		var image = new Image();
		var ind = index;

		image.onload = function() {

			self.imageLoadedIndex = ind;

			// When the first image is loaded we can get the image dimention and init the autoresize of the canvas
			if (self.imagesLoaded === 0) {

				// Set some image size related vars
				self.imageWidth = image.width;
				self.imageHeight = image.height;
				self.srcSquareWidth = image.width / self.squareCountX;
				self.srcSquareHeight = image.height / self.squareCountY;

				// Call the resize object
				if (incResize !== undefined) {
					incResize.resizeElements();
				}
			}			
			self.imagesLoaded += 1;
		};
		image.src = url;
		return image;
	};
}

// --------------------------------------------------------------------------------------------------------------------
// Effects
// --------------------------------------------------------------------------------------------------------------------

function IncColor(r, v, b, a)
{
	this.r = r;
	this.v = v;
	this.b = b;
	this.a = a;

	this.copy = function()
	{
		return new IncColor(this.r, this.v, this.b, this.a);
	};
}

function IncAnim(value, time)
{
	this.value = value;
	this.time = time;
}

function IncEffectInfo()
{
	this.color;
	this.alpha;
}

function IncEffectParams(color, colorAnim, alpha, alphaAnim)
{
	// Color
	this.color = color;
	this.colorAnim = colorAnim;

	// Alpha
	this.alpha = alpha;
	this.alphaAnim = alphaAnim;

	this.computeColorAnimValue = function(elapsedTime, easeFunc)
	{
		return this.computeAnimValue(this.colorAnim, elapsedTime, easeFunc);
	};

	this.computeAlphaAnimValue = function(elapsedTime, easeFunc)
	{
		return this.computeAnimValue(this.alphaAnim, elapsedTime, easeFunc);
	};

	this.computeAnimValue = function(anim, elapsedTime, easeFunc)
	{
		// Compute color alpha anim
		if (elapsedTime < anim.time) {
			return easeFunc(elapsedTime/anim.time) * anim.value;			
		}
		return anim.value;
	};	
}

function IncSquareEffect_Alpha(effectParms, tweenColorFunc, tweenAlphaFunc, waitTime)
{
	// Effect parameters
	this.effectParms = effectParms;

	// Tween functions
	this.tweenColorFunc = tweenColorFunc;
	this.tweenAlphaFunc = tweenAlphaFunc;

	// Time
	this.waitTime = (waitTime!==undefined) ? waitTime : 0; 

	this.update = function(elapsedTime)
	{
		var info = new IncEffectInfo();

		// Compute new color
		var newColorValue = this.effectParms.computeColorAnimValue(elapsedTime, this.tweenColorFunc);
		info.color = this.effectParms.color.copy();
		info.color.a += newColorValue;

		// Compute alpha anim
		var newAlphaValue = this.effectParms.computeAlphaAnimValue(elapsedTime, this.tweenAlphaFunc);
		info.alpha = this.effectParms.alpha;
		info.alpha += newAlphaValue;

		return info;
	};
}

function IncFullEffect(effect1, effect2, totalTime1, totalTime2)
{
	// Position
	this.x = 0;
	this.y = 0;

	// Effect	
	this.effects = [effect1, effect2];
	this.effectInfos = [null, null];

	// Time
	this.startTime = [0, 0];	
	this.totalTime = [totalTime1, totalTime2];	

	this.copy = function()
	{
		return new IncFullEffect(this.effects[0], this.effects[1], this.totalTime[0], this.totalTime[1]);
	};

	this.update = function(time)
	{
		if (this.startTime[0] === 0) {
			this.startTime[0] = time;
			this.startTime[1] = time;
			return true;
		}

		for (var i = 0; i < 2; ++i) {
			// If we are in the good time range we update the effect
			var waitTime = this.startTime[i] + this.effects[i].waitTime;
			var updateEffect = time < this.totalTime[i] + waitTime;
			if (time > waitTime && updateEffect) {
				this.effectInfos[i] = this.effects[i].update(time - waitTime);
			}

			if (i == 1 && !updateEffect) {
				// The effect is done
				return false;
			}
		}

		return true;
	};	
}

// --------------------------------------------------------------------------------------------------------------------
// Tools
// --------------------------------------------------------------------------------------------------------------------

window.requestAnimationFrame = (function() {              
	return window.requestAnimationFrame    ||  	// Chromium 
		window.webkitRequestAnimationFrame ||  	// Webkit
		window.mozRequestAnimationFrame    || 	// Mozilla Geko
		window.oRequestAnimationFrame      || 	// Opera Presto
		window.msRequestAnimationFrame     || 	// IE Trident?
		function(callback, element){ 			// Fallback function
		   window.setTimeout(callback, 20);                
		};    
})();

Array.prototype.remove = function(from, to) {
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

var incMosaic = new IncMosaic();

