/**
 * jigsawpuzzle-rhill 1.0 (27-May-2009) (c) by Raymond Hill
 *
 * jigsawpuzzle-rhill licensed under a Creative Commons
 * Attribution-Noncommercial-Share Alike 2.5 Canada License.
 * Source: http://www.raymondhill.net/puzzle-rhill.php
 *
 * Sorry for the blend name, I didn't want to spend any time figuring
 * some unique, snappy name.
 *
 * I made this puzzle project simply because I was curious if
 * this could be done using the HTML5 <canvas> tag, and yet
 * still have a smooth and fluid in interface. As far as I can tell,
 * It can :)
 * 
 * The key to have responsive graphics interface is to redraw
 * *only* what is necessary. Thus, when moving a piece of puzzle,
 * it is necessary to redraw only the area intersecting the
 * former position, and the area intersecting the new position.
 * Seems trivial enough, but I couldn't find any canvas-based puzzle
 * which actually did that. Using minimal refresh allows huge amount
 * of puzzle piece: I tried 400 pieces, and except for the
 * initialization phase which takes longer to complete, the interface
 * stays smooth.
 *
 * What I learned:
 * - When using drawImage(), canvas to canvas operations are much
 *   faster than image to canvas operations. Thus it is a good idea
 *   to convert an image object into a canvas object if an image
 *   needs to be draw often onto a canvas object (this holds true
 *   for pattern objects.)
 *
 * Still a work in progress, as I wish to experiment further with
 * this little project.
 * 
 * Revisions:
 * 29-May-2009:
 *     - Added button to toggle on/off the showing of edge pieces only
 *     - Added shuffle button
 *     - When moving a piece, all other pieces fade
 *     - num_rows/num_cols respects more original image w/h ratio
 * 31-May-2009:
 *     - JSlint'ed
 *     - Gotten rid of cloneObject() as seen at
 *       http://www.andrewsellick.com/93/javascript-clone-object-function:
 *       The performance cost is way too high. Copy constructors are
 *       preferred.
 */

/*global self*/
// Sound Manager initialization
if (self.soundManager){
	self.soundManager.debugMode=false; // disable debug output
	self.soundManager.onload=function(){
		self.soundManager.createSound({id:'snap1',url:'12842__schluppipuppie__klick_01.mp3',autoLoad:true});
		self.soundManager.createSound({id:'snap2',url:'12843__schluppipuppie__klick_02.mp3',autoLoad:true});
		self.soundManager.createSound({id:'applaud_low',url:'35102_m1rk0_applause_5sec.mp3',autoLoad:false});
		self.soundManager.createSound({id:'applaud_medium',url:'35104_m1rk0_applause_5sec.mp3',autoLoad:false});
		self.soundManager.createSound({id:'applaud_high',url:'60789_J.Zazvurek_Applause_9s.mp3',autoLoad:false});
		};
	}
else {
	self.soundManager={play:function(){}};
	}

var mthrnd = self.Math.round;
var mthceil = self.Math.ceil;
var mthrand = self.Math.random;
var mthsqrt = self.Math.sqrt;
var mthmx = self.Math.max;
var mthmn = self.Math.min;

// helper functions
function console(s,clear) {
	var consoleObj = self.document.getElementById('console');
	if ( clear ) {
		consoleObj.innerHTML = "";
		}
	consoleObj.innerHTML += s + "<br>";
	}

// Point object
function Point(p) {
	if ( p ) {
		this.x = p.x;
		this.y = p.y;
		}
	else {
		this.x = 0;
		this.y = 0;
		}
	this.offset = function(dx,dy) { this.x += dx; this.y += dy; };
	this.set = function(x,y) { this.x = x; this.y = y; };
	}

