function IncMosaic()
{
	// --------------------------------------------------------------------------------------------------------------------
	// Members
	// --------------------------------------------------------------------------------------------------------------------
	
	// Canvas
	this.canvas;
	this.context;

	/// Images
	this.pairUrl = [];
	this.pairImages = [];
	this.imagesLoaded = 0;

	// Effect
	this.countX;
	this.countY;
	this.squareX;
	this.squareY;
	this.effects = [];
	this.squareEffects = [];

	this.noEffectinfo = new IncEffectInfo();
	this.noEffectinfo.color = new IncColor(255, 255, 255, 1)
	this.noEffectinfo.alpha = 1;

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

	this.addPairImages = function(url1, url2)
	{
		var urls = new IncPairUrl(url1, url2);
		this.pairUrl.push(urls);
	};

	this.start = function(canvasId, countX, countY)
	{
		// Init the canvas objects
		this.init(canvasId);

		// Register effects
		this.registerEffects();

		// Count, square`
		this.countX = countX;
		this.countY = (countY === undefined) ? countX: countY;
		this.squareX = Math.floor(this.canvas.width / this.countX);
		this.squareY = Math.floor(this.canvas.height / this.countY);

		// Set a random pair images
		this.setRandomPairImages();

		// Set random effect on the squares
		this.setRandomSquareEffect();
										
		// Main loop
		this.loopCallback();
	};

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

	this.registerEffects = function()
	{
		// No effect 
		//-----------------------------------
		var noEffect = new IncSquareEffect_NoEffet()
		var fullEffect0 = new IncFullEffect(0, 0, noEffect);
		this.effects.push(fullEffect0);

		// Alpha 
		//-----------------------------------
		{
			var effectParam1 = new IncEffectParams(new IncColor(255, 255, 255, 0), new IncAnim(1, 5000), 1, new IncAnim(-1, 5000), 8000);
			var effect1 = new IncSquareEffect_Alpha(effectParam1, createjs.Ease.linear, createjs.Ease.linear);
			var effectParam2 = new IncEffectParams(new IncColor(255, 255, 255, 1), new IncAnim(0, 5000), 0, new IncAnim(1, 5000), 8000);
			var effect2 = new IncSquareEffect_Alpha(effectParam2, createjs.Ease.linear, createjs.Ease.linear, 2500);
			var fullEffect1 = new IncFullEffect(1, 1, effect2, effect1);
			this.effects.push(fullEffect1);
		}

		// Alpha 2
		//-----------------------------------
		{
			var effectParam1 = new IncEffectParams(new IncColor(32, 32, 32, 0), new IncAnim(1, 5000), 1, new IncAnim(-1, 5000), 8000);
			var effect1 = new IncSquareEffect_Alpha(effectParam1, createjs.Ease.linear, createjs.Ease.linear);
			var effectParam2 = new IncEffectParams(new IncColor(32, 32, 32, 1), new IncAnim(0, 5000), 0, new IncAnim(1, 5000), 8000);
			var effect2 = new IncSquareEffect_Alpha(effectParam2, createjs.Ease.linear, createjs.Ease.linear, 2500);
			var fullEffect1 = new IncFullEffect(1, 1, effect2, effect1);
			this.effects.push(fullEffect1);
		}
	}

	this.setRandomPairImages = function()
	{
		this.imagesLoaded = 0;
		var randInd = this.randomInt(0, this.pairUrl.length);
		var pairUrl = this.pairUrl[randInd];
		this.pairImages.push(this.getImageFromUrl(pairUrl.imageUrl1));
		this.pairImages.push(this.getImageFromUrl(pairUrl.imageUrl2));
	};

	this.setRandomSquareEffect = function()
	{
		for (var i = 0; i < this.countX; ++i) {
			for (var j = 0; j < this.countY; ++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) {
			// Images are not loaded yet
			requestAnimationFrame(self.loopCallback);
			return;
		}

		// Clear canvas
		self.ctx.fillStyle = "#ffffff";
		self.ctx.fillStyle = "#000000";
		self.ctx.fillRect(0, 0, self.canvas.width, self.canvas.height);	

		// Get time
		var time = new Date().getTime();

		// Update effect
		for (var i = 0; i < self.squareEffects.length; ++i) {
			var fullEffect = self.squareEffects[i];

			for (var j = 0; j < 2; ++j) {
				var effect = fullEffect.effects[j];
				if (effect !== undefined) {
					// Draw square
					self.drawSquare(fullEffect, self.pairImages[j], effect, time);									
				}
			}
		}

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


	this.drawSquare = function(fullEffect, image, effect, time)
	{
		// Update effect
		var effectInfo = effect.update(fullEffect, time);

		if (effectInfo === null) {
			return;
		}

		// Compute square position
		var posX = fullEffect.x * this.squareX;
		var posY = fullEffect.y * this.squareY;

		this.ctx.save();

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

		// Draw colored rectangle
		this.ctx.fillStyle = "rgba(" + effectInfo.color.r + "," + effectInfo.color.v + "," + effectInfo.color.b + "," + effectInfo.color.a + ")";
		this.ctx.fillRect(posX, posY, this.squareX, this.squareY);

		// Draw image
		this.ctx.drawImage(image, posX, posY, this.squareX, this.squareY, posX, posY, this.squareX, this.squareY);

		this.ctx.restore();
	};

	// --------------------------------------------------------------------------------------------------------------------
	// 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)
	{
		var image = new Image();
		image.onload = function() {
			incMosaic.imagesLoaded += 1;
		};
		image.src = url;
		return image;
	};
}

function IncPairUrl(url1, url2)
{
	this.imageUrl1 = url1;
	this.imageUrl2 = url2;
}

// --------------------------------------------------------------------------------------------------------------------
// 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, time)
{
	// Color
	this.color = color;
	this.colorAnim = colorAnim;

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

	// Time
	this.time = time;

	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_NoEffet()
{
	this.update = function(fullEffect, time)
	{
		return incMosaic.noEffectinfo;
	}
}

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) ? (new Date().getTime() + waitTime) : 0; 

	this.update = function(fullEffect, time)
	{
		if (this.waitTime > time) {
			return null;
		}

		if (fullEffect.startTime == 0) {
			fullEffect.startTime = time;			
		}

		var elapsedTime = time - fullEffect.startTime;		
		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(x, y, effect1, effect2)
{
	// Position
	this.x = x;
	this.y = y;

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

	// Time
	this.startTime = 0;	

	this.copy = function()
	{
		return new IncFullEffect(this.x, this.y, this.effects[0], this.effects.length > 1 ? this.effects[1] : undefined);
	};	
}

// --------------------------------------------------------------------------------------------------------------------
// 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);                
		};    
})();

var incMosaic = new IncMosaic();

