// Anyone up for a game of "Count the IE hacks"?

// This is required because MSIE doesn't understand how to handle events properly.
// http://ejohn.org/blog/flexible-javascript-events/
function addEvent( obj, type, fn ) {
  if ( obj.attachEvent ) {
    obj['e'+type+fn] = fn;
    obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
    obj.attachEvent( 'on'+type, obj[type+fn] );
  } else
    obj.addEventListener( type, fn, false );
}
function removeEvent( obj, type, fn ) {
  if ( obj.detachEvent ) {
    obj.detachEvent( 'on'+type, obj[type+fn] );
    obj[type+fn] = null;
  } else
    obj.removeEventListener( type, fn, false );
}

// This is required because MSIE doesn't understand how to handle setInterval and setTimeout functions.
// http://webreflection.blogspot.com/2007/06/simple-settimeout-setinterval-extra.html
/*@cc_on
(function(f){
 window.setTimeout =f(window.setTimeout);
 window.setInterval =f(window.setInterval);
})(function(f){return function(c,t){var a=[].slice.call(arguments,2);return f(function(){c.apply(this,a)},t)}});
@*/




function imageObject(src){
	// The rotation (in degrees) of the object. 0-360
	this.rotation = 0;
	// The current X position (relative to topleft corner) of the image.
	this.x = 0;
	// The current Y position (relative to topleft corner) of the image.
	this.y = 0;
	// The image itself.
	this._img = new Image();
	this._img.object = this;
	// A flag that gets set when the data for the image has loaded.
	this.ready = false;

	// The function that fires when the user clicks on this image.
	this.clickfn = function(){ };

	// The z-index of the item, behaves just like z-indexes of webpage elements.
	this.zindex = 1;

	addEvent(this._img, 'load', function(e){
		// In the event listener, 'this' points to the image, not the object controlling the image.
		this.object.ready = true;
	}, false);

	this._img.src = src;
	// Parent will be the canvas object this object is bound to.
	this.parent;

	this.getRotation = function(){ return this.rotation; };
	this.getX = function(){ return this.x || 0; };
	this.getY = function(){ return this.y || 0; };
	this.getWidth = function(){ return this._img.width; };
	this.getHeight = function(){ return this._img.height; };
	this.getImage = function(){ return this._img; };

	this.setRotation = function(newrot, speed, fn){
		if(typeof speed == 'undefined') speed = 0;

		fn = fn || function(){ };

		// Call the helper function to trigger events.
		this._setPos(false, false, newrot, speed, fn);
	}

	this.setPosition = function(whereto, speed, fn){
		//console.log('Setting location for this element to ' + whereto);
		// whereto can be left, middle or right.
		//whereto = whereto.toLowerCase();

		if(typeof speed == 'undefined') speed = 0;

		fn = fn || function(){ };

		var newx = this.getX();
		var newy = this.getY();
		var newr = this.getRotation();

		// Multiple commands can be sent, each separated by a comma (,)
		cmds = whereto.toLowerCase().split(',');
		var i = 0;
		var c = cmds.length;
		while(i < c){
			// Possible example incoming strings include:
			// 10deg
			// top
			// 50px from left
			// right
			// 50% from bottom
			// middle
			// center


			// Trim any whitespace.
			cmds[i] = cmds[i].replace(/^[ ]*/, '').replace(/[ ]*$/, '');

			//console.log(cmds[i]);


			// Vertically middle.  Easy one.
			if(cmds[i] == 'middle'){
				newy = (this.parent.getHeight() / 2) - (this._img.height / 2);
			}
			// Horizontally middle.
			else if(cmds[i] == 'center'){
				newx = this.parent.getWidth() / 2 - this._img.width / 2;
			}
			// Left aligned
			else if(cmds[i] == 'left'){
				newx = 0;
			}
			// Right aligned
			else if(cmds[i] == 'right'){
				newx = this.parent.getWidth() - this._img.width;
			}

			// Degrees of rotation.
			else if(cmds[i].match(/^[0-9]*deg/)){
				val = parseInt(cmds[i].replace(/[^0-9]/g, ''));
				newr = val;
			}

			// Set pixels from the left border.
			else if(cmds[i].match(/[0-9]*px from left/)){
				val = parseInt(cmds[i].replace(/[^0-9]/g, '')); // Only need the number.
				newx = 0 + val;
			}
			// Set pixels from the right border.
			else if(cmds[i].match(/[0-9]*px from right/)){
				val = parseInt(cmds[i].replace(/[^0-9]/g, '')); // Only need the number.
				newx = this.parent.getWidth() - this._img.width - val;
			}
			// Set pixels from the top border.
			else if(cmds[i].match(/[0-9]*px from top/)){
				val = parseInt(cmds[i].replace(/[^0-9]/g, '')); // Only need the number.
				newy = 0 + val;
			}
			// Set pixels from the bottom border.
			else if(cmds[i].match(/[0-9]*px from bottom/)){
				val = parseInt(cmds[i].replace(/[^0-9]/g, '')); // Only need the number.
				newy = this.parent.getHeight() - this._img.height - val;
			}

			// Percentage from the left border.
			else if(cmds[i].match(/[0-9]*% from left/)){
				val = parseInt(cmds[i].replace(/[^0-9]/g, '')); // Only need the number.
				val = this.parent.getWidth() * val / 100;
				newx = 0 + val;
			}
			// Percentage from the right border.
			else if(cmds[i].match(/[0-9]*% from right/)){
				val = parseInt(cmds[i].replace(/[^0-9]/g, '')); // Only need the number.
				val = this.parent.getWidth() * val / 100;
				newx = this.parent.getWidth() - this._img.width - val;
			}
			// Percentage from the top border.
			else if(cmds[i].match(/[0-9]*% from top/)){
				val = parseInt(cmds[i].replace(/[^0-9]/g, '')); // Only need the number.
				val = this.parent.getHeight() * val / 100;
				newy = 0 + val;
			}
			// Percentage from the bottom border.
			else if(cmds[i].match(/[0-9]*% from bottom/)){
				val = parseInt(cmds[i].replace(/[^0-9]/g, '')); // Only need the number.
				val = this.parent.getHeight() * val / 100;
				newy = this.parent.getHeight() - this._img.height - val;
			}

			i++;
		}


		// Call the helper function to trigger events.
		this._setPos(newx, newy, newr, speed, fn);
	};

	// This is useful for moving an image to the top, bottom, etc.
	this.setLayer = function(layer){

		if(layer == 'top') layer = 1000;
		else if(layer == 'bottom') layer = 1;

		this.zindex = layer;
		this.parent.resetZ();

		return;

		layer = layer.toLowerCase();
		if(layer == 'top'){
			// Move this image to the end of the stack.  This will render it last.
			var i = 0;
			var c = this.parent.elements.length;
			while(i < c){
				if(this.parent.elements[i] == this){
					this.parent.elements.splice(i, 1);
					break;
				}
				i++;
			}
			this.parent.elements.push(this);
			this.parent.dirty = true;
		}
		else if(layer == 'bottom'){
			// Move this image to the end of the stack.  This will render it last.
			var i = 0;
			var c = this.parent.elements.length;
			while(i < c){
				if(this.parent.elements[i] == this){
					this.parent.elements.splice(i, 1);
					break;
				}
				i++;
			}
			this.parent.elements.unshift(this);
			this.parent.dirty = true;
		}
	};

	this._setPos = function(newx, newy, newr, speed, fn){
		// 0 speed means instant.
		if(typeof speed == 'undefined') speed = 0;
		if(speed == 'fast') speed = 6;
		else if(speed == 'slow') speed = 40;

		fn = fn || function(){ };

		if(newx === false) newx = this.x;
		if(newy === false) newy = this.y;
		if(newr === false) newr = this.rotation;

		// I can set them now!
		if(speed <= 1){
			this.x = newx;
			this.y = newy;
			this.rotation = newr;
			this.parent.dirty = true;
			fn();
		}
		else{
			// So a speed of 1
			var stepsx = (newx - this.x) / speed;
			var stepsy = (newy - this.y) / speed;
			var stepsr = (newr - this.rotation) / speed;

			// Step through every 0.1 seconds.  This will set a speed of 10 to be 1 second, 50 to be 5 seconds, etc.
			var step = 0;
			var interval = setInterval(function(obj, stepsx, stepsy, stepsr, fn){
				if(step >= speed){
					clearInterval(interval);
					//obj.x = newx;
					//obj.y = newy;
					//obj.rotation = newr;
					// Call the requested function on complete.
					// Reset "this" back to the original object.
					fn(obj);
					return;
				}
				step += 1;
				//console.log('Running step ' + step);
				obj.x += stepsx;
				obj.y += stepsy;
				obj.rotation += stepsr;
				obj.parent.dirty = true;
				return true;
			}, 50, this, stepsx, stepsy, stepsr, fn);
		}

	};

	this.click = function(fn){
		if(typeof fn == 'undefined'){
			// Fire off the click function already set.
			this.clickfn();
		}
		else if(typeof fn == 'function'){
			this.clickfn = fn;
		}
		else{
			// What do I do?
		}
	};
};