// Bounding box object
function Bbox(a,b) {
	if ( a instanceof Bbox ) {
		this.tl = new Point(a.tl);
		this.br = new Point(a.br);
		}
	else {
		this.tl = new Point(a);
		this.br = new Point(b);
		}
	this.union_point = function(p) {
		this.tl.x = mthmn(this.tl.x,p.x);
		this.tl.y = mthmn(this.tl.y,p.y);
		this.br.x = mthmx(this.br.x,p.x);
		this.br.y = mthmx(this.br.y,p.y);
		};
	this.width = function() { return (this.br.x - this.tl.x) + 1; };
	this.height = function() { return (this.br.y - this.tl.y) + 1; };
	this.offset = function(dx,dy) { this.tl.offset(dx,dy); this.br.offset(dx,dy); };
	this.set = function(tl,br) { this.tl = tl; this.br = br; };
	this.does_intersect = function(bb) {
		var isect = new Bbox({x:mthmx(bb.tl.x,this.tl.x),y:mthmx(bb.tl.y,this.tl.y)},{x:mthmn(bb.br.x,this.br.x),y:mthmn(bb.br.y,this.br.y)});
		return isect.width() > 0 && isect.height() > 0;
		};
	this.union = function(bb) {
		this.tl.x = mthmn(this.tl.x,bb.tl.x);
		this.tl.y = mthmn(this.tl.y,bb.tl.y);
		this.br.x = mthmx(this.br.x,bb.br.x);
		this.br.y = mthmx(this.br.y,bb.br.y);
		};
	}

// Region object (collection of [todo:non-overlapping] bounding boxes
function Region() {
	this.bboxes=[];
	this.add = function(tl,br){
		this.bboxes[this.bboxes.length]=new Bbox(tl,br);
		};
	this.fill = function(ctx,fillStyle,clip) {
		ctx.fillStyle = fillStyle;
		for ( var i in this.bboxes ) {
			if ( this.bboxes.hasOwnProperty(i) ) {
				var bbox = this.bboxes[i];
				if ( typeof clip == 'undefined' || !clip || bbox.does_intersect(clip) ) {
					ctx.fillRect(bbox.tl.x,bbox.tl.y,bbox.width(),bbox.height());
					}
				}
			}
		};
	}

// Polygon object
function Polygon(tl,tr,br,bl) {
	this.tl = new Point(tl);
	this.tr = new Point(tr);
	this.br = new Point(br);
	this.bl = new Point(bl);
	this.bbox = new Bbox(tl,br);
	this.bbox.union_point(tr);
	this.bbox.union_point(bl);
	this.offset = function(dx,dy) {
		this.tl.offset(dx,dy);
		this.tr.offset(dx,dy);
		this.br.offset(dx,dy);
		this.bl.offset(dx,dy);
		this.bbox.offset(dx,dy);
		};

	/*!
	 * The following function which test whether a point is inside a
	 * polygone is based on Wm. Randolph Franklin's algorithm, who
	 * allows his work to be reused in others' work as long as the
	 * following notice is included in the work. so here it is:
	 *
	 * =====
	 * Copyright (c) 1970-2003, Wm. Randolph Franklin
	 * Permission is hereby granted, free of charge, to any person
	 * obtaining a copy of this software and associated documentation
	 * files (the "Software"), to deal in the Software without restriction,
	 * including without limitation the rights to use, copy, modify, merge,
	 * publish, distribute, sublicense, and/or sell copies of the Software,
	 * and to permit persons to whom the Software is furnished to do so,
	 * subject to the following conditions:
	 *
	 * 1. Redistributions of source code must retain the above copyright
	 *    notice, this list of conditions and the following disclaimers.
	 * 2. Redistributions in binary form must reproduce the above copyright
	 *    notice in the documentation and/or other materials provided with
	 *    the distribution.
	 * 3. The name of W. Randolph Franklin may not be used to endorse or
	 *    promote products derived from this Software without specific
	 *    prior written permission. 
	 *
	 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
	 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
	 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
	 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
	 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	 * SOFTWARE.
	 * =====
	 * http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
	 */
	this.point_in = function(p) {
		var r = false;
		if (((this.tl.y > p.y) != (this.bl.y > p.y)) && (p.x < (this.bl.x-this.tl.x) * (p.y-this.tl.y) / (this.bl.y-this.tl.y) + this.tl.x) ) { r = !r; }
		if (((this.tr.y > p.y) != (this.tl.y > p.y)) && (p.x < (this.tl.x-this.tr.x) * (p.y-this.tr.y) / (this.tl.y-this.tr.y) + this.tr.x) ) { r = !r; }
		if (((this.br.y > p.y) != (this.tr.y > p.y)) && (p.x < (this.tr.x-this.br.x) * (p.y-this.br.y) / (this.tr.y-this.br.y) + this.br.x) ) { r = !r; }
		if (((this.bl.y > p.y) != (this.br.y > p.y)) && (p.x < (this.br.x-this.bl.x) * (p.y-this.bl.y) / (this.br.y-this.bl.y) + this.bl.x) ) { r = !r; }
		return r;
		};
	}

