// this needs to match the CSS declaration .movingDiv {border:1px}
var BORDERWIDTH = 1;

//==================================================
// "Public" methods which you can use in the HTML
//==================================================

/* change disabled state of the two buttons */
function disableButtons(b) {
	//YAHOO.util.Dom.get("btnReload").disabled = b;
	//YAHOO.util.Dom.get("btnShuffle").disabled = b;
}

/* shows or hides the mask which overlays a number on the tiles. "Show Numbers" option */
function showHideMask()
{
	if (typeof allTiles != "undefined" )
	{
		for(t in allTiles)
		{
			allTiles[t].refreshMask();
		}
	}
}

/* helper function to see if hint is checked */
function isHintOn()
{
// 	return YAHOO.util.Dom.get("chkHints").checked;
}

/* shows or hides the image overlay of the completed puzzle */
function showHideHint(b) 
{
	try{	
		if (!SOLVED) 
			YAHOO.util.Dom.get("overlayDiv").style.display = (b ? "block" : "none" );  
	} catch(e){}
}


/* This is the main method to call when reloading the puzzle, will be called with images as this */
function reloadMainImage(imgsc, sz)
{
	IMAGESRC=imgsc;
		
	allTiles = new Array();
	numCorrect = 0;
	size = sz;//YAHOO.util.Dom.get("ddSize").value;
	SOLVED = false;
	
	//disableButtons(true);

	// set a loading message
	loading();

	// create an img element
	var img = document.createElement("img");

	// only do this when image has loaded
	img.onload = function() {
		var mainDiv = YAHOO.util.Dom.get("main");

		// check the size
		mainDiv.appendChild(this);
		var imgH = this.height;
		var imgW = this.width;
		mainDiv.removeChild(this);

		// write global vars
		imgDimen = [imgW, imgH];
		tileWidth = getTileSize(imgDimen[0]);
		tileHeight = getTileSize(imgDimen[1]);
		tileBoxWidth = tileWidth-BORDERWIDTH*2;
		tileBoxHeight = tileHeight-BORDERWIDTH*2;

		// do the rest of the loading

		buildTable();
		generateTiles();	
		// create an overlay of the completed image
		var overlayDiv = document.createElement("div");
		overlayDiv.id="overlayDiv";
 		var posText = "position:absolute;" + cssPos((Left(mainDiv)), (Top(mainDiv)));

		var dimenText = cssDimen( tileWidth*size-BORDERWIDTH*2, tileHeight*size-BORDERWIDTH*2);  // Box model, must minus border width
 		overlayDiv.style.cssText = posText + dimenText + "overflow:hidden; display:none; border: " + BORDERWIDTH + "px solid #ffeb00;";
		mainDiv.appendChild(overlayDiv);
		var img = document.createElement("img");	
		//img.style.cssText = "position:relative";
		img.src = IMAGESRC;
		overlayDiv.appendChild(img);

		shuffle();
	}
	
	// load the image here. NOTE: this line must be placed after onload is created,
	// because if image already cached, in IE it will generally be a race condition,
	// i.e. the image gets loaded before the onload is registered, i.e. wont fire.
	img.setAttribute("src", IMAGESRC);
}

//=============================================================
// methods used in building everything (shouldn't be called)
//=============================================================

/* uses a pretty loading indicator */
function loading()
{
	// remove existing elements
	var main = YAHOO.util.Dom.get("main");
	while(main.firstChild!=null) {
		main.removeChild(main.firstChild);
	}
	
	var imgLoading = document.createElement("img");
	//imgLoading.src = "indicator_medium.gif";
	var txtLoading = document.createTextNode(" Loading...")
	//YAHOO.util.Dom.get("main").appendChild(imgLoading);
	YAHOO.util.Dom.get("main").appendChild(txtLoading);
}

