//
// Copyright Just2Easy Limited 2008,2009
// all rights reserved
//
// see - www.j2e.com
//

function jigsaw(jigsawShape,xBits,yBits,sx,sy,sw,sh,pieces){
	this.jigsawShape=jigsawShape     // type of jigsaw
	this.xBits=xBits   // bits across
	this.yBits=yBits   // bits down
	this.sx=sx   	 // x
	this.sy=sy   	 // y
	this.sw=sw   	 // width
	this.sh=sh   	 // height
	this.pieces=pieces // pieces - array of pieces, in order
	this.done=false

	for (i=0;i<pieces.length;i++)
		pieces[i] = getElementById(pieces[i])
}

function setJig(id,jigsaw,sx,sy,sw,sh,row,col,offsetX,offsetY,l_tag,t_tag,r_tag,b_tag,drag){
	var o = getElementById(id)
	o.jigsaw = jigsaw
	o.sx = sx
	o.sy = sy
	o.sw = sw
	o.sh = sh
	o.row = row
	o.col = col
	o.offsetX = offsetX
	o.offsetY = offsetY
	o.l_tag = l_tag
	o.t_tag = t_tag
	o.r_tag = r_tag
	o.b_tag = b_tag

	if (drag=="1")
		makeDraggable(id)
}

function setJigPos(id,rowActual,colActual){
	var o = getElementById(id)
	o.rowActual = rowActual
	o.colActual = colActual
	o.style.zIndex = 1

	o.onmousedown = function(ev){
		blockGame(o,false)
	}
}

function missingJig(missing,jigsaw){
	jigsaw.missing = missing
	var o = getElementById(missing)

	o.onmousedown = function(ev){
		if (jigsaw.done){
			fade(jigsaw.missing,10)
			jigsaw.done = false
			jigsaw.solving=undefined

			if (jigsaw.solveButton!=undefined)
				jigsaw.solveButton.disabled=false
		}
	}
}

function blockGame(o,undo){
	if (o.jigsaw!=undefined && o.jigsaw.done==false){
		var jigsaw = o.jigsaw
		var pieces = jigsaw.pieces

		var up = true
		var down = true
		var left = true
		var right = true

		for(i in pieces){
			var p = pieces[i]

			if (p!=undefined && p!=o){
				if (p.rowActual==o.rowActual-1 && p.colActual==o.colActual)
					up = false

				if (p.rowActual==o.rowActual+1 && p.colActual==o.colActual)
					down = false

				if (p.colActual==o.colActual-1 && p.rowActual==o.rowActual)
					left = false

				if (p.colActual==o.colActual+1 && p.rowActual==o.rowActual)
					right = false
			}
		}

		if (o.rowActual==0)
			up = false
		if (o.rowActual==jigsaw.yBits-1)
			down = false
		if (o.colActual==0)
			left = false
		if (o.colActual==jigsaw.xBits-1)
			right = false

		if (up)
			blockTo(jigsaw,o,o.colActual,o.rowActual-1)
		else if (down)
			blockTo(jigsaw,o,o.colActual,o.rowActual+1)
		else if (left)
			blockTo(jigsaw,o,o.colActual-1,o.rowActual)
		else if (right)
			blockTo(jigsaw,o,o.colActual+1,o.rowActual)
		else
			o = undefined

		if (!undo && o!=undefined){
			if (jigsaw.moves == undefined)
				jigsaw.moves = ""

			var lastMove = ""
			var comma = jigsaw.moves.indexOf(",")
			if (comma!=-1)
				lastMove = jigsaw.moves.substr(0,comma)

			if (lastMove == o.id)
				jigsaw.moves = jigsaw.moves.substr(comma+1)
			else
				jigsaw.moves = o.id + "," + jigsaw.moves
		}

		if (solved(jigsaw)){
			appear(jigsaw.missing,10)
			jigsaw.done = true
			jigsaw.moves = undefined
		}
	}
}

function solved(jigsaw){
	var pieces = jigsaw.pieces

	for(i in pieces){
		var p = pieces[i]

		if (p.row!=p.rowActual || p.col!=p.colActual)
			return false
	}

	return true
}