function canvasRendererMSIE(id, refresh){
	this._canvas = document.getElementById(id);
	if(!this._canvas) return false;
	this._canvas.object = this;

	this.elements = [];

	// Indicates when all elements on the canvas have loaded.
	this.ready = false;

	// Number of milliseconds to wait between frame refreshes.
	this.refreshms = refresh || 50;

	this.addElement = function(el){
		this.elements.push(el);
		// Notify the element too.
		el.parent = this;
	};

	this.getWidth = function(){ return this._canvas.width; };
	this.getHeight = function(){ return this._canvas.height; };

	// Run the initialization process, this will continually scan all elements and wait till they're all available.
	this.init = function(functioncallonload){
		functioncallonload = functioncallonload || function(){ };
		var interval = setInterval(function(o, f){
			if(o.ready) return false;
			var r = true;
			var c = o.elements.length;
			var ic = 0;
			while(ic < c){
				if(!o.elements[ic].ready){
					r = false;
					break;
				}
				ic++;
			}

			if(r){
				o.ready = true;
				clearInterval(interval);

				// Trigger the click listener.
				addEvent(o._canvas, 'click', function(e){
					// In the event listener, 'this' points to the canvas, not the object controlling the image.
					this.object.triggerClick(e);
				}, false);

				// Update the zindexes.
				o.resetZ();

				setInterval(function(){ o.draw(); }, o.refreshms);

				f();
				return false;
			}
		}, 100, this, functioncallonload);
	};

	this.draw = function(){
		var canvasw = this.getWidth();
		var canvash = this.getHeight();

		var c = this.elements.length;
		var i = 0;
		while(i < c){
			// Get this element's dimensions.
			//var r = (this.elements[i].getRotation() == 0)? 0 : this.elements[i].getRotation() * Math.PI / 180;
			var r = 0;
			var hx = this.elements[i].getWidth() / 2;
			var hy = this.elements[i].getHeight() / 2;
			var cx = hx + this.elements[i].getX();
			var cy = hy + this.elements[i].getY();
//console.log(cx); console.log(cy);
return;
			// By calling translate, this effectively places all activities at this point.
			this._ctx.translate(cx, cy);
			this._ctx.rotate(r);
			// @todo Add support for additional elements, other than just images.
			this._ctx.drawImage(this.elements[i].getImage(), 0-hx, 0-hy);
			//ctx.drawImage(this._img, 0, 0);
			// I need to move the canvas back to where it was for the next pass.
			this._ctx.rotate(0 - r);
			this._ctx.translate(0-cx, 0-cy);
			iz++;
		}
	};

	this.createImage = function(url){
		var img = new imageObject(url);
		this.addElement(img);
		return img;
	};

	this.triggerClick = function(e){
		//console.log(e);
		//var x = e.offsetX || e.clientX;
		//var y = e.offsetY || e.clientY;

		var x = e.offsetX ? (e.offsetX) : e.pageX - e.currentTarget.offsetLeft;
		var y = e.offsetY ? (e.offsetY) : e.pageY - e.currentTarget.offsetTop;
		//console.log(pos_x);
		//console.log(pos_y);

		// I need to find if the user clicked on an actual item.
		var rarr = this.zindexes.slice();
		rarr.reverse(); // Start from the top of the stack and work backwards.

		var iz = 0;
		var c = rarr.length;
		while(iz < c){
			var i = rarr[iz];

			var cx = this.elements[i].getX();
			var cw = this.elements[i].getWidth();
			var cy = this.elements[i].getY();
			var ch = this.elements[i].getHeight();
			// @todo Add support for rotation in this algorithm.
			if(x > cx && x < cx + cw && y > cy && y < cy + ch){
				// w00t, found one!
				this.elements[i].click();
				break;
			}
			iz++;
		}
	};

	this.resetZ = function(){
		// Reset the z-indexes of the elements.
		// This is an external function to make the draw() quicker by not having to have it check them on every frame refresh.
		var i = 0;
		var c = this.elements.length;
		var elzs = [];
		this.zindexes = [];
		while(i < c){
			elzs.push({index:i, z:this.elements[i].zindex});
			i++;
		}
		elzs.sort(function(a, b){
			return a.z - b.z;
		});
		i = 0;
		c = elzs.length;
		while(i < c){
		//for(i in elzs){
			this.zindexes.push(elzs[i].index);
			i++;
		}
		this.dirty = true;
	};
}