/* builds an HTML table used for layout of absolutely positioned divs */
function buildTable()
{
	// clean table	
	var main = YAHOO.util.Dom.get("main");
	while(main.firstChild!=null) {
		main.removeChild(main.firstChild);
	}
	
	// create the table
	var tbl = document.createElement('table');
	//tbl.setAttribute('cellpadding', "0"); // !!! NOTE These 3 lines dont work in IE !!!
	//tbl.setAttribute('cellspacing', "0");
	//tbl.setAttribute('border', "0");
	tbl.style.cssText="border-collapse: collapse;";  // !!! NOTE THIS IS HOW YOU REMOVE THE BORDERS !!!
	tbl.setAttribute('width', (tileWidth*size) );
	tbl.setAttribute('height', (tileHeight*size) );
	tbl.setAttribute('id', 'layoutTable');
	var tbody = document.createElement('tbody'); // !!! NOTE the use of the tbody element for IE, otherwise wont show up
		
	var x;
	var y;
	for(y=0; y<size; y++) // y-coordinates
	{
		var row = document.createElement('tr');
		for(x=0; x<size; x++) // x-coordinates
		{			
			
			var cell = document.createElement('td');
			cell.setAttribute('width', tileWidth);
			cell.setAttribute('height', tileHeight);
			cell.id="cell_" + x + "_" + y;
							
			row.appendChild(cell);
		}
		tbody.appendChild(row);
	}
	tbl.appendChild(tbody);
	document.getElementById('main').appendChild(tbl);
}

/* This generates the elements needed for the puzzle */
function generateTiles()
{
	var counter = 0;
	for(y=0; y<size;y++)
	{
		for(x=0;x<size;x++)
		{
			counter++;

			if (x==size-1 && y==size-1)
			{
				//last tile is empty, store coord in global
				spaceCoord = new Coord(x, y);
				cell.appendChild(document.createTextNode(" "));
			}
			else
			{
				var main = YAHOO.util.Dom.get("main");
				var cell = YAHOO.util.Dom.get("cell_" + x + "_" + y);				
				//var posStr = cssPos((cell.offsetLeft +  main.offsetLeft), (cell.offsetTop + main.offsetTop));
				var posStr = cssPos((Left(cell) ), (Top(cell)));
				// AbsPos Moving Div
				var div1 = document.createElement('div');
				div1.id = "div_" + x + "_" + y;  
				div1.className = "movingDiv";
				div1.style.cssText = posStr + cssDimen(tileBoxWidth, tileBoxHeight) + "cursor:pointer;"; // !!! NOTE setAttribute('style', 'blah') doesn't work in IE. this works in IE,FF,Kq.			
				div1.onmouseover = function() { allTiles[this.id].showHideMaskForCorrectTile(true); };   // !!! NOTE setAttribute('onmouseover', ...) works in FF, but doesn't work in IE. do this instead.
				div1.onmouseout = function() {  allTiles[this.id].showHideMaskForCorrectTile(false);  };
				YAHOO.util.Dom.get("main").appendChild(div1);

				// background div				
				var div2 = document.createElement('div');  
				var marginText = "margin-left:" + (-1*x*tileWidth) + "px; margin-top:" + (-1*y*tileHeight) + "px;";
				div2.style.cssText = cssDimen(imgDimen[0], imgDimen[1]) + marginText + "background:url(" + IMAGESRC + ")";
				div1.appendChild(div2);

				// masking div
				var div3 = document.createElement('div');
				div3.className = "mask";  // the padding below negates effects of margin in parent, so that the mask is over the area we can see.
				div3.style.cssText = cssDimen(tileBoxWidth, tileBoxHeight) + "padding-left:" + (x*tileWidth) + "px; padding-top:" + (y*tileHeight) + "px;";
				div3.style.display = "none"; // dont display initially
				div2.appendChild(div3);
				
				// text div
				var div4 = document.createElement('div');
				div4.className = "textDiv";
				div4.style.cssText = cssDimen(tileBoxWidth, tileBoxHeight) + "line-height:" + (tileHeight-BORDERWIDTH*2) + "px"
				div4.appendChild(document.createTextNode(counter));
				div3.appendChild(div4);

				// add tile to map	
				var tile = new Tile( new Coord(x,y) , counter, div1);				
				allTiles[div1.id] = tile; 	
				
				YAHOO.util.Event.on(div1, "click", function(evt){
					var nd = evt.target || evt.srcElement ;
					var key = "";
					if (nd.className=="textDiv")
						key = nd.parentNode.parentNode.parentNode.id  // TextNode
					else
						key =  nd.parentNode.id; // background node (when mask node is not visible)
					var b = allTiles[key].moveTile(spaceCoord);
				});
	
				
			}	
		}
	}	
}
function getTileSize(n)
{
	return Math.round(n / size);
}
// NOTE: In strict mode, leaving out the "px" will make the CSS declaration invalid!!!
function cssDimen(w, h)
{
	return "width:" + w + "px;height:" + h + "px;";
}
function cssPos(x, y)
{
	return "left:" + x + "px;top:" + y + "px;";
}