function blockTo(jigsaw,o,col,row){
	// Position o where o1 should really be.
	var h = length(jigsaw.sw)/jigsaw.xBits
	var w = length(jigsaw.sh)/jigsaw.yBits
	
	var x = length(jigsaw.sx) - - col*h + length(o.offsetX)
	var y = length(jigsaw.sy) - - row*w + length(o.offsetY)

	o.rowActual = row
	o.colActual = col

	animateTo(o, x, y)
}

function jigsawSnap(o,jigsaw){
	if (jigsaw!=undefined && o!=undefined){
		var closest = null;
		var dist = 1000000000000;
		var x = xCentre(o)
		var y = yCentre(o)
		var pieces = jigsaw.pieces

		for(i in pieces){
			var p = pieces[i]

			if (p!=o && p!=undefined){
				var xx = xCentre(p) - x
				var yy = yCentre(p) - y
				var d = Math.sqrt( xx * xx + yy * yy )

				if (closest==undefined || d<dist){
					closest = p
					dist = d
				}
			}
		}

		if (closest!=undefined){
			var sx = length(closest.sx) - length(closest.offsetX)
			var sy = length(closest.sy) - length(closest.offsetY)
			var sw = length(jigsaw.sw) / jigsaw.xBits
			var sh = length(jigsaw.sh) / jigsaw.yBits

			// Now snap to the closest
			if (x < sx)
				x = sx - sw
			else if (x > sx + sw)
				x = sx + sw
			else
				x = sx

			if (y < sy)
				y = sy - sh
			else if (y > sy + sh)
				y = sy + sh
			else
				y = sy

			x += length(o.offsetX)
			y += length(o.offsetY)

			var snapDist = 6
			if (units(jigsaw.sy)=="in")
				snapDist = snapDist / 25.4

			if ( !(x==sx && y==sy)){	// Don't snap on top
				var xx = x - length(o.sx)
				var yy = y - length(o.sy)
				var snap = Math.sqrt( xx * xx + yy * yy )

				if (snap < snapDist){
					var u = units(closest.sx)
					o.style.position = 'absolute'
					o.style.left = o.sx = x+u
					o.style.top = o.sy = y+u
				}
			}	
		}
	}
}

function xCentre(o){
	return length(o.sx) + length(o.sw)/2 - length(o.offsetX)
}

function yCentre(o){
	return length(o.sy) + length(o.sh)/2 - length(o.offsetY)
}

function solve(jigsaw,button){
	if (jigsaw.missing!=undefined){
		if (button!=undefined){
			jigsaw.solveButton = getElementById(button)

			if (jigsaw.solveButton!=undefined)
				jigsaw.solveButton.disabled=true
		}

		solve1(jigsaw)
	}
	else
		solve2(jigsaw)
}

function solve2(jigsaw){
	var col = 0
	var row = 0
	var x = length(jigsaw.sx)
	var y = length(jigsaw.sy)
	var u = units(jigsaw.sx)
	var w = length(jigsaw.sw) / jigsaw.xBits
	var h = length(jigsaw.sh) / jigsaw.yBits
	var pieces = jigsaw.pieces

	for(i in pieces){
		var p = pieces[i]
		var posX = x - - col * w + length(p.offsetX)
		var posY = y - - row * h + length(p.offsetY)

		if (!(col==jigsaw.xBits-1 && row==jigsaw.yBits-1)){
			p.colActual = col
			p.rowActual = row
		}

		animateTo(p, posX, posY)
		col++

		if (col==jigsaw.xBits){
			col = 0
			row++
		}
	}
}

