/*jPuzzle
jQuery plugin (http://jquery.com/)
jQuery => Copyright 2010, John Resig
jPuzzle => Copyright 2010, Adrien Guéret
Dual licensed under the MIT or GPL Version 2 licenses.
http://jquery.org/license
Last update: 26/07/2011*/
(function($)
{
	$.fn.jPuzzle=function(options)
	{
		var defaults=
		{
			'rows': 5,
			'columns': 5,
			'help': true,
			'fixed': true,
			'showHelpButton': true,
			'textPlayButton': 'Play !',
			'textHelpButtonShow': 'Show final image',
			'textHelpButtonHide': 'Hide final image',
			'onstart': null,
			'onend': null
		};
		
		var parameters=$.extend(defaults,options);
		
		parameters.rows=Math.max(1,parameters.rows);
		parameters.columns=Math.max(1,parameters.columns);
		
		//Format time in seconds to HH"MM'SS
		function formatTime(s)
		{
			s=parseInt(s,10);
			
			var h=Math.floor(s/3600);
			s-=h*3600;
			var formatTime=(h>9?h:'0'+h)+'"';
			
			var m=Math.floor(s/60);
			s-=m*60;
			formatTime+=(m>9?m:'0'+m)+'\'';
			
			return formatTime+((s>9?s:'0'+s));
		}
		
		//Function waiting for the loading of the image
		function loading()
		{
			if($(this).is('img'))
			{
				//If the image is not loaded, we have to wait for it
				if(this.complete)
				{
					init($(this));
				}
				else
				{
					$(this).load(function()
					{
						init($(this));
					});
				}
			}
		}
		
		function init(obj)
		{
			//We store the image dimensions...
			var width=obj.width();
			var height=obj.height();
			
			//... and the source of the image
			var src=obj.attr('src');
			
			//We calculate the pieces dimensions
			var pieceWidth=width/parameters.columns;
			var pieceHeight=height/parameters.rows;
			
			//And the height of the box containing all the pieces
			var sizeBox=pieceHeight>height/2?pieceHeight:height/2;
			
			//The main container
			var game=$('<span></span>').css(
			{
				'width': width+'px',
				'display': 'inline-block',
				'position': 'relative'
			}).addClass('jPuzzle-game').insertAfter(obj);
			
			//The tag containing the puzzle
			var puzzle=$('<span></span>').css(
			{
				'width': width+'px',
				'height': height+'px',
				'display': 'inline-block',
				'position': 'relative'
			}).addClass('jPuzzle-container').appendTo(game);
			
			//The tag displaying the image as a background
			var image=$('<span></span>').css(
			{
				'width': width+'px',
				'height': height+'px',
				'opacity': 1,
				'background-image': 'url("'+src+'")',
				'display': 'inline-block'
			}).addClass('jPuzzle-image').appendTo(puzzle);
			
			//Box where the pieces will be generated
			var box=$('<span></span>').css(
			{
				'width': width+'px',
				'minHeight': sizeBox+'px',
				'display': 'inline-block',
				'position': 'relative'
			}).addClass('jPuzzle-box').droppable(
			{
				'accept': '.jPuzzle-piece',
				'addClasses': false,
				'drop': function(event,ui)
				{
					var piece=ui.draggable;
					var lastLocation=$.data(piece[0],'lastLocation');
					if(lastLocation[0]!=this)
					{
						/* If the piece comes from a puzzle location,
						we have to adjust its position */
						piece.appendTo($(this));
						piece.css(
						{
							'left': '+='+lastLocation.position().left,
							'top':(piece.position().top+lastLocation.position().top-puzzle.outerHeight())+'px'
						});
						$.data(piece[0],'lastLocation',$(this));
					}
				}
			}).hide().insertAfter(puzzle);
			
			//Foot bar
			var infos=$('<span></span>').css(
			{
				'width': width+'px',
				'display': 'inline-block'
			}).addClass('jPuzzle-infos').insertAfter(box);
			
			//The data-time attribute will contain the elapsed time
			var infosTime=$('<span></span>').addClass('jPuzzle-time').attr('data-time',0).hide().appendTo(infos);
			
			var playButton=$('<input type="button" />').val(parameters.textPlayButton).addClass('jPuzzle-playButton').click(function()
			{
				startGame();
				$(this).hide();
			}).appendTo(infos);
			
			if(parameters.showHelpButton)
			{
				var helpButton=$('<input type="button" />').val(parameters.textHelpButtonShow).addClass('jPuzzle-helpButton').toggle(function()
				{
					//The final image is not displayed, we show it!
					$(this).val(parameters.textHelpButtonHide).attr('disabled',true);
					image.animate(
					{
						'opacity': 1
					},'slow',function()
					{
						helpButton.removeAttr('disabled');
					});
					
					//We hide the pieces of the puzzle to avoid the cheating
					$('.jPuzzle-piece').hide();
				},function()
				{
					//The final image is displayed, we hide it!
					$(this).val(parameters.textHelpButtonShow).attr('disabled',true);
					
					image.animate(
					{
						'opacity': (parameters.help?0.1:0)
					},'slow',function()
					{
						helpButton.removeAttr('disabled');
					});
					
					$('.jPuzzle-piece').show();
				}).hide().appendTo(infos);
			}
			
			//And we remove the initial image!
			obj.remove();
			
			var timer;
			function startGame()
			{
				if(parameters.onstart)
				{
					parameters.onstart(game);
				}
				
				box.show();
				infosTime.show();
				infosTime.text('00"00\'00');
				
				if(parameters.showHelpButton)
				{
					helpButton.show();
				}
				
				image.animate(
				{
					'opacity': (parameters.help?0.1:0)
				},'slow');
				
				for(var i=0;i<parameters.rows;i++)
				{
					for(var j=0;j<parameters.columns;j++)
					{
						var maxX=width-pieceWidth;
						var maxY=sizeBox-pieceHeight;
						
						var piece=$('<span></span>').addClass('jPuzzle-piece')
						.attr('data-num',i+'_'+j).css(
						{
							'width': pieceWidth+'px',
							'height': pieceHeight+'px',
							'display': 'block',
							'left': Math.floor((Math.random()*(maxX+1)))+'px',
							'top': Math.floor((Math.random()*(maxY+1)))+'px',
							'z-index': Math.floor((Math.random()*(10))+1),
							'background-image': 'url("'+src+'")',
							'cursor': 'move',
							'background-position': (-j*pieceWidth)+'px '+(-i*pieceHeight)+'px',
							'position': 'absolute'
						}).draggable(
						{
							'start': function(event,ui)
							{
								/* If the piece is already on its
								good location, we stop the dragging */
								if($.data(this,'ok') && parameters.fixed)
								{
									return false;
								}
								
								//Otherwise, we put the piece in front of the others
								var zIndex=0;
								game.find('.jPuzzle-piece').each(function()
								{
									var x=parseInt($(this).css('z-index'));
									zIndex=Math.max(x,zIndex);
								});				
								$(this).css('z-index',zIndex+1);
							},
							'revert': 'invalid',
							'containment': game
						}).appendTo(box);
						
						$.data(piece[0],'ok',false);
						$.data(piece[0],'lastLocation',box);
						
						var location=$('<span></span>').addClass('jPuzzle-location')
						.attr('data-num',i+'_'+j).css(
						{
							'width': pieceWidth+'px',
							'height': pieceHeight+'px',
							'left': j*pieceWidth+'px',
							'top': i*pieceHeight+'px',
							'display': 'block',
							'position': 'absolute'
						}).droppable(
						{
							'accept': function(ui)
							{
								/* We accept only puzzle pieces, and only if the location is empty
								We also check if the piece and the location are in the same puzzle game */
								return (ui.hasClass('jPuzzle-piece') && $(this).is(':empty') && $(this).parents('.jPuzzle-game')[0]==ui.parents('.jPuzzle-game')[0]);
							},
							'addClasses': false,
							'hoverClass': 'jPuzzle-location-hover',
							'drop': function(event,ui)
							{
								var obj=$(this);
													
								ui.draggable.appendTo(obj);
								$.data(ui.draggable[0],'lastLocation',obj);
														
								ui.draggable.css(
								{
									'left': '0px',
									'top': '0px'
								});
								
								/* If piece and location have the same data-num attribute,
								then the piece is correctly placed !*/
								$.data(ui.draggable[0],'ok',false);
								if(obj.attr('data-num')==ui.draggable.attr('data-num'))
								{
									$.data(ui.draggable[0],'ok',true);
									
									/* If we can't remove a piece which is correctly
									placed, we can apply a small effect on it*/
									if(parameters.fixed)
									{
										/* We modify the cursor to indicate
										the piece is not longer draggable */
										ui.draggable.css('cursor','default');
										
										/* This span will add a "flash
										effect" on the piece */
										$('<span></span>').css(
										{
											'position': 'absolute',
											'left': '0px',
											'top': '0px',
											'display':'block',
											'width': '100%',
											'height': '100%',
											'background-color': '#fff'
										}).appendTo(ui.draggable)
										.fadeOut(2000,function()
										{
											$(this).remove();
										});
									}
									
									//The player has correctly placed one piece: what about the others?
									var total=0;
									game.find('.jPuzzle-piece').each(function()
									{
										if(!$.data(this,'ok'))
										{
											return false;
										}
										total++;
									});
									if(total==parameters.rows*parameters.columns)
									{
										endGame();
									}
								}
							}
						}).appendTo(puzzle);
					}
				}
				
				timer=setInterval(function()
				{
					infosTime.attr('data-time',parseInt(infosTime.attr('data-time'),10)+1);
					infosTime.text(formatTime(infosTime.attr('data-time')));
				},1000);
			}
			
			function endGame()
			{
				//We stop the timer
				clearInterval(timer);
				var time=parseInt(infosTime.attr('data-time'),10);
					
				//We set the puzzle to its initial state
				box.hide();
				game.find('.jPuzzle-piece,.jPuzzle-location').remove();
				image.css('opacity','1');
				if(parameters.showHelpButton)
				{
					helpButton.hide();
				}
				infosTime.hide().attr('data-time',0);
				playButton.show();
				
				//We eventually call a function defined by the user
				if(parameters.onend)
				{
					//We send to this function three useful variables
					parameters.onend(game,time,formatTime(time));
				}
			}
		}
		
		return this.each(loading);
	};
})(jQuery);