//===============================================
// Objects
//===============================================

function Coord(x, y)
{
	this.x = x;
	this.y = y;
	this.getNeighbours = function() {
		var neighbours = new Array();
		
		// Right/East
		var x1 = this.x + 1;
		var y1 = this.y;
		neighbours.push(new Coord(x1, y1));

		// left/West
		x1 = this.x - 1;
		y1 = this.y;
		neighbours.push(new Coord(x1, y1));

		// Top/North
		x1 = this.x;
		y1 = this.y-1;
		neighbours.push(new Coord(x1, y1));

		// bottom/South
		x1 = this.x;
		y1 = this.y+1;
		neighbours.push(new Coord(x1, y1));

		return neighbours;
	};
	this.toString = function() {
		return "(" + x + "," + y + ")";
	};
	this.equals = function(c) {
		return (c.x==this.x) && (c.y==this.y);
	};
	this.isValid = function() {
		return (this.x >=0)  && (this.y >=0)  && (this.x <size)  && (this.y <size)
	};
}

/* an object which represents a Tile */
function Tile(coord, value, divObj)
{
	this.coord = coord;
	this.value = value;
	this.divObj = divObj;
	this.origCoord = coord;
	numCorrect++;
	this.afterAnimNumCorrect;

	this.isMovable = function(space) 
	{
		var neibs = this.coord.getNeighbours();
		var i;
		for(i in neibs)
		{
			if (neibs[i].equals(space))
				return true;
		}
		return false;
	};	
	
	this.isCorrect = function()
	{
		return this.coord.equals(this.origCoord);
	};
	
	this.moveTile = function(space, fastMode)
	{
		if (this.isMovable(space))
		{
			var wasCorrect = this.coord.equals(this.origCoord);

			// swap the coords
			var tmp = this.coord;
			this.coord = space;
			spaceCoord = tmp;			

			// check if correct
			if (this.coord.equals(this.origCoord))
			{
				numCorrect++;
			}
			else if (wasCorrect) // only decrement when going from correct to incorrect
			{
				numCorrect--;
			}

			// check speed
			var doFast = fastMode || false;
			//var speed = (doFast ? 0.1 : 0.4);
			
			// animate it and oncomplete, check if solved
			if (!doFast) {
				this.afterAnimNumCorrect = numCorrect;
				var slideAnim = new YAHOO.util.Motion(this.divObj.id, { points: { to: getNewLoc(this.coord) } }, 0.3, YAHOO.util.Easing.easeOut);
				slideAnim.onComplete.subscribe( this._postAnimate, this, this);
				slideAnim.animate();
			} else {
				queuedAnims.push( {id: this.divObj.id, loc: getNewLoc(this.coord) } );
			}
			
			return true;
		}
		else
		{
			return false;
		}
	};
	
	/* refresh the mask and check if solved */
	this._postAnimate = function() {
		this.refreshMask();
		if (this.afterAnimNumCorrect==(size*size)-1)
			finish();
	}
	
	/* Mask will be shown if hint is checked and tile is incorrect */
	this.refreshMask = function() {
		var maskDiv = this.divObj.childNodes[0].childNodes[0];
		maskDiv.style.display = (   (isHintOn() && !this.isCorrect())  ? "block" : "none"  );	
	};
	
	/* When hint is on and you mouseover a correct tile, is will show the mask */
	this.showHideMaskForCorrectTile = function(b) {
		if (this.isCorrect())
		{
			var maskDiv = this.divObj.childNodes[0].childNodes[0];
			maskDiv.style.display = (   (b && isHintOn())  ? "block" : "none"  );	
		}
	};

}