function scatter(jigsaw){
	var pieces = jigsaw.pieces
	var sorted = new Array()
	for(i in pieces){
		var o = pieces[i]
		o.random = Math.random()
		sorted[ sorted.length ] = o
	}
		
	sorted.sort( sortPiece )
		
	var left = length(jigsaw.sx)
	var top = length(jigsaw.sy)
	var right = left + length(jigsaw.sw)
	var bottom = top + length(jigsaw.sh)
		
	var sizeX = length(jigsaw.sw) / jigsaw.xBits
	var sizeY = length(jigsaw.sh) / jigsaw.yBits

	var window = windowSize()
	var windowLeft =  pixelsToMM(window.x)
	var windowRight = windowLeft + pixelsToMM(window.width)
	var windowTop = pixelsToMM(window.y)
	var inch = units(jigsaw.sw)=="in"
            
	if (inch){
		windowLeft = windowLeft / 25.4
		windowRight = windowRight / 25.4
		windowTop = windowTop / 25.4
	}
	
	var smidge = 5

	if (inch)
		smidge = smidge / 25.4

	var leftX = Math.max(windowLeft + smidge, left - 2*sizeX)
	windowRight = Math.min(right + 2*sizeX, windowRight)

	var x = leftX
	var y = Math.max( sizeY, top - 2*sizeY )

	for(i in sorted){			
		var o = sorted[i]
		if (y+sizeY>top && y<bottom-sizeY && x+sizeX>left && x<right)
			x = right+smidge
		
		if (x>windowRight-sizeX){
			x = leftX
			y += sizeY * 3/2
		}
			
		sorted[i].style.zIndex = i
		var x1 = x -sizeX/2+sizeX*Math.random()
		var y1 = y -sizeY/2+sizeY*Math.random()

		var sw = length(o.sw)

		x1 = Math.max(leftX,x1)
		x1 = Math.min(windowRight-sw - smidge,x1)

		if (x1<left+smidge && x1+sw>left)
			x1 = left - sw - 2 * smidge
		else if (x1<right+smidge && x1+sw>right)
			x1 = right + 2 * smidge
		else if (y1<bottom+smidge && y1+sizeY>bottom)
			y1 = bottom + 2 * smidge

		animateTo(o, x1, y1)
		x += sizeX
	}
}

function sortPiece(a,b){
	return a.random - b.random;
}

function jumble(jigsaw){
	if (jigsaw.solveButton!=undefined)
		jigsaw.solveButton.disabled=false

	if (jigsaw.missing!=undefined)
		jumble1(jigsaw)
	else
		jumble2(jigsaw)
}

function jumble2(jigsaw){
	var pieces = jigsaw.pieces

	for(i in pieces)
		pieces[i].jumbled = false

	for(i in pieces){
		var o = pieces[i]

		// Find any similar pieces, and give them a value.
		if (o.jumbled==false){
			var similar = new Array()
			similar[0] = o
			o.random = Math.random()
			o.jumbled = true

			for(j in pieces){
				var p = pieces[j]

				if (p.jumbled==false && sameShape(o, p)){
					similar[ similar.length ] = p
					p.random = Math.random()
					p.jumbled = true
				}
			}

			if ( similar.length == 1)
				position(jigsaw,o,o)
			else{
				// copy and sort array
				var sorted = new Array( similar.length )

				for (n in similar)
					sorted[n] = similar[n]

				sorted.sort(sortPiece)

				var m = 0
				for (k in similar)
					position( jigsaw, similar[k], sorted[m++] )
			}
		}
	}	
}

function jumble1(jigsaw){
	var pieces = jigsaw.pieces
	fade(jigsaw.missing,10)
	jigsaw.done = false
	jigsaw.moves = ""

	for(i in pieces){
		var p = pieces[i]
		p.colActual = p.col
		p.rowActual = p.row
	}

	var c = jigsaw.xBits-1
	var r = jigsaw.yBits-1
	var last = null
	var iterations = pieces.length * 10
		
	for(var i=0;i<iterations;i++){
		var p = undefined
		var random = Math.random()
		if (random<0.25)	// left
			p = at(jigsaw,c-1,r)
		else if (random<0.5)	// above
			p = at(jigsaw,c,r+1)
		else if (random<0.75)	// right
			p = at(jigsaw,c+1,r)
		else	// bottom
			p = at(jigsaw,c,r-1)
	
		if (p!=undefined && p!=last){	// avoid repeat moves
			var c1 = p.colActual
			var r1 = p.rowActual
			p.colActual = c
			p.rowActual = r
			c = c1
			r = r1
			last = p
			jigsaw.moves = p.id + "," + jigsaw.moves
		}
	}
				
	var h = length(jigsaw.sw)/jigsaw.xBits
	var w = length(jigsaw.sh)/jigsaw.yBits

	for(i in pieces){
		var o = pieces[i]
		var x = length(jigsaw.sx) - - o.colActual*h + length(o.offsetX)
		var y = length(jigsaw.sy) - - o.rowActual*w + length(o.offsetY)

		animateTo(o, x, y)
	}

	jigsaw.solving=undefined
}