function canvasRenderer(id, refresh){
	this._canvas = document.getElementById(id);
	if(!this._canvas) return false;
	this._canvas.object = this;

	this._ctx = this._canvas.getContext('2d');

	// The time (in milliseconds), the canvas was last drawn.
	this._lastdrawn = 0;

	this.elements = [];

	this.zindexes = [];

	// Indicates when all elements on the canvas have loaded.
	this.ready = false;

	// Indicates if elements have changed position, style, etc.
	// If false, no drawing is required.
	this.dirty = true;

	// Number of milliseconds to wait between frame refreshes.
	this.refreshms = refresh || 50;

	this.addElement = function(el){
		this.elements.push(el);
		// Notify the element too.
		el.parent = this;
	};

	this.getWidth = function(){ return this._canvas.width; };
	this.getHeight = function(){ return this._canvas.height; };

	// Run the initialization process, this will continually scan all elements and wait till they're all available.
	this.init = function(functioncallonload){
		functioncallonload = functioncallonload || function(){ };
		var interval = setInterval(function(o, f){
			if(o.ready) return false;
			var r = true;
			var c = o.elements.length;
			var ic = 0;
			while(ic < c){
				if(!o.elements[ic].ready){
					r = false;
					break;
				}
				ic++;
			}

			if(r){
				o.ready = true;
				clearInterval(interval);

				// Trigger the click listener.
				addEvent(o._canvas, 'click', function(e){
					// In the event listener, 'this' points to the canvas, not the object controlling the image.
					this.object.triggerClick(e);
				}, false);

				// Update the zindexes.
				o.resetZ();

				setInterval(function(){ o.draw(); }, o.refreshms);

				f();
				return false;
			}
		}, 100, this, functioncallonload);
	};

	this.draw = function(){
		if(!this.dirty) return;

		this.dirty = false;
		//console.log('DRAW!');

		var canvasw = this.getWidth();
		var canvash = this.getHeight();

		this._ctx.clearRect(0, 0, canvasw, canvash); // clear canvas

		var c = this.zindexes.length;
		var iz = 0;
		while(iz < c){
			var i = this.zindexes[iz];
		//for(i in this.elements){
			// Get this element's dimensions.
			var r = (this.elements[i].getRotation() == 0)? 0 : this.elements[i].getRotation() * Math.PI / 180;
			var hx = this.elements[i].getWidth() / 2;
			var hy = this.elements[i].getHeight() / 2;
			var cx = hx + this.elements[i].getX();
			var cy = hy + this.elements[i].getY();
//console.log(cx); console.log(cy);
			// By calling translate, this effectively places all activities at this point.
			this._ctx.translate(cx, cy);
			this._ctx.rotate(r);
			// @todo Add support for additional elements, other than just images.
			this._ctx.drawImage(this.elements[i].getImage(), 0-hx, 0-hy);
			//ctx.drawImage(this._img, 0, 0);
			// I need to move the canvas back to where it was for the next pass.
			this._ctx.rotate(0 - r);
			this._ctx.translate(0-cx, 0-cy);
			iz++;
		}
	};

	this.createImage = function(url){
		var img = new imageObject(url);
		this.addElement(img);
		return img;
	};

	this.triggerClick = function(e){
		//console.log(e);
		//var x = e.offsetX || e.clientX;
		//var y = e.offsetY || e.clientY;

		var x = e.offsetX ? (e.offsetX) : e.pageX - e.currentTarget.offsetLeft;
		var y = e.offsetY ? (e.offsetY) : e.pageY - e.currentTarget.offsetTop;
		//console.log(pos_x);
		//console.log(pos_y);

		// I need to find if the user clicked on an actual item.
		var rarr = this.zindexes.slice();
		rarr.reverse(); // Start from the top of the stack and work backwards.

		var iz = 0;
		var c = rarr.length;
		while(iz < c){
			var i = rarr[iz];

			var cx = this.elements[i].getX();
			var cw = this.elements[i].getWidth();
			var cy = this.elements[i].getY();
			var ch = this.elements[i].getHeight();
			// @todo Add support for rotation in this algorithm.
			if(x > cx && x < cx + cw && y > cy && y < cy + ch){
				// w00t, found one!
				this.elements[i].click();
				break;
			}
			iz++;
		}
	};

	this.resetZ = function(){
		// Reset the z-indexes of the elements.
		// This is an external function to make the draw() quicker by not having to have it check them on every frame refresh.
		var i = 0;
		var c = this.elements.length;
		var elzs = [];
		this.zindexes = [];
		while(i < c){
			elzs.push({index:i, z:this.elements[i].zindex});
			i++;
		}
		elzs.sort(function(a, b){
			return a.z - b.z;
		});
		i = 0;
		c = elzs.length;
		while(i < c){
		//for(i in elzs){
			this.zindexes.push(elzs[i].index);
			i++;
		}
		this.dirty = true;
	};
};