// Puzzle object
function Puzzle_part(edge,tl,tr,br,bl,img) {
	this.edge = edge; // whether the piece is on the edge
	this.correct = false; // whether the piece is correctly placed
	this.polySrc = new Polygon(tl,tr,br,bl);
	this.polyDes = new Polygon(tl,tr,br,bl);
	this.canvasObj = self.document.createElement('canvas');
	this.canvasObj.width = this.polySrc.bbox.width();
	this.canvasObj.height = this.polySrc.bbox.height();
	// blit from source to canvas
	var ctx = this.canvasObj.getContext('2d');
	ctx.save(this.polySrc.tl.x-this.polySrc.bbox.tl.x,this.polySrc.tl.y-this.polySrc.bbox.tl.y);
	ctx.beginPath();
	ctx.moveTo(this.polySrc.tl.x-this.polySrc.bbox.tl.x,this.polySrc.tl.y-this.polySrc.bbox.tl.y);
	ctx.lineTo(this.polySrc.tr.x-1-this.polySrc.bbox.tl.x,this.polyDes.tr.y-this.polySrc.bbox.tl.y);
	ctx.lineTo(this.polySrc.br.x-1-this.polySrc.bbox.tl.x,this.polySrc.br.y-1-this.polySrc.bbox.tl.y);
	ctx.lineTo(this.polySrc.bl.x-this.polySrc.bbox.tl.x,this.polySrc.bl.y-1-this.polySrc.bbox.tl.y);
	ctx.lineTo(this.polySrc.tl.x-this.polySrc.bbox.tl.x,this.polySrc.tl.y-this.polySrc.bbox.tl.y);
	ctx.closePath();
	ctx.clip();
	ctx.drawImage(img,this.polySrc.bbox.tl.x,this.polySrc.bbox.tl.y,this.polySrc.bbox.width(),this.polySrc.bbox.height(),0,0,this.polySrc.bbox.width(),this.polySrc.bbox.height());
	ctx.restore();
	}