function tile(jigsaw,p){
	return p.row*jigsaw.xBits + p.col
}

function at(jigsaw,col,row){
	var pieces = jigsaw.pieces

	for(j in pieces){
		var p = pieces[j]

		if (p.colActual==col && p.rowActual==row)
			return p
	}
		
	return undefined
}

function sameShape(o, o1){
	if (o.jigsaw.jigsawShape=="block")
		return true
		
	if (isCorner(o) || isCorner(o1))
		return false
		
	if (isTop(o) || isTop(o1)){	// Both on top row
		if (o.row==o1.row)
			return o.l_tag==o1.l_tag && o.r_tag==o1.r_tag && o.b_tag==o1.b_tag
	}
	else if (isBottom(o) || isBottom(o1)){	// Both on bottom row
		if (o.row==o1.row)
			return o.l_tag==o1.l_tag && o.r_tag==o1.r_tag && o.t_tag==o1.t_tag
	}
	else if (isLeft(o) || isLeft(o1)){	// Both in first column
		if (o.col==o1.col)
			return o.r_tag==o1.r_tag && o.t_tag==o1.t_tag && o.b_tag==o1.b_tag
	}
	else if (isRight(o) || isRight(o1)){	// Both on last column
		if (o.col==o1.col)
			return o.l_tag==o1.l_tag  && o.t_tag==o1.t_tag && o.b_tag==o1.b_tag
	}
	else
		return o.l_tag==o1.l_tag && o.r_tag==o1.r_tag && o.t_tag==o1.t_tag && o.b_tag==o1.b_tag
		
	return false
}

function isTop(o){
	return o.row==0
}

function isBottom(o){
	return o.row==o.jigsaw.yBits-1
}

function isLeft(o){
	return o.col==0
}

function isRight(o){
	return o.col==o.jigsaw.xBits-1
}

function isCorner(o){
	if (o.col==0 && (o.row==0 || o.row==o.jigsaw.yBits-1))
		return true

	if (o.row==0 && (o.col==o.jigsaw.xBits-1))
		return true

	if (o.row==o.jigsaw.yBits-1 && o.col==o.jigsaw.xBits-1)
		return true

	return false
}

function position(jigsaw,o,o1){
	// Position o where o1 should really be.
	var h = length(jigsaw.sw)/jigsaw.xBits
	var w = length(jigsaw.sh)/jigsaw.yBits
	
	var x = length(jigsaw.sx) - - o1.col*h + length(o.offsetX)
	var y = length(jigsaw.sy) - - o1.row*w + length(o.offsetY)

	o.rowActual = o1.row
	o.colActual = o1.col

	animateTo(o, x, y)
}

function animateTo(o,ex,ey){
	o.style.position = 'absolute'

	if (o.sx!=undefined){
		var u = units(o.sx)
		var sx = length(o.sx)
		var sy = length(o.sy)
		var starttime = time()
		var duration = 1000
		var cmd = "'"+o.id+"','"+u+"','"+sx+"','"+sy+"','"+ex+"','"+ey+"',"+starttime+","+duration

		if (u!=undefined)
			animateLoop(o.id, u, sx, sy, ex, ey, starttime, duration, cmd)
	}
}

function animateLoop(id, u, sx, sy, ex, ey, starttime, duration, cmd){
	var t = time()-starttime
	var o = getElementById(id)
	var style = o.style
	
	if ( t < duration ){
		var factor = t/duration
		var x = sx - - (ex-sx) * factor	// - - to force arithmetic, not concatenation
		var y = sy - - (ey-sy) * factor	// - - to force arithmetic, not concatenation

		style.top = y+u
		style.left = x+u

		setTimeout( "animateLoop("+cmd+",\""+cmd+"\")", frameTime )
	}
	else{
		style.left = o.sx = ex+u
		style.top = o.sy = ey+u
	}
}

function length(o){
	return size(o)
}