function getNewLoc(c)
{
	var main = YAHOO.util.Dom.get("main");
	var cell = YAHOO.util.Dom.get("cell_" + c.x + "_" + c.y);
	
	// for some reason, IE is 2 px off!!! (is it YUI???) hack to fix
	/*@cc_on
		return [Left(cell)+2, Top(cell)+2];
	@*/
	return [Left(cell), Top(cell) ];
}

/* Do this when the puzzle is completed */
function finish()
{
	// enable the overlay and make transparent
	YAHOO.util.Dom.get("overlayDiv").style.opacity = 0;
	YAHOO.util.Dom.get("overlayDiv").style.filter = "alpha(opacity=0)";
	YAHOO.util.Dom.get("overlayDiv").style.display = "block";
	
	// fade in the overlay
	var fadein = new YAHOO.util.Anim("overlayDiv", { opacity: { to: 1 } }, 1, YAHOO.util.Easing.easeOut);
	fadein.onComplete.subscribe( function(e) {        
		// after fading in, tell user congrats
		SOLVED = true;
		alert("Congratulations! You've solved the puzzle!");
	});
	fadein.animate();
}


//===============================================
// Shuffle
//===============================================

function shuffle()
{
	var lastCoord; // stores location of most recently moved tile
	var i;
	var maxShuf = 2*size*size;
	for(i=0; i<maxShuf;i++)
	{		
		var movableCoord = getMovableCoord(lastCoord);
		var toMove;
		var rnd = 0;
		if (movableCoord.length>=1)
			rnd = Math.round(Math.random()*movableCoord.length) % movableCoord.length;		

		
		var toMove = movableCoord[rnd];	
		for(t in allTiles) // find the tile which we are moving
		{
			if (allTiles[t].coord.equals(toMove))
			{
				allTiles[t].moveTile(spaceCoord, true); 
				lastCoord = allTiles[t].coord;
				break;
			}
		}
	}
	
	animateAll();
}

function getMovableCoord(lastCoord)
{
	// finds neighbours which are valid
	var neibs = spaceCoord.getNeighbours();
	var validNeibs = new Array();
	var i;
	for(i in neibs)
	{
		if (neibs[i].isValid())
		{
			// if last coord was provided, dont put that in the list
			if (typeof lastCoord == "undefined" || !neibs[i].equals(lastCoord))
			{
				validNeibs.push(neibs[i]);
			}			
		}
	}
	return validNeibs;
}

/* when shuffling, queue up all animations and run in sequence */
var queuedAnims = [];

function animateAll() {
	var obj = queuedAnims.shift();
	if (typeof obj!="undefined") {
		var slideAnim = new YAHOO.util.Motion(obj.id, { points: { to: obj.loc } }, 0.01, YAHOO.util.Easing.easeOut);
		slideAnim.onComplete.subscribe( animateAll );  // recursive callback to animate next one
		slideAnim.animate();
	} else {
		// animated everything, refresh the masks
		showHideMask();
		disableButtons(false);
	}
}


function Left(obj)
{
	var curleft = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curleft += obj.offsetLeft
			obj = obj.offsetParent;
		}
	}
	else if (obj.x)
		curleft += obj.x;
	return curleft;
}

function Top(obj)
{
	var curtop = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curtop += obj.offsetTop
			obj = obj.offsetParent;
		}
	}
	else if (obj.y)
		curtop += obj.y;
	return curtop;
}
//===============================================================================
// improve performance to cater for flickr slowness
// * carousel images will be created after page is loaded
// * if carousel images not completely loaded, load default images instead
//===============================================================================


