/**
 * @author Omer Gertel
 * All rights reserved
 */

(function( $ ){

function imageLoad(img, w, h)
{
	if (img)
	{
		var canvas = document.createElement("canvas");
		var pic = new Image();
				
		pic.src = img.src;
		canvas.width = w;
		canvas.height = h;
		var ctx = canvas.getContext('2d');
		ctx.drawImage(pic, 0, 0, w, h);
		
		return ctx;
	}
	return null;
};

function getColor(ctx, x, y, w, h) 
{			
	var jumpSize = 5;				
	var data =  ctx.getImageData(x, y ,w, h).data;	
	var res = {r:0,g:0,b:0};
 
	//sum all channels values
	for (var i = 0; i < data.length; i += 4*jumpSize	) 
	{
		res.r += data[i];
    	res.g += data[i + 1];
    	res.b += data[i + 2];	   
	}
	 
	//calculate average color for each channel
	res.r = Math.round(res.r/ (data.length / 4 / jumpSize));
	res.g = Math.round(res.g/ (data.length / 4 / jumpSize));
	res.b = Math.round(res.b/ (data.length / 4 / jumpSize));
			
	return res;
};

function metric(c1, c2, width, height)
{
	var diff = 0;	
	for (var j=0;j<height;j++)
	{			
		for (var i=0;i<width;i++)
		{
			index = (j*width + i)*4; // r, g, b, alpha
			diff += Math.pow(c2[index]-c1[index],2);
			diff += Math.pow(c2[index+1]-c1[index+1],2);
			diff += Math.pow(c2[index+2]-c1[index+2],2);
		}
	}
	return diff;
};

function calcTinyImages(imgs, ctx, w, h, grainSize)
{
	var fit = new Array();
	var images = new Array();
	
	var jw = 0;
	var jg = 0;
	var height = 0;
	var width = 0;
	
	for (var j = 0; j < h ; j++ )
	{
		jw = j*w;
		for(var i = 0; i < w; i++)
		{
			fit[jw+i] = null;
			images[jw+i] = null;
		}
	}
	
	for(var index = 0; index< imgs.length;index++)
	{
		var smallImg = imgs[index];
		var smallCtx = imageLoad(smallImg, grainSize, grainSize);	
		var smallData = smallCtx.getImageData(0,0,grainSize, grainSize);
		
		for (var j = 0; j < h ; j++ )
		{
			jw = j*w;
			jg = j*grainSize;
			height = grainSize;
			if (height + jg > ctx.canvas.height)
			{
				height = ctx.canvas.height- jg;
			}
			for(var i = 0; i < w; i++)
			{
				width = grainSize;
				if (width + i*grainSize > ctx.canvas.width)
				{
					width = ctx.canvas.width - i*grainSize;
				}
		
				var data =  ctx.getImageData(i*grainSize, jg ,width, height).data;
				var diff = metric(smallData.data, data, width, height);	
				if(!fit[jw+i] || diff < fit[jw+i])
				{
					fit[jw+i] = diff;
					images[jw+i]  = smallImg;
				}
			}
		}
		
		smallCtx.canvas.width = 0;
	}
		
	return images;
};

function fill(images, workspace, ctx, w, h, grainSize)
{
	var jw = 0;
	var jg = 0;
	var height = 0;
	var width = 0;
	
	for (var j = 0; j < h ; j++ )
	{
		jw = j*w;
		jg = j*grainSize;
		height = grainSize;
		
		if (height + jg > workspace.height)
		{
			height = workspace.height- jg;
		}
		for(var i = 0; i < w; i++)
		{
			width = grainSize;
			if (width + i*grainSize > workspace.width)
			{
				width = workspace.width - i*grainSize;
			}
			
			if(images[jw+i])
			{
				var smallImg = images[jw+i];
				var smallCtx = imageLoad(smallImg, grainSize, grainSize);	
				var smallData = smallCtx.getImageData(0,0,grainSize, grainSize);
		
				workspace.putImageData(smallData, i*grainSize, jg);
				
				var color = getColor(ctx, i*grainSize, jg, width, height);
				workspace.fillStyle="rgba("+color.r + "," +color.g + "," +color.b + "," + 0.5 +")";
				workspace.fillRect(i*grainSize, jg, width, height);
			}
			else
			{
				var color = getColor(ctx, i*grainSize, jg, width, height);
				workspace.fillStyle="rgba("+color.r + "," +color.g + "," +color.b + ")";
				workspace.fillRect(i*grainSize, jg, width, height);
			}
		}
	}
}

$.fn.mosaic = function(options)
{
	
	var settings = $.extend({
        grainSize: 10,
        canvas:"canvas",
        images:[],
        onmouseover:null,
        onmouseleave:null
    }, options);

	return this.each(function()
		{
			var w = this.width;
    		var h = this.height;
    		var grainSize = settings.grainSize;
    		var ctx = imageLoad(this, w, h);
    		
    		var workspace = $(settings.canvas)[0];
    		workspace.width = w;
    		workspace.height = h;
    		workspace = workspace.getContext("2d");
    		    		
    		w = Math.ceil(w / grainSize);
			h = Math.ceil(h / grainSize);
			
			var images = [];			
			if(settings.images.length>0)
			{
				images = calcTinyImages(settings.images, ctx, w, h, grainSize);				
			}
			
			fill(images, workspace, ctx, w, h, grainSize);
			
    		if(settings.onmouseover)
			{	    				
    			$(settings.canvas).mousemove(images, function(e)
    			{
    				var offset = $(this).offset();
    				if(offset.left<e.pageX && e.pageX < offset.left + this.width && offset.top <e.pageY && e.pageY < offset.top+this.height)
    				{
    					var i = ~~((e.pageX-offset.left)/settings.grainSize);
    					var j = ~~((e.pageY-offset.top)/settings.grainSize); 
    					settings.onmouseover(e.data[~~(j*w+i)], e.pageX, e.pageY);
    				}
    			});
    		}
    		
    		if(settings.onmouseleave)
    		{
    			$(settings.canvas).mouseleave(function(){settings.onmouseleave()});
    		}
    		
    		ctx.canvas.width = 0;
    	});
};
})(jQuery);