function solve1(jigsaw){
	if (jigsaw.solving==undefined){
		jigsaw.solving=true
		var start = time()
		var pieces = jigsaw.pieces
		var rows = new Array(jigsaw.yBits-1)

		for (var i=0;i<jigsaw.yBits;i++)
			rows[i] = new Array(jigsaw.xBits-1)
	
		for(i in pieces){
			var o = pieces[i]

			if (o!=undefined && o.colActual!=undefined){
				var row = rows[o.rowActual]
				row[o.colActual] = i
			}
		}

		var x,y

		for(var j=0;j<rows.length;j++){
			var row = rows[j]

			for (var i=0;i<jigsaw.xBits;i++){
				if (row[i]==undefined){
					x = i
					y = j
					row[i] = "x"
					break
				}
			}
		}

		var root = new node(undefined,jigsaw,0,undefined,rows,x,y)
		root.isSolved()
		jigsaw.playing = false
		root.loopExpand(0,start)
	}
}

var jigsaw

function playJig(j){
	if (j.moves!=undefined){
		jigsaw = j
		// Pick of first number, and reduce length of moves
		var i = jigsaw.moves.indexOf(",")
		var tile = jigsaw.moves.substr(0,i)
		jigsaw.moves = jigsaw.moves.substr(i+1)

		// Convert number to tile
		var o = getElementById(tile)

		if (o!=undefined){
			// emulate click on tile
			blockGame(o,true)

			// loop
			setTimeout("doNext1()",1000)
		}
		else
			jigsaw.solving = undefined
	}
}

function doNext1(){
	if (jigsaw.moves!=undefined)
		playJig(jigsaw)
}

function node(root,jigsaw,level,parent,rows,x,y,direction){
	this.jigsaw = jigsaw
	this.level = level
	this.parent = parent
	this.rows = rows
	this.x = x
	this.y = y
	this.direction = direction

	if (root==undefined)
		this.root = this
	else
		this.root = root

	// member functions
	this.isSolved = isSolved
	this.expand = expand
	this.markRoute = markRoute
	this.exec = exec
	this.exists = exists
	this.loopExpand = loopExpand
}

function doNext(){
	if (jigsaw.next!=undefined)
		jigsaw.next.exec()
}

function exec(){
	jigsaw = this.jigsaw
	jigsaw.next = undefined
	var o = undefined

	if (this.left!=undefined && this.left.solved){
		o = at(jigsaw,this.x-1,this.y)
		o.colActual = this.x
		jigsaw.next = this.left
	}
	else if (this.right!=undefined && this.right.solved){
		o = at(jigsaw,this.x+1,this.y)
		o.colActual = this.x
		jigsaw.next = this.right
	}
	else if (this.up!=undefined && this.up.solved){
		o = at(jigsaw,this.x,this.y-1)
		o.rowActual = this.y
		jigsaw.next = this.up
	}
	else if (this.down!=undefined && this.down.solved){
		o = at(jigsaw,this.x,this.y+1)
		o.rowActual = this.y
		jigsaw.next = this.down
	}

	if (o!=undefined)
		moveBit(o)

	if (jigsaw.next != undefined)
		setTimeout("doNext()",1000)
	else if (this.solved){
		appear(jigsaw.missing,10)
		jigsaw.done = true
		jigsaw.solving = undefined
	}
}

function moveBit(o){
	var jigsaw = o.jigsaw
	var h = length(jigsaw.sw)/jigsaw.xBits
	var w = length(jigsaw.sh)/jigsaw.yBits
	var x = length(jigsaw.sx) - - o.colActual*h + length(o.offsetX)
	var y = length(jigsaw.sy) - - o.rowActual*w + length(o.offsetY)

	animateTo(o, x, y)
}

function markRoute(){
	this.solved = true;

	if (this.parent!=undefined)
		this.parent.markRoute()
}

function isSolved(){
	var jigsaw = this.jigsaw
	var c = jigsaw.xBits
	var r = jigsaw.yBits
	var rows = this.rows

	if (this.x==c-1 && this.y==r-1){	// can only be solved if last square is empty
		for(var j=0;j<r;j++){
			for (var i=0;i<c;i++){
				if ( rows[j][i] != j*c+i){
					if (i==c-1 && j==r-1 && rows[j][i]=="x"){
						this.markRoute()
						return true
					}

					this.solved = false
					return false
				}
			}
		}
	}

	this.solved = false
	return false
}

var nextRoot
var maxTime = 6 * 1000	// limit to 8 seconds