// Puzzle object
function Puzzle(id,puzzleOptions) {
	var me = this;
	this.canvasObj = self.document.createElement('canvas');
	this.canvasObj.width = '1040';
	this.canvasObj.height = '840';
	if ( typeof this.canvasObj.getContext == 'undefined' ) { return; }
	var canvasParent=self.document.getElementById('canvasParent');
	canvasParent.style.backgroundColor='#777';
	canvasParent.style.width=this.canvasObj.width+'px';
	canvasParent.style.height=this.canvasObj.height+'px';
	canvasParent.innerHTML='';
	canvasParent.appendChild(this.canvasObj);
	this.imoved = -1; // the (drawing stack) index of the part being moved
	this.movedAnchor = new Point(); // the distance of the mouse position relative to the top-left corner of the piece being moved
	this.showEdges = false;
	this.puzzleOptions = puzzleOptions;
	// handle user options
	self.document.getElementById("puzzleShowEdges").onclick = function() {
		me.showEdges = !me.showEdges;
		this.value = (me.showEdges)?"Show all pieces":"Show edge pieces only";
		me.draw();
		};
	// default drawing stack
	this.drawing_stack=[];
	// default puzzle frame
	this.bedFrame = new Region();
	this.bedFrame.add({x:0,y:0},{x:this.canvasObj.width,y:this.canvasObj.height});
	// default puzzle bed
	this.bbox_img = new Bbox();
	// background image for puzzle frame
	var bgFrm = new self.Image();
	bgFrm.onload = function(){
		me.bgCanvas = self.document.createElement('canvas');
		me.bgCanvas.width = this.width;
		me.bgCanvas.height = this.height;
		me.bgCanvas.getContext('2d').drawImage(bgFrm,0,0,me.bgCanvas.width,me.bgCanvas.height);
		this.onload = null;
		me.draw(); // force redraw now that we have our background image ready
		};
	bgFrm.src = 'bg-b19marbles013.jpg'; // Source: http://www.imageafter.com/category.php?category=marbles
	// background image for puzzle bed
	var bgBed = new self.Image();
	bgBed.onload = function(){
		me.bgBed = self.document.createElement('canvas');
		me.bgBed.width = this.width;
		me.bgBed.height = this.height;
		me.bgBed.getContext('2d').drawImage(bgBed,0,0,me.bgBed.width,me.bgBed.height);
		this.onload = null;
		me.draw(); // force redraw now that we have our background image ready
		};
	bgBed.src = 'checkerboard.png'; // Source: http://media.photobucket.com/image/transparent%20pattern%20background/bitwierd/Chequerboard.png

	this.shuffle = function() {
		for ( var ipart = 0; ipart < this.drawing_stack.length; ipart++ ) {
			var partref = me.drawing_stack[ipart];
			// random canvas position
			var canvas_x = mthrnd(mthrand() * (me.canvasObj.width - partref.polyDes.bbox.width()));
			var canvas_y = mthrnd(mthrand() * (me.canvasObj.height - partref.polyDes.bbox.height()));
			// clip to current polygon
			var dx = canvas_x - partref.polyDes.bbox.tl.x;
			var dy = canvas_y - partref.polyDes.bbox.tl.y;
			// save canvas polygon
			partref.polyDes.offset(dx,dy);
			partref.correct = false;
			}
		};
	this.draw = function(bbox) {
		var ctx = me.canvasObj.getContext('2d');
		ctx.save();
		// draw only what intersect with bbox
		if ( bbox ) {
			ctx.beginPath();
			ctx.moveTo(bbox.tl.x,bbox.tl.y);
			ctx.lineTo(bbox.br.x,bbox.tl.y);
			ctx.lineTo(bbox.br.x,bbox.br.y);
			ctx.lineTo(bbox.tl.x,bbox.br.y);
			ctx.closePath();
			ctx.clip();
			}
		// canvas area
		if ( !me.bgPat && typeof me.bgCanvas!='undefined' ) { 
			me.bgPat = ctx.createPattern(me.bgCanvas,'repeat');
			}
		me.bedFrame.fill(ctx,typeof me.bgPat!='undefined'?me.bgPat:self.document.getElementById('canvasParent').style.backgroundColor,bbox);
		// puzzle bed area
		if ( !me.bgBedPattern && typeof me.bgBed!='undefined' ) { 
			me.bgBedPattern = ctx.createPattern(me.bgBed,'repeat');
			}
		ctx.fillStyle = typeof me.bgBedPattern!='undefined'?me.bgBedPattern:'#fff';
		ctx.fillRect(me.bbox_img.tl.x,me.bbox_img.tl.y,me.bbox_img.width(),me.bbox_img.height());
		// puzzle parts
		ctx.globalCompositeOperation = "source-over";
		// if solved, display original picture
		if ( me.is_solved() ) {
			ctx.drawImage(me.imageObj,me.bbox_img.tl.x,me.bbox_img.tl.y,me.bbox_img.width(),me.bbox_img.height());
			}
		// else display parts
		else {
			for ( var ipart = 0; ipart < me.drawing_stack.length; ipart++ ) {
				var partref = me.drawing_stack[ipart];
				if ( !bbox || partref.polyDes.bbox.does_intersect(bbox) ) {
					ctx.globalAlpha = ((me.imoved>=0&&ipart!=me.imoved&&!partref.correct)||(me.showEdges&&!partref.edge))?0.1:1.0;
					ctx.drawImage(partref.canvasObj,partref.polyDes.bbox.tl.x,partref.polyDes.bbox.tl.y,partref.polySrc.bbox.width(),partref.polySrc.bbox.height());
					// draw 3d-edges. One unit added/subtracted as it seems (on FFox) antialiasing disregards clipping region (I might be wrong)
					if ( !partref.correct ) {
						ctx.strokeStyle = '#ccc';
						ctx.beginPath();
						ctx.moveTo(partref.polyDes.bl.x+1,partref.polyDes.bl.y-1);
						ctx.lineTo(partref.polyDes.tl.x+1,partref.polyDes.tl.y+1);
						ctx.lineTo(partref.polyDes.tr.x-1,partref.polyDes.tr.y+1);
						ctx.stroke();
						ctx.strokeStyle = '#444';
						ctx.beginPath();
						ctx.moveTo(partref.polyDes.tr.x-1,partref.polyDes.tr.y+1);
						ctx.lineTo(partref.polyDes.br.x-1,partref.polyDes.br.y-1);
						ctx.lineTo(partref.polyDes.bl.x+1,partref.polyDes.bl.y-1);
						ctx.stroke();
						}
					}
				}
			}
		ctx.restore();
		};
	this.init = function() {
		var img_width = me.img.width;
		var img_height = me.img.height;
		var img_ratio = img_width / img_height;
		// if image size < 80px, zoom in
		if ( img_width < 80 || img_height < 80 ) {
			if ( img_ratio >= 1.0 ) {
				img_width = 80;
				img_height = mthrnd(img_width/img_ratio)|0;
				}
			else {
				img_height = 80;
				img_width = mthrnd(img_height*img_ratio)|0;
				}
			}
		else if ( img_width > (me.canvasObj.width-40) || img_height > (me.canvasObj.height-40) ) {
			if ( img_ratio >= 1.0 ) {
				img_width = me.canvasObj.width-40;
				img_height = mthrnd(img_width/img_ratio)|0;
				}
			else {
				img_height = me.canvasObj.height-40;
				img_width = mthrnd(img_height*img_ratio)|0;
				}
			}
		if ( img_width < 80 || img_height < 80 || img_width > (me.canvasObj.width-40) || img_height > (me.canvasObj.height-40) ) {
			console("Because of its size, this image can't be used as a puzzle");
			return;
			}
		console("Original image size (w x h): "+me.img.width+"px &times; "+me.img.height+"px");
		if ( img_width != me.img.width || img_height != me.img.height ) {
			console("Resized to "+img_width+"px &times; "+img_height+"px");
			}
		// create the image that will be used as a basis for the puzzle
		me.imageObj = self.document.createElement('canvas');
		me.imageObj.width = img_width;
		me.imageObj.height = img_height;
		var ctx = me.imageObj.getContext('2d');
		ctx.drawImage(me.img,0,0,me.img.width,me.img.height,0,0,img_width,img_height);
		// center puzzle bed in canvas
		me.bbox_img = new Bbox({x:0,y:0},{x:me.imageObj.width-1,y:me.imageObj.height-1});
		me.bbox_img.offset((me.canvasObj.width-me.imageObj.width)>>1,(me.canvasObj.height-me.imageObj.height)>>1);
		// build region for area surrounding puzzle bed
		me.bedFrame = new Region();
		me.bedFrame.add({x:0,y:0},{x:me.canvasObj.width,y:me.bbox_img.tl.y}); // top
		me.bedFrame.add({x:0,y:me.bbox_img.tl.y},{x:me.bbox_img.tl.x,y:me.canvasObj.height}); // left
		me.bedFrame.add({x:me.bbox_img.br.x,y:me.bbox_img.tl.y},{x:me.canvasObj.width,y:me.canvasObj.height}); // right
		me.bedFrame.add({x:me.bbox_img.tl.x,y:me.bbox_img.br.y},{x:me.bbox_img.br.x,y:me.canvasObj.height}); // bottom
		// following will be used to generate puzzle pieces
		var num_cols = mthmn(mthmx(mthceil(mthsqrt(me.puzzleOptions.puzzlePieces)*mthsqrt(img_ratio)),2),mthrnd(this.width/40))|0; // pieces can't be smaller than 40px
		var num_rows = mthmn(mthmx(mthceil(mthsqrt(me.puzzleOptions.puzzlePieces)/mthsqrt(img_ratio)),2),mthrnd(this.height/40))|0; // pieces can't be smaller than 40px
		console("Actual number of pieces: "+num_cols*num_rows+" ("+num_cols+" &times; "+num_rows+" pieces)");
		var part_width = mthrnd(me.bbox_img.width()/num_cols)|0; // rounded to avoid fractional pixels
		var part_height = mthrnd(me.bbox_img.height()/num_rows)|0; // rounded to avoid fractional pixels
		var part_width_var = mthrnd(part_width * mthmx(mthmn(me.puzzleOptions.puzzleComplexity,9),0) * 0.48 / 9);
		var part_height_var = mthrnd(part_height * mthmx(mthmn(me.puzzleOptions.puzzleComplexity,9),0) * 0.48 / 9);
		var random_part_x = function(part_index) { return part_width*part_index+mthrnd(part_width_var*2*mthrand())-part_width_var; };
		var random_part_y = function(part_index) { return part_height*part_index+mthrnd(part_height_var*2*mthrand())-part_height_var; };
		// drawing stack
		me.drawing_stack=[];
		// generate parts
		var parts=[num_rows];
		for ( var row = 0; row < num_rows; row++ ) {
			parts[row] = [num_cols];
			for ( var col = 0; col < num_cols; col++ ) {
				me.drawing_stack[me.drawing_stack.length] = parts[row][col] = new Puzzle_part(
					(row===0)||(col===0)||(row==num_rows-1)||(col==num_cols-1),
					(col===0)?((row===0)?{x:0,y:0}:parts[row-1][0].polySrc.bl):parts[row][col-1].polySrc.tr,
					(row===0)?{x:(col==num_cols-1)?me.bbox_img.width():random_part_x(col+1),y:0}:parts[row-1][col].polySrc.br,
					{x:(col==num_cols-1)?me.bbox_img.width():random_part_x(col+1),y:(row==num_rows-1)?me.bbox_img.height():random_part_y(row+1)},
					(col===0)?{x:0,y:(row==num_rows-1)?me.bbox_img.height():random_part_y(row+1)}:parts[row][col-1].polySrc.br,
					me.imageObj);
				}
			}
		me.shuffle();
		me.draw();
		};
	this.part_under_point = function(p) {
		for ( var ipart = this.drawing_stack.length-1; ipart >= 0; ipart-- ) {
			var partref = me.drawing_stack[ipart];
			if ( (!partref.correct) &&
			     (!me.showEdges || partref.edge) &&
			     (partref.polyDes.point_in(p)) ) { return ipart; }
			}
		return -1;
		};
	this.send_back = function(ipart) {
		if ( ipart >= 0 ) {
			var movedPart = me.drawing_stack[ipart];
			me.drawing_stack.splice(ipart,1);
			me.drawing_stack.unshift(movedPart);
			}
		};
	this.send_top = function(ipart) {
		if ( ipart >= 0 ) {
			var movedPart = me.drawing_stack[ipart];
			me.drawing_stack.splice(ipart,1);
			me.drawing_stack[me.drawing_stack.length] = movedPart;
			}
		};

	// complete initialization of object
	// setup new image
	this.img = self.document.getElementById('puzzleImage');
	this.img.onload = me.init;
	//this.img.src = puzzleOptions.puzzleURL;

	// normalize event position
	// based on Quirksmode.org's Peter-Paul Koch
	// http://www.quirksmode.org/js/events_properties.html#position
	function normalizeEventPos(e) {
		if (!e) {
			e = self.event;
			}
		var pos = new Point();
		if (e.pageX || e.pageY) {
			pos.x = e.pageX;
			pos.y = e.pageY;
			}
		else if (e.clientX || e.clientY) {
			pos.x = e.clientX + self.document.body.scrollLeft + self.document.documentElement.scrollLeft;
			pos.y = e.clientY + self.document.body.scrollTop + self.document.documentElement.scrollTop;
			}
		pos.x -= me.canvasObj.offsetLeft;
		pos.y -= me.canvasObj.offsetTop;
		return pos;
		}
	// event handlers
	this.canvasObj.onclick = function(e) {
		var moved;
		if ( me.imoved >= 0 ) {
			moved = me.drawing_stack[me.imoved];
			// snap into place?
			if ( self.Math.abs(moved.polyDes.bbox.tl.x - me.bbox_img.tl.x - moved.polySrc.bbox.tl.x) < 5 &&
			     self.Math.abs(moved.polyDes.bbox.tl.y - me.bbox_img.tl.y - moved.polySrc.bbox.tl.y) < 5 ) {
				self.soundManager.play('snap1');
				// send at the bottom of the drawing stack
				me.send_back(me.imoved);
				moved.correct = true;
				me.imoved = -1;
				// new position
				var dx = moved.polySrc.bbox.tl.x + me.bbox_img.tl.x - moved.polyDes.bbox.tl.x;
				var dy = moved.polySrc.bbox.tl.y + me.bbox_img.tl.y - moved.polyDes.bbox.tl.y;
				moved.polyDes.offset(dx,dy);
				// if top part is correct, puzzle is solved
				if ( me.is_solved() ) {
					//self.soundManager.play('applaud');
					}
				}
			else {
				me.imoved = -1;
				}
			me.draw();
			}
		else {
			var pos = normalizeEventPos(e);
			var ipart = me.part_under_point({x:pos.x,y:pos.y});
			if ( ipart >= 0 ) {
				// bring on top
				me.send_top(ipart);
				me.imoved = me.drawing_stack.length-1;
				moved = me.drawing_stack[me.imoved];
				me.movedAnchor.x = pos.x - moved.polyDes.bbox.tl.x;
				me.movedAnchor.y = pos.y - moved.polyDes.bbox.tl.y;
				me.draw();
				}
			}
		};
	this.canvasObj.onmousemove = function(e) {
		if ( me.imoved < 0 ) { return; }
		var pos = normalizeEventPos(e);
		var moved = me.drawing_stack[me.imoved];
		// old position
		var draw_bbox = new Bbox(moved.polyDes.bbox);
		// new position
		var dx = pos.x - me.movedAnchor.x - moved.polyDes.bbox.tl.x;
		var dy = pos.y - me.movedAnchor.y - moved.polyDes.bbox.tl.y;
		moved.polyDes.offset(dx,dy);
		if ( draw_bbox.does_intersect(moved.polyDes.bbox) ) {
			draw_bbox.union(moved.polyDes.bbox);
			me.draw(draw_bbox);
			}
		else {
			me.draw(draw_bbox);
			me.draw(moved.polyDes.bbox);
			}
		};
	this.is_solved = function() {
		return me.drawing_stack.length > 0 && me.drawing_stack[me.drawing_stack.length-1].correct;
		};
	}
