(function($) {

/*

 * jqPuzzle - Sliding Puzzles with jQuery

 * Version 1.02

 * 

 * Copyright (c) 2008 Ralf Stoltze, http://www.2meter3.de/jqPuzzle/

 * Dual-licensed under the MIT and GPL licenses.

 */

$.fn.jqPuzzle = function(settings, texts) {



	// default settings

	var defaults = {

		

		rows: 2, 					// number of rows [3 ... 9]

		cols: 2,		 			// number of columns [3 ... 9]

		hole: 4,					// initial hole position [1 ... rows*columns]

		shuffle: true,				// initially show shuffled pieces [true|false]

		numbers: true,				// initially show numbers on pieces [true|false]

		language: 'en',				// language for gui elements [language code]

		

		// display additional gui controls

		control: {

			shufflePieces: true,	// display 'Shuffle' button [true|false]

			confirmShuffle: true,	// ask before shuffling [true|false]

			toggleOriginal: true,	// display 'Original' button [true|false]

			toggleNumbers: true,	// display 'Numbers' button [true|false]

			counter: true,			// display moves counter [true|false]

			timer: true,			// display timer (seconds) [true|false]

			pauseTimer: false		// pause timer if 'Original' button is activated 

									// [true|false]

		},

		

		// perform actions when the puzzle is solved sucessfully

		success: {

			fadeOriginal: true,		// cross-fade original image [true|false]

			callback: undefined,	// callback a user-defined function [function]

									// the function is passed an object as its argument

									// which includes the fields 'moves' and 'seconds'

			callbackTimeout: 300	// time in ms after which the callback is called

		},		

		

		// animation speeds and settings

		animation: {

			shuffleRounds: 3,		// number of shuffle rounds [1 ... ]

			shuffleSpeed: 800,		// time in ms to perform a shuffle round

			slidingSpeed: 200,		// time in ms for a single move

			fadeOriginalSpeed: 600	// time in ms to cross-fade original image

		},

		

		// additional style information not specified via css

		style: {

			gridSize: 2,			// space between two pieces in px

			overlap: true,			// if true, adjacent piece borders will overlap

									// applies only if gridSize is set to 0

			backgroundOpacity: 0.1	// opacity of the original image behind the pieces

									// [0 ... 1] (0 means no display)

		}

	};

	

	// language localizations

	var i18n = {

		en: {

			shuffleLabel: 			'mezclar',

			toggleOriginalLabel: 	'original',

			toggleNumbersLabel: 	'números',

			confirmShuffleMessage: 	'Realmente quieres mezclar?',

			movesLabel:				'movimientos',

			secondsLabel:			'segundos'

		},

		fr: {

			shuffleLabel: 			'Mélanger',

			toggleOriginalLabel: 	'Original',

			toggleNumbersLabel: 	'Nombres',

			confirmShuffleMessage: 	'Veux-tu vraiment mélanger?',

			movesLabel:				'mouvements',

			secondsLabel:			'secondes'

		},

		de: {

			shuffleLabel:			'Mischen',

			toggleOriginalLabel: 	'Original',

			toggleNumbersLabel: 	'Nummern',

			confirmShuffleMessage: 	'Willst du wirklich mischen?',

			movesLabel:				'Züge',

			secondsLabel:			'Sekunden'

		},

		pt: {

			shuffleLabel:			'Embaralhar',

			toggleOriginalLabel:	'Original',

			toggleNumbersLabel:		'Numeros',

			confirmShuffleMessage:	'Tem certeza que deseja reembralhar?',

			movesLabel:				'movimentos',

			secondsLabel:			'segundos'

		}

	};

	

	// if rows or cols, but no hole was user-defined,

	// explicitly set hole position to last piece (bottom right)

	if(settings && !settings.hole && (settings.rows || settings.cols)) {

		settings.hole = (settings.rows || defaults.rows) * (settings.cols || defaults.cols);

	}



	// extend the user-defined settings object with default settings

	settings = $.extend(true, {}, defaults, settings);



	// extend the user-defined texts object with current language texts

	texts = $.extend((i18n[settings.language] || i18n[defaults.language]), texts);

	

	// create some handy shortcut variables

	var rows = settings.rows, 

		cols = settings.cols, 

		hole = settings.hole;

	var control = settings.control,

		success = settings.success,

		animation = settings.animation,

		style = settings.style;



	// check settings for valid values



	// keep rows and columns within limits

	if(rows < 3 || rows > 9) rows = defaults.rows;

	if(cols < 3 || cols > 9) cols = defaults.rows;



	// keep hole position within limits

	if((hole > (rows*cols)) || (hole < 1)) hole = rows*cols;

	hole--; // zero-based index

	

	// animation speed = 0 doesn't work

	if(animation.slidingSpeed < 1) animation.slidingSpeed = 1;

	if(animation.shuffleSpeed < 1)  animation.shuffleSpeed = 1;

	if(animation.fadeOriginalSpeed < 1) animation.fadeOriginalSpeed = 1;

	

	// keep shuffle rounds within limits

	if(animation.shuffleRounds < 1) animation.shuffleRounds = 1;





	// helper functions --------------------------------------------------------

	

	// checks if the puzzle is solved

	var checkSolution = function($pieces) {

		// iterate over pieces and check each piece

		for(var i = 0; i < $pieces.length; i++) {

			// since the hole is not saved in the pieces array, 

			// adjust the index if it is bejond the hole position

			var pieceIndex = (i < hole) ? i : i + 1;

			

			// check if current position match target (index) position

			if($pieces.eq(i).attr('current') != pieceIndex) return false;

		}

		return true;

	};

	

	// checks if the puzzle can be solved (pure math ...)

	var checkOrder = function(numbersArray) {

		var product = 1;

		for(var i = 1; i <= (rows*cols-1); i++) {

			for(var j = (i+1); j <= (rows*cols); j++) {

				product *= ((numbersArray[i-1] - numbersArray[j-1]) / (i-j));

			}

		}

		return Math.round(product) == 1;

	};

	

	// get the linear index from a row/col pair (zero-based)

	var getLinearPosition = function(row, col) {

		return parseInt(row)*cols + parseInt(col);

	};

	

	// get the row/col pair from a linear index (zero-based)

	var getMatrixPosition = function(index) {

		return {row: (Math.floor(index/cols)), col: (index%cols)};

	};

	

	// get the pixel width of a border (internet explorer returns keywords)

	// the left side values will be used

	var getBorderWidth = function($element) {

		// the reported css value

		var property = $element.css('border-left-width');

		// a border style must be set to get a valid border width

		if($element.css('border-left-style') != 'none') {

			switch(property) {

				case 'thin': return 2;

				case 'medium': return 4;

				case 'thick': return 6;

				default:

					return parseInt(property) || 0;	// parse pixel value

			}

		}

		return 0;

	};



	// a reusable timer component

	// pass in a timeout interval in ms, after which callback is called

	// callback gets passed one argument, the elapsed time is ms

	var Timer = function(interval, callback) {

		var startTime;

		var startPauseTime;

		var totalPause = 0;

		var timeout;

		

		var run = function() {	

			update(new Date().getTime());

			timeout = setTimeout(run, interval);

		};

		

		var update = function(now) {

			callback(now - totalPause - startTime);

		};



		// start the timer

		this.start = function() {

			if(startTime) return false;

			startTime = new Date().getTime();

			run();

		};



		// stop the timer

		this.stop = function() {

			if(!startTime) return false;

			clearTimeout(timeout);

			var now = new Date().getTime();

			if(startPauseTime) totalPause += now - startPauseTime;

			update(now);

			startTime = startPauseTime = undefined;

			totalPause = 0;

		};



		// pause the timer

		this.pause = function() {

			if(!startTime || startPauseTime) return false;

			clearTimeout(timeout);

			startPauseTime = new Date().getTime();

		};



		// resume the timer

		this.resume = function() {

			if(!startPauseTime) return false;

			totalPause += new Date().getTime() - startPauseTime;

			startPauseTime = undefined;

			run();

		};

	};





	// apply jqPuzzle to each image element within selection -------------------



	return this.filter('img').each(function(){

		var $srcImg = $(this);			// source image as jQuery object

		var lock = false;				// flag if animations are running

		var moves = 0;					// counter for single moves

		var seconds = 0;				// counter for seconds after first move

		var solved;						// flag if the puzzle is solved by the user

		var shuffled = settings.shuffle;// flag if the puzzle was shuffled

		var timer;						// a timer component

					

		// save the current hole position for further manipulation

		var currHole = hole;

		

		

		// create dummy elements to get computed css properties

		var $dummyPiece = $('<div/>').addClass('jqp-piece');

		var $dummyWrapper = $('<div/>').addClass('jqp-wrapper').append($dummyPiece);

		var $dummyGui = $('<div/>')

			.attr('class', $srcImg.attr('class') || '') // transfer classes

			.addClass('jqPuzzle')

			.append($dummyWrapper);		

	

		// replace original image with dummy

		$srcImg.replaceWith($dummyGui);

	

		// assign old image id to dummy

		$dummyGui.attr('id', $srcImg.attr('id') || '');

		

		// get computed css properties of dummy elements

		var computedStyles = {

			gui: {

				border: getBorderWidth($dummyGui),

				padding: {

					left: parseInt($dummyGui.css('padding-left')) || 0,

					right: parseInt($dummyGui.css('padding-right')) || 0,

					top: parseInt($dummyGui.css('padding-top')) || 0,

					bottom: parseInt($dummyGui.css('padding-bottom')) || 0

				}

			},

			wrapper: {

				border: getBorderWidth($dummyWrapper),

				padding: parseInt($dummyWrapper.css('padding-left')) || 0

			},

			piece: {

				border: getBorderWidth($dummyPiece)

			}

		};

		

		// re-replace dummy elements with original image

		$dummyGui.removeAttr('id');

		$dummyGui.replaceWith($srcImg);

		

		

		// wait for the image to be loaded, to be able to get its real width/height

		$srcImg.one('load', function() {



			// overlap piece borders if there is no margin between pieces

			// this way, piece borders will not be doubled

			var overlap = (style.gridSize === 0 && style.overlap);

					

			// total space of piece borders and grid lines, which will cover parts of the image

			var coveredWidth  = cols*(2*computedStyles.piece.border) + (cols-1)*style.gridSize;

			var coveredHeight = rows*(2*computedStyles.piece.border) + (rows-1)*style.gridSize;

			

			// recalc if overlap

			if(overlap) {

				coveredWidth  -= (cols-1)*computedStyles.piece.border;

				coveredHeight -= (rows-1)*computedStyles.piece.border;

			}



			// make sure to get the original image size, not scaled values

			// in mozilla, width() and height() do not work with hidden elements

			$srcImg.css({width: 'auto', height: 'auto', visibility: 'visible'}); 

			

			// pieces width and height, based on original image size

			var width  = Math.floor(($srcImg.width()-coveredWidth) / cols);

			var height = Math.floor(($srcImg.height()-coveredHeight) / rows);

			

			// reject too small images

			if(width < 30 || height < 30) return false;



			// recalc full image width and height to avoid rounding errors

			var fullWidth  = cols*width + coveredWidth;

			var fullHeight = rows*height + coveredHeight;



			// image source path

			var imgSrc = $srcImg.attr('src');

			

			// total width/height of a piece (including piece border and 1 grid size)

			var totalPieceWidth  = width + 2*computedStyles.piece.border + style.gridSize;

			var totalPieceHeight = height + 2*computedStyles.piece.border + style.gridSize;



			// handle internet explorer quirks mode box model

			var boxModelHack = {

				piece : $.boxModel ? 0 : 2*computedStyles.piece.border,

				wrapper: $.boxModel ? 0 : 2*(computedStyles.wrapper.border + computedStyles.wrapper.padding),

				gui: {

					width: $.boxModel ? 0 : 2*computedStyles.gui.border + 

						computedStyles.gui.padding.left + computedStyles.gui.padding.right,

					height: $.boxModel ? 0 : 2*computedStyles.gui.border + 

						computedStyles.gui.padding.top + computedStyles.gui.padding.bottom

				}

			};

			

			

			// helper functions ------------------------------------------------

			

			// pixel offset of an element, based on matrix position

			var getOffset = function(row, col) {

				var offset = {

					left: computedStyles.wrapper.padding + col*totalPieceWidth,

					top:  computedStyles.wrapper.padding + row*totalPieceHeight

				};

				

				if(overlap) {

					offset.left -= col * computedStyles.piece.border;

					offset.top  -= row * computedStyles.piece.border;

				}

				

				return offset;

			};



			// shuffle pieces

			var shuffle = function(rounds, speed) {

				

				// when speed is defined, the function was triggered by a user event (button click)

				if(speed) {

					// do nothing, if disabled

					if($shuffleButton.is('.jqp-disabled')) return false;



					// do nothing, if locked

					if(lock) return false;

					

					// ask for confirmation

					if(control.confirmShuffle && (moves > 0) && 

					!window.confirm(texts.confirmShuffleMessage)) return false;



					lock = true; // set lock

					

					// if the puzzle is solved

					if(solved) {

						// reset gui

						$gui.removeClass('jqp-solved');

						

						// fade out original

						$background.fadeTo(animation.fadeOriginalSpeed, style.backgroundOpacity, function() {

							// opera gets kicked without remove()

							$background.remove().prependTo($wrapper);



							// re-enable all buttons

							$buttons.removeClass('jqp-disabled');							

						});

					}

				}



				// stop the timer

				if(timer) timer.stop();



				// reset flag and counters

				solved = false;

				shuffled = true;

				moves = 0;

				seconds = 0;

				

				// reset display

				if($display) $display.removeClass('jqp-disabled');

				if($counter) $counter.val(moves);

				if($timer) $timer.val(seconds);

				

				var shuffles = []; 

				var i = 0;

				// generate orders for several shuffle rounds

				while(i < rounds) {				

					// create an array for choosing random positions

					// based on its lenght, we can select free positions

					var choices = [];

					for(var j = 0; j < rows*cols; j++) {

						choices[j] = j;

					}

					// remove element on initial hole position

					choices.splice(hole, 1);

					

					shuffles[i] = [];

					// generate random numbers

					for(var j = 0; j < rows*cols; j++) {

						

						// but keep hole at initial position

						if(j == hole) {

							shuffles[i][j] = hole;

							continue;

						}

						

						// select a random position based on the length of the choices

						var randomIndex = Math.floor(Math.random()*choices.length);

						

						// save the value at this index as the next number in the current order

						shuffles[i][j] = choices[randomIndex];

						

						// remove this value from the choices array (reducing its length)

						choices.splice(randomIndex, 1);

					}

					

					// don't increase i if we are in last round 

					// and the generated order is not solvable

					if(((i+1) < rounds) || checkOrder(shuffles[i])) i++;

				}



				var animCounter = 0; // animation counter for save unlock

				

				// shuffle pieces in several rounds

				for(var i = 0; i < rounds; i++) {



					// set flag for the last round

					var lastRound = ((i+1) == rounds);



					// iterate over the generated orders

					// with j being the linear index for the destination order

					for(var j = 0; j < shuffles[i].length; j++) {

						

						// we cannot move the hole

						if(j == hole) {

							// update hole position

							if(lastRound) currHole = hole;

							continue;

						}



						// the value is the index of the current piece

						// in the original, ordered $pieces array

						var pieceIndex = shuffles[i][j];



						// since the hole is not saved in the $pieces array, 

						// adjust the index if it is bejond the hole position

						if(pieceIndex > hole) pieceIndex -= 1;

						

						// get the actual piece to be moved

						var $piece = $pieces.eq(pieceIndex);



						// get target position

						var target = getMatrixPosition(j);



						// get pixel offset new position

						var offset = getOffset(target.row, target.col);

						

						// update current row/cal in last round	(ie needs a string)

						if(lastRound) $piece.attr('current', j.toString());

						

						// either just set or animate styles

						if(speed === undefined) {

							$piece.css({left: offset.left, top: offset.top});

						} else {

							// animate!

							$piece.animate({left: offset.left, top: offset.top}, speed, null, function() {

								// unlock after last animation in last round

								animCounter++;

								if(animCounter == animation.shuffleRounds*(rows*cols-1)) {

									lock = false;

									animCounter = 0;

								}

							});

						}

					}

				}

			};





			// create elements -------------------------------------------------



			// create a wrapper for the pieces

			var $wrapper = $('<div/>')

				.addClass('jqp-wrapper')

				.css({	

					width: fullWidth + boxModelHack.wrapper,

					height: fullHeight + boxModelHack.wrapper,

					borderWidth: computedStyles.wrapper.border,

					padding: computedStyles.wrapper.padding,

					margin: 0,

					position: 'relative',

					overflow: 'hidden',

					display: 'block',

					visibility: 'inherit'

				});



			// create a single piece prototype to be cloned for the actual pieces

			var $protoPiece = $('<div/>')

				.addClass('jqp-piece')

				.css({

					width: width + boxModelHack.piece,

					height: height + boxModelHack.piece,

					backgroundImage: 'url(' + imgSrc + ')',

					borderWidth: computedStyles.piece.border,

					margin: 0,

					padding: 0,

					position: 'absolute',

					overflow: 'hidden',

					display: 'block',

					visibility: 'inherit',

					cursor: 'default'

				})

				.append($('<span/>')); // will hold the numbers



			// create pieces inside wrapper

			var $pieces = $([]); // create an empty jQuery object

			for(var i = 0; i < rows; i++) {

				for(var j = 0; j < cols; j++) {

					var index = getLinearPosition(i,j); // linear index

					

					// do not create piece at initial hole position

					if(index == hole) continue;

						

					// get piece position offset

					var offset = getOffset(i,j);



					// calculate background offset

					var bgLeft = -1 * (j*totalPieceWidth + computedStyles.piece.border);

					var bgTop  = -1 * (i*totalPieceHeight + computedStyles.piece.border);



					// recalc if overlap

					if(overlap) {

						bgLeft += j*computedStyles.piece.border;

						bgTop  += i*computedStyles.piece.border;

					}



					// create single pieces from prototype

					$pieces = $pieces.add($protoPiece.clone()

						.css({

							left: offset.left,

							top: offset.top,

							backgroundPosition: (bgLeft + 'px ' + bgTop + 'px')

						})

						// add expando property to save the current position

						.attr('current', String(index)) // ie hack: String()

						.appendTo($wrapper)

						// add number to inner span

						.children().text(index + 1).end()

					);

				}

			}

	

			// initially shuffle pieces

			if(settings.shuffle) shuffle(1);

			

			// create background (original image) inside wrapper

			var $background = $('<div/>')

				.css({

					width: fullWidth,

					height: fullHeight,

					left: computedStyles.wrapper.padding,

					top: computedStyles.wrapper.padding,

					backgroundImage: 'url(' + imgSrc + ')',

					borderWidth: 0,

					margin: 0,

					padding: 0,

					position: 'absolute',

					opacity: style.backgroundOpacity

				})

				.prependTo($wrapper);

			

			// create controls which will hold the buttons and the display

			var $controls = $('<div/>')

				.addClass('jqp-controls')

				.css({

					visibility: 'inherit',

					display: 'block',

					position: 'static'

				});



			var $shuffleButton, $originalButton, $numbersButton;

			

			// create a button prototype to be cloned for the actual buttons

			var $protoButton = $('<a/>').css('cursor', 'default');



			// create shuffle button

			if(control.shufflePieces) {

				$shuffleButton = $protoButton.clone()

					.text(texts.shuffleLabel)

					.appendTo($controls);

			}



			// create toggle original button

			if(control.toggleOriginal) {

				$originalButton = $protoButton.clone()

					.text(texts.toggleOriginalLabel)

					.appendTo($controls);

			}



			// create toggle numbers button

			if(control.toggleNumbers) {

				$numbersButton = $protoButton.clone()

					.text(texts.toggleNumbersLabel)

					.appendTo($controls);

				// immediately toggle button, if numbers are initially shown

				if(settings.numbers) $numbersButton.addClass('jqp-toggle');		

			}

			

			// keep a reference to all buttons for convenience

			var $buttons = $controls.children();



			var $display, $counter, $timer;

			if(control.counter || control.timer) {



				// create wrapper for counter/timer

				$display = $('<span/>')

					.css('cursor', 'default')

					.appendTo($controls);

			

				// create a text field prototype to be cloned for actual text fields

				var $protoField = $('<input/>')

					.val(0)

					.css({

						width: '5ex',

						cursor: 'default'

					})

					.attr('readonly', 'readonly');



				// create counter component

				if(control.counter)	$counter = $protoField.clone()

					.appendTo($display)

					.after(texts.movesLabel + ' ');

					

				// create timer component

				if(control.timer) $timer = $protoField.clone()

					.appendTo($display)

					.after(texts.secondsLabel);



				// disable display if the puzzle is not shuffled yet

				if(!settings.shuffle) $display.addClass('jqp-disabled');

			}

			

			// add link to shopepk homepage

			var $credits = $('<a/>')

				.text('shopepk')

				.attr('href', 'http://www.shopepk.com/')

				.css({

					'float': 'right',

					fontFamily: 'Verdana, Arial, Helvetica, sans-serif',

					fontSize: '0px',

					lineHeight: '0px',

					textDecoration: 'none',

					color: '#FFFFFF',

					backgroundColor: '#777777',

					backgroundImage: 'none',

					borderBottom: '0px dotted #FFFFFF',

					padding: '0px 0px 0px',

					marginRight: computedStyles.wrapper.border,

					position: 'static',

					display: 'inline',

					visibility: 'inherit'

				});



			// panel which holds controls and credits, used for height() calculations

			var $panel = $('<div/>')

				.css({

					width: fullWidth + 2*(computedStyles.wrapper.padding + computedStyles.wrapper.border),

					position: 'absolute', 

					display: 'block',

					visibility: 'inherit',//'visible',

					margin: '0px',

					padding: '0px',

					backgroundColor: 'transparent'

				})

				.append($credits).append($controls);



			// full gui (including wrapper and panel)

			var $gui = $('<div/>')

				.attr('class', $srcImg.attr('class') || '') // transfer classes

				.addClass('jqPuzzle') // always assign class jqPuzzle

				.css({

					width: fullWidth + 2*(computedStyles.wrapper.padding + computedStyles.wrapper.border) + boxModelHack.gui.width,

					height: fullHeight + 2*(computedStyles.wrapper.padding + computedStyles.wrapper.border) + boxModelHack.gui.height,

					textAlign: 'left',

					overflow: 'hidden', 

					display: 'block'

				})

				.append($wrapper).append($panel);



			// replace source image with jqPuzzle

			$srcImg.replaceWith($gui);



			// assign source image id to jqPuzzle

			var id = $srcImg.attr('id');

			if(id) $gui.attr('id', id);

			

			// opera has strange effect when calling hide() and val() before 

			// the elements are attached to the dom

			// hide numbers

			if(!settings.numbers) $pieces.children().hide();

			// fill inputs

			if($display) $display.children('input').val(0);



			// now, after everything is rendered, recalc gui height

			var guiHeight = $gui.height();

			var panelHeight = $panel.height();

			

			$gui.height($gui.height() + $panel.height());





			// attach events ---------------------------------------------------

			

			// prevent text selection

			if($.browser.msie) $gui[0].onselectstart = function() { return false; };

			else  $gui.mousedown(function() { return false; });

			

			// button press on mousedown

			$buttons.mousedown(function() {

				if(!$(this).is('.jqp-disabled')) $(this).addClass('jqp-down');

			});

			$buttons.mouseout(function() {

				$(this).removeClass('jqp-down');

			});

			$buttons.mouseup(function() {

				$(this).removeClass('jqp-down');	

			});

			

			// swap pieces on click

			$pieces.click(function() {

				// do nothing, if locked

				if(lock) return false;

				

				// do nothing, if solved after being shuffled

				if(solved) return false;

				

				lock = true; // set lock

				

				var $piece = $(this);

				

				// get current position from expando

				var current = $piece.attr('current');

				

				// get current matrix positions for piece and hole

				var source = getMatrixPosition(current);

				var dest = getMatrixPosition(currHole);



				// only swap pieces adjacent to the hole

				if(Math.abs(source.row - dest.row) + Math.abs(source.col - dest.col) != 1) {

						lock = false;

						return false;

					}



				// get offset for the new position

				var offset = getOffset(dest.row, dest.col);



				// update piece expando and current hole position

				$piece.attr('current', String(currHole)); // ie hack: String()

				currHole = current;



				// increase moves counter only if the puzzle was shuffled

				if(shuffled) moves++;



				// update counter field

				if($counter) $counter.val(moves);



				// start timer, if needed

				if(moves == 1) {

					// initiate timer with update function

					if(!timer) timer = new Timer(333, function(ms) {

						seconds = Math.floor(ms/1000);

						if($timer) $timer.val(seconds);

					});

					timer.start();

				}

				

				// animate!

				$piece.animate({left: offset.left, top: offset.top}, animation.slidingSpeed, null, function() {

					// only check if the puzzle was shuffled

					if(shuffled) {

						// check if the puzzle is solved

						solved = checkSolution($pieces);

						if(solved) {

							if(timer) timer.stop();

							shuffled = false;

							$gui.addClass('jqp-solved');

							window.setTimeout(finishGame, 100);	

						}

						else lock = false;						

					}

					else lock = false;

				});

			});



			// shuffle pieces on click

			if(control.shufflePieces) $shuffleButton.click(function() {

				shuffle(animation.shuffleRounds, animation.shuffleSpeed);	

			});



			// toggle original on click

			if(control.toggleOriginal) $originalButton.click(function() {

				// do nothing, if disabled

				if($originalButton.is('.jqp-disabled')) return false;



				// do nothing, if locked

				if(lock) return false;

				

				lock = true; // set lock

				

				if($originalButton.is('.jqp-toggle')) {

					// re-enable other buttons

					if(control.shufflePieces) $shuffleButton.removeClass('jqp-disabled');

					if(control.toggleNumbers) $numbersButton.removeClass('jqp-disabled');

			

					$originalButton.removeClass('jqp-toggle');

					

					// fade out original

					$background.fadeTo(animation.fadeOriginalSpeed, style.backgroundOpacity, function() {

						$(this).prependTo($wrapper);

						

						// resume timer

						if(control.pauseTimer && timer) timer.resume();

						

						lock = false;

					});

				} else {

					// disable other buttons

					if(control.shufflePieces) $shuffleButton.addClass('jqp-disabled');

					if(control.toggleNumbers) $numbersButton.addClass('jqp-disabled');

					

					$originalButton.addClass('jqp-toggle');



					// pause timer

					if(control.pauseTimer && timer) timer.pause();

					

					// fade in original

					$background.appendTo($wrapper).fadeTo(animation.fadeOriginalSpeed, 1, function() {

			

						lock = false;

					});

				}

				return false; // prevent default action

			});



			// toggle numbers on click

			if(control.toggleNumbers) $numbersButton.click(function() {

				// do nothing, if disabled

				if($numbersButton.is('.jqp-disabled')) return false;

				

				if ($numbersButton.is('.jqp-toggle')) {

					$numbersButton.removeClass('jqp-toggle');

					$pieces.children().hide();

				} else {

					$numbersButton.addClass('jqp-toggle');

					$pieces.children().show();

				}

			});		

			

			

			// work to do when the puzzle is solved

			var finishGame = function() {

				if(success.fadeOriginal) {	

					// disable buttons

					if(control.toggleOriginal) $originalButton.addClass('jqp-disabled');

					if(control.toggleNumbers) $numbersButton.addClass('jqp-disabled');

					

					// fade in original

					$background.appendTo($wrapper).fadeTo(animation.fadeOriginalSpeed, 1.0, function() {

						lock = false; // reset lock

						solutionCallback(); // call user callback

					});

				} else {

					lock = false; // reset lock

					solutionCallback(); // call user callback	

				}

			};



			// call a user-defined callback after a timeout, when the puzzle is solved

			var solutionCallback = function() {

				if($.isFunction(success.callback)) {

					setTimeout(function() {

						success.callback({moves: moves, seconds: seconds});

					}, success.callbackTimeout);	

				}

			};



		}); // img load

		

		// unfortunately, image load does not fire consistently across browsers

		// (especially with cached images)

		// therefore, check image.load periodically (bah, brute force...)

		var interval = setInterval(function() {

			if($srcImg[0].complete) {

				clearInterval(interval);

				$srcImg.trigger('load');

			}

		}, 333);

		

		/*

		var interval;

		(function waitForImage($image) {

			if(!$image[0].complete) {

				interval = setInterval(function() {

					waitForImage($image);

				}, 100);

			} else {

				clearInterval(interval);

				$image.trigger('load');

			}

		})($srcImg);*/

		

		// ie shows some inconsistencies with img load and cached images

		// fortunately, in this cases img.complete is true from the beginning

		//if($.browser.msie && $srcImg[0] && $srcImg[0].complete) $srcImg.trigger('load');





	}).end(); // return full collection to allow chaining

};



// automagically apply jqPuzzle to all images with class 'jqPuzzle'

$(document).ready(function() {



	$('img.jqPuzzle').each(function() {



		// define an additional micro format (to be used as a class name)

		

		/* Syntax:   .jqp[-LANGUAGE]-rROWS-cCOLS[-hHOLE][-sSHUFFLE_ROUNDS][-FLAGS]

		 * 

		 * Flags:    S - initially shuffle pieces

		 *           N - initially hide numbers

		 *           A - hide 'Shuffle' button

		 *           B - hide 'Original' button

		 *           C - hide 'Numbers' button

		 *           D - hide 'moves' display

		 *           E - hide 'seconds' display

		 */

		var microFormat = /\bjqp(-[a-z]{2})?-r(\d)-c(\d)(-h(\d+))?(-s(\d+))?(-[A-Z]+)?\b/;

				

		// execute regex and save matches

		var match = microFormat.exec(this.className);

		

		// build settings object from micro format

		var settings;

		if(match) {

			settings = {	

				rows: parseInt(match[2]),

				cols: parseInt(match[3]), 

				hole: parseInt(match[5]) || null,

				shuffle: match[8] && match[8].indexOf('S') != -1,

				numbers: match[8] ? match[8].indexOf('N') == -1 : true,

				language: match[1] && match[1].substring(1)

			};

			

			if(match[7]) {

				settings.animation = {};

				settings.animation.shuffleRounds = parseInt(match[7]);

			}

			

			if(match[8] && match[8].search(/[ABCDE]/) != -1) {

				settings.control = 	{};

				settings.control.shufflePieces = match[8].indexOf('A') == -1;

				settings.control.toggleOriginal = match[8].indexOf('B') == -1;

				settings.control.toggleNumbers = match[8].indexOf('C') == -1;

				settings.control.counter = match[8].indexOf('D') == -1;

				settings.control.timer = match[8].indexOf('E') == -1;

			}

		}

		// call the plugin

		$(this).jqPuzzle(settings);

	});

});



})(jQuery);