function loopExpand(level,start){
	this.expand(level)
	var root = this.root
	var jigsaw = this.jigsaw

	if (level<16 && !root.solved && time()-start<maxTime){
		document.body.style.cursor = "wait"
		nextRoot = root
		var nextLevel = level+1
		setTimeout("nextRoot.loopExpand(" + nextLevel + "," + start + ")",10)
	}
	else if (!jigsaw.playing){
		// Now, assuming is solved, need to make moves, starting at root
		jigsaw.playing = true
		document.body.style.cursor = "default"

		if (root.solved){
			root.exec()
			return true
		}
		else{
			playJig(jigsaw)
			return false
		}
	}
}

function expand(level,start){
	if (this.solved==false && this.left!=undefined)
		this.left.expand(level,start)

	if (time()-start > maxTime)
		return

	if (this.solved==false && this.right!=undefined)
		this.right.expand(level,start)

	if (time()-start > maxTime)
		return

	if (this.solved==false && this.up!=undefined)
		this.up.expand(level,start)

	if (time()-start > maxTime)
		return

	if (this.solved==false && this.down!=undefined)
		this.down.expand(level,start)

	if (time()-start > maxTime)
		return

	if (this.level==level){
		var root = this.root
		var jigsaw = this.jigsaw
		var x = this.x
		var y = this.y

		// try left
		if (this.direction!="r" && x>0 && this.left==undefined){
			var copy = copyArray(this.rows)
			copy[y][x] = copy[y][x-1]
			copy[y][x-1] = "x"

			if (!root.exists(copy,x-1,y,start,maxTime)){
				this.left = new node(root,jigsaw,this.level+1,this,copy,x-1,y,"l")

				if (this.left.isSolved())
					return
			}
		}

		if (time()-start > maxTime)
			return

		// try right
		if (this.direction!="l" && x<jigsaw.xBits-1 && this.right==undefined){
			var copy = copyArray(this.rows)
			copy[y][x] = copy[y][x+1]
			copy[y][x+1] = "x"

			if (!root.exists(copy,x+1,y,start,maxTime)){
				this.right = new node(root,jigsaw,this.level+1,this,copy,x+1,y,"r")

				if (this.right.isSolved())
					return
			}
		}

		if (time()-start > maxTime)
			return

		// try up
		if (this.direction!="d" && y>0 && this.up==undefined){
			var copy = copyArray(this.rows)
			copy[y][x] = copy[y-1][x]
			copy[y-1][x] = "x"

			if (!root.exists(copy,x,y-1,start,maxTime)){
				this.up =  new node(root,jigsaw,this.level+1,this,copy,x,y-1,"u")

				if (this.up.isSolved())
					return
			}
		}

		if (time()-start > maxTime)
			return

		// try down
		if (this.direction!="u" && this.down==undefined && y<jigsaw.yBits-1 ){
			var copy = copyArray(this.rows)
			copy[y][x] = copy[y+1][x]
			copy[y+1][x] = "x"

			if (!root.exists(copy,x,y+1,start,maxTime)){
				this.down = new node(root,jigsaw,this.level+1,this,copy,x,y+1,"d")

				if (this.down.isSolved())
					return
			}
		}
	}
}

function exists(rows,x,y,start){
	if (this.x==x && this.y==y){
		if (equals(this.rows,rows))
			return true
	}

	if (time()-start > maxTime)
		return true

	if (this.left!=undefined && this.left.exists(rows,x,y))
		return true

	if (time()-start > maxTime)
		return true

	if (this.right!=undefined && this.right.exists(rows,x,y))
		return true

	if (time()-start > maxTime)
		return true

	if (this.up!=undefined && this.up.exists(rows,x,y))
		return true

	if (time()-start > maxTime)
		return true

	if (this.down!=undefined && this.down.exists(rows,x,y))
		return true

	return false
}

function equals(one,two){
	var rows = one.length
	var cols = one[0].length

	for (var j=0;j<rows;j++){
		for (var i=0;i<cols;i++){
			if (one[j][i] != two[j][i])
				return false
		}
	}

	return true
}

function copyArray(rows){
	var r = new Array(rows.length)

	for(var j=0;j<rows.length;j++)
		r[j] = rows[j].slice(0)

	return r
}
