(function($){
	// ==Description==
	// Pour chaque élément 'fn' :
	// Créer un nouvel élément juste après composés de Div en absolue composant le jeu du taquin. 
	// Un clique permet de mélanger le jeu.
	// La résolution du tableau remet les éléments aux états initiaux.

	// ==image==
	// Ceci réprésent l'image qui sera fractionnée pour être jouer.

	// ==params==
	// width : Indique la taille que fait l'image en largeur. Si rien ou zéro est précisé, la taille par défaut est celle de image.
	// height : Indique la taille que fait l'image en hauteur. Si rien ou zéro est précisé, la taille par défaut est celle de image.
	// division : Indique le nombre de carré en largeur/hauteur qui compose le taquin.
	// hidePart : Indique quel parti du carré est masqué pour faire office de trou pour les déplacements. Les valeurs possible sont Haut Gauche "tl", Haut droite "tr", Bas gauche "bl", Bas droite "br" (par défaut).
	// mixing : Indique le nombre de déplacement fait aléatoirement pour simuler un mélange à la main du taquin.
	// success : Si la fonctuion de succès n'est pas écrasé, comporte le message proposé en alerte.

	// ==success==
	// Fonction à exécuter en cas de succès.
	$.fn.gameTaquin = function(image , params) {

		// Paramètres initiaux écrasés s'il sont passés en paramètre d'entré.
		var params = $.extend({
			width: 0,
			height: 0,
			division: 4,
			hidePart: 'br',
			mixing: 200,
			success: 'Complete',
			successFunction: function () {
				alert(params.success);
			}
		}, params);

		// Si la division est inférieur à 2, la valeur est 2.
		if (params.division < 2) { params.division = 2; }

		// Fonction exécuté sur chaque élément sélectonné comme model pour devenir un taquin.
		function taquin(element, imageOriginalSize)
		{
			////////////////////
			// Initialisation //
			////////////////////

			// Initialisation des variables.
			var gameDivision = params.division;
			var imageWidth = 0;
			var imageHeight = 0;

			// Récupère la largeur et hauteur des partis du taquin.
			if (params.width != 0) { imageWidth = params.width; } else { imageWidth = imageOriginalSize[0]; }
			if (params.height != 0) { imageHeight = params.height; } else { imageHeight = imageOriginalSize[1]; }
			var squareWidth = Math.round(imageWidth / gameDivision);
			var squareHeight = Math.round(imageHeight / gameDivision);

			// Initialise l'état initial et actuelle du jeu.
			var gameStart = initialiseGameVar(gameDivision);
			var gameState = initialiseGameVar(gameDivision);

			// Génère l'élément qui contiendra les partis du taquin.
			var taquin = $("<div>").addClass("taquin-generate");
			if (element.next().hasClass("taquin-generate"))
				element.next().remove(); 
			element.after(taquin);
			taquin.css("position","relative");
			taquin.css("width", imageWidth + "px");
			taquin.css("height",imageHeight + "px");

			// Générer les partis du taquin.
			for (var xi = 0; xi < gameStart.length; xi++) {
				for (var yi = 0; yi < gameStart[0].length; yi++) {
					$("<div>", {
						css: {
							cursor: "pointer",
							backgroundImage: "url('" + image + "')",
							backgroundPosition : "-" + parseInt(squareWidth * xi) + "px -" + parseInt(squareHeight * yi) + "px",
							position: "absolute",
							top: parseInt(squareHeight * yi) + "px",
							left: parseInt(squareWidth * xi) + "px",
							width: squareWidth + "px",
							height: squareHeight + "px"
						}
					}).appendTo(taquin).addClass(gameStart[xi][yi][1]).addClass("taquin-part");
					
					/*var temp = $("<div>", {
						css: {
							cursor: "pointer",
							position: "absolute",
							top: parseInt(squareHeight * yi) + "px",
							left: parseInt(squareWidth * xi) + "px",
							width: squareWidth + "px",
							height: squareHeight + "px",
							overflow: "hidden"
						}
					}).appendTo(taquin).addClass(gameStart[xi][yi][1]).addClass("taquin-part")
						.append('<img>')
							.children(":first")
							.attr("src",image);*/
				}
			}

			// Permettre de démarrer le jeu.
			taquin.find(".taquin-part").bind("click", launcher);

			///////////////////
			// Bouger Pièces //
			///////////////////

			// Démarrer le jeu
			function launcher() {
				// On enlève l'intialisation de jeu.
				taquin.find(".taquin-part").unbind("click", launcher);

				// On cherche l'élément vide.
				var empty = getEmpty();
				$("." + empty[2]).animate({ opacity: 0 }, function () {
					// On le cache.
					$(this).hide();

					// On mélange le jeu.
					randomGame(params.mixing);

					// Annules toutes les animations s'il y en a pas finie.
					taquin.find(".taquin-part").clearQueue();

					// Permettre le déplacement des pièces, jeu jouable !
					taquin.find(".taquin-part").bind("click", clickFunction);
				});
			}

			// Quand on clique sur une partie après le lancement du jeu.
			function clickFunction() {
				// Identifier l'élément.
				var id = getId($(this));

				// Trouver sa position dans le jeu.
				var coord = getCoord(id);

				// Vérifier si il est déplaçable.
				var isMovable = getIsMovable(coord[0], coord[1]);

				// Si il est déplaçable, interchanger les positions.
				if (isMovable) {
					// Trouver l'élément vide.
					var empty = getEmpty();

					// On trouve les déplacements pour l'animation.
					taquin.find("." + empty[2]).show();
					var moveTop = taquin.find("." + id).position().top - taquin.find("." + empty[2]).position().top;
					var moveLeft = taquin.find("." + id).position().left - taquin.find("." + empty[2]).position().left;
					taquin.find("." + empty[2]).hide();

					//On met les variable en temporaire pour l'interchangement.
					var tempTop = $(this).position().top;
					var tempLeft = $(this).position().left;

					// On inverse les positions de l'éléments vide.
					var temp = gameState[coord[0]][coord[1]];
					gameState[coord[0]][coord[1]] = gameState[empty[0]][empty[1]];
					gameState[empty[0]][empty[1]] = temp;

					// On anime le changement.
					$(this).unbind("click", clickFunction).animate({
						top: "-=" + moveTop,
						left: "-=" + moveLeft
					}, function () {
						$(this).bind("click", clickFunction);
						
						// Si l'état initial après animation est le même que l'état actuel, on a fini le jeu.
						if (gameState.toString() == gameStart.toString()) {
							// On cherche l'élément vide pour le ré-afficher.
							var empty = getEmpty();
							$("." + empty[2]).show().animate({ opacity: 1 }, function () {
								// On replace le mécanisme de démarrage du jeu.
								taquin.find(".taquin-part").bind("click", launcher);
								// On exécute la fonction dréussite.
								params.successFunction();
							});
						}
					});

					taquin.find("." + empty[2])
						.css("top", tempTop + "px")
						.css("left", tempLeft + "px");
				}
			}

			//////////////
			// Fonction //
			//////////////

			// Mélange pièce.
			function randomGame(mixing) {
			
				for (var j = 0; j < mixing; j++) {
					var empty = getEmpty();

					// On cherche les voisins déplaçable.
					var possibleMove = [];
					i = 0;

					try { if (gameState[empty[0]][empty[1] - 1][0] == true) {
						possibleMove[i] = [];
						possibleMove[i][0] = empty[0];
						possibleMove[i][1] = empty[1] - 1;
						i++;
					} } catch (err) {}
					try { if (gameState[empty[0] + 1][empty[1]][0] == true) {
						possibleMove[i] = [];
						possibleMove[i][0] = empty[0] + 1;
						possibleMove[i][1] = empty[1];
						i++;
					} } catch (err) {}
					try { if (gameState[empty[0]][empty[1] + 1][0] == true) { 
						possibleMove[i] = [];
						possibleMove[i][0] = empty[0];
						possibleMove[i][1] = empty[1] + 1;
						i++;
					} } catch (err) {}
					try { if (gameState[empty[0] - 1][empty[1]][0] == true) { 
						possibleMove[i] = [];
						possibleMove[i][0] = empty[0] - 1;
						possibleMove[i][1] = empty[1];
						i++;
					} } catch (err) {}

					// On choisit au hasard le voisin à interchanger.
					var rand = Math.floor(Math.random() * possibleMove.length);

					// On l'interchange dans l'état de jeu.
					var temp = gameState[empty[0]][empty[1]];
					gameState[empty[0]][empty[1]] = gameState[possibleMove[rand][0]][possibleMove[rand][1]];
					gameState[possibleMove[rand][0]][possibleMove[rand][1]] = temp;
				}

				// On trouve les déplacements pour l'animation.
				// Pour chaque élément positionné au départ.
				for (var xi = 0; xi < gameStart.length; xi++) {
					for (var yi = 0; yi < gameStart[0].length; yi++) {
						// On cherche sa nouvelle position
						for (var xj = 0; xj < gameState.length; xj++) {
							for (var yj = 0; yj < gameState[0].length; yj++) {
								// On trouve la nouvelle position.
								if (gameStart[xi][yi][1] == gameState[xj][yj][1]) {
									// On récupère les coordonnées de la nouvelle position.
									if (gameStart[xi][yi][0] == true) {
										var newPosTop = parseInt((squareHeight * yj) - $(".taquin-" + xi + "-" + yi).position().top) + "px";
										var newPosLeft = parseInt((squareWidth * xj) - $(".taquin-" + xi + "-" + yi).position().left) + "px";

										$(".taquin-" + xi + "-" + yi).animate({
											top: "+=" + newPosTop,
											left: "+=" + newPosLeft
										});
									} else {
										$(".taquin-" + xi + "-" + yi)
											.css("top",(squareHeight * yj))
											.css("left",(squareWidth * xj));
										}
									break; break;
								}
							}
						}
					}
				}
			}

			// Repéré la case dans le jeu.
			function getId(taquin) {
				var classList = taquin.attr('class').split(/\s+/);
				var result = "";
				
				$.each(classList, function(index, item) {
					if (index == 0) {
						result = item;
					}
				});
				
				return result;
			}
			function getCoord(id) {
				var coord = [];
				
				for (var xi = 0; xi < gameStart.length; xi++) {
					for (var yi = 0; yi < gameStart[0].length; yi++) {
						if (gameState[xi][yi][1] == id) {
							coord[0] = xi;
							coord[1] = yi;
						}
					}
				}
				
				return coord;
			}

			// Repéré l'élément vide.
			function getEmpty() {
				var empty = [];
				
				for (var xi = 0; xi < gameStart.length; xi++) {
					for (var yi = 0; yi < gameStart[0].length; yi++) {
						if (gameState[xi][yi][0] == false) {
							empty[0] = xi;
							empty[1] = yi;
							empty[2] = gameState[xi][yi][1];
						}
					}
				}
				
				return empty;
			}

			// vérifier si un élément peut être bougé.
			function getIsMovable(x, y) {
				var isMovable = false;

				try {
					if (gameState[x][y - 1][0] == false) isMovable = true;
				} catch (err) {}
				try {
					if (gameState[x + 1][y][0] == false) isMovable = true;
				} catch (err) {}
				try {
					if (gameState[x][y + 1][0] == false) isMovable = true;
				} catch (err) {}
				try {
					if (gameState[x - 1][y][0] == false) isMovable = true;
				} catch (err) {}
				
				return isMovable;
			}

			// Créer les états de jeu.
			function initialiseGameVar(gameDivision) {
				var game = [];
				
				for (var xi = 0; xi < gameDivision; xi++) {
					var gameRow = [];
					for (var yi = 0; yi < gameDivision; yi++) {
						var gameDiv = [];

						currentDisplay = true;
						if ((xi == 0) && (yi == 0) && (params.hidePart == 'tl')) { currentDisplay = false; }
						if ((xi == (gameDivision - 1)) && (yi == 0) && (params.hidePart == 'tr')) { currentDisplay = false; }
						if ((xi == 0) && (yi == (gameDivision - 1)) && (params.hidePart == 'bl')) { currentDisplay = false; }
						if ((xi == (gameDivision - 1)) && (yi == (gameDivision - 1)) && (params.hidePart == 'br')) { currentDisplay = false; }

						gameDiv[0] = currentDisplay;
						gameDiv[1] = 'taquin-' + xi + '-' + yi;
						gameRow[yi] = gameDiv;
					}
					game[xi] = gameRow;
				}
				
				return game;
			}
		}

		// S'execute pour chaque élément 'fn' trouvé.
		return this.each(function(){

			// Information sur l'image.
			var forImage = new Image();
			forImage.src = image;
			var element = $(this);
			var imageSize = [];

			// Quand on obtient une taille pour l'image, on execute le mécanisme.
			var waitForImageSize = setInterval(function () {
				if (forImage.width != 0) {
					imageSize[0] = forImage.width;
					imageSize[1] = forImage.height;
					
					taquin(element, imageSize);
					clearInterval(waitForImageSize);
				}
			}, 50);
		});
	};
})(jQuery)
