var Canvas = window.Canvas || {};

(function () {

	/**
	 * Canvas Element Class
	 *
	 * @namespace Canvas.Element
	 * @class Element
	 * @constructor
	 * @param el {HTMLElement | String} Container element for the canvas.
	 */
	Canvas.Element = function(el, oConfig) {
		/// this.rotateImage = new YAHOO.util.CustomEvent('rotateImage', this);
		this._initElement(el);
		this._initConfig(oConfig);
		this._createContainer();
		this._initEvents();
		this._initCustomEvents();
	};
	
	function getVerifiedElement() {
		
	}
	
	/**
	 * From YUI
     * We want to be able to use getElementsByTagName as a collection
     * to attach a group of events to.  Unfortunately, different 
     * browsers return different types of collections.  This function
     * tests to determine if the object is array-like.  It will also 
     * fail if the object is an array, but is empty.
     * @method _isValidCollection
     * @param o the object to test
     * @return {boolean} true if the object is array-like and populated
     * @static
     * @private
     */
    function isValidCollection(o) {
        try {
            return ( o                     && // o is something
                     typeof o !== "string" && // o is not a string
                     o.length              && // o is indexed
                     !o.tagName            && // o is not an HTML element
                     !o.alert              && // o is not a window
                     typeof o[0] !== "undefined" );
        } catch(ex) {
            return false;
        }
    }

	/**
	 * Constant for the default CSS class name that represents a Canvas
     * @property Canvas.Element.CSS_CANVAS
     * @static
     * @final
     * @type String
     */
    /// Canvas.Element.CSS_CANVAS = "canvas-module";
	/// mirar els cursors per veure com afagir classes en comptes de matxacarles
	
	Canvas.Element.prototype.fillBackground = true;
	Canvas.Element.prototype.showcorners = false;
	Canvas.Element.prototype.photoborder = true;
	Canvas.Element.prototype.polaroid = false;
	
	
	/**
     * The object literal containing mouse position if clicked in an empty area (no image)
     * @property groupSelector
     * @type object
     */
	Canvas.Element.prototype.groupSelector = null;
	
	/**
     * The array element that contains all the images of the canvas
     * @property _aImages
     * @type object
     */
	Canvas.Element.prototype._aImages = null;
	
	/**
     * The element that references the canvas interface implementation
     * @property _oContext
     * @type object
     */
	Canvas.Element.prototype._oContext = null;
	
	/**
     * The main element that contains the canvas
     * @property _oElement
     * @type object
     */
	Canvas.Element.prototype._oElement = null;

	/**
     * The object literal containing config parameters
     * @property _oConfig
     * @type object
     */
	Canvas.Element.prototype._oConfig = null;
	
	/**
     * The object literal containing the current x,y params of the transformation
     * @property _currentTransform
     * @type object
     */
	Canvas.Element.prototype._currentTransform = null;
	
	// 
	
	/**
     * The Canvas class's initialization method. This method is automatically 
     * called by the constructor, and sets up all DOM references for 
     * pre-existing markup, and creates required markup if it is not 
     * already present.
     * @method _initElement
     * @param {HTMLElement | String} el The element representing the Canvas
     * @param {Object} userConfig The configuration Object literal 
     * containing the configuration that should be set for this module. 
     * See configuration documentation for more details.
     */
	Canvas.Element.prototype._initElement = function(el) {
		if(YAHOO.util.Dom.inDocument(el)) {
			if(YAHOO.lang.isString(el)) {
				this._oElement = document.getElementById(el);
			} else {
				this._oElement = el;
			}
			/// YAHOO.util.Dom.addClass(this._oElement, Canvas.Element.CSS_CANVAS);
		}
		else {
			/// add element to the document: module.js
		}
	
		// it contains the active image and the listeners
		this._oContextTop = this._oElement.getContext('2d');
	};

	Canvas.Element.prototype._initCustomEvents = function() {
		this.beforeRenderEvent = new YAHOO.util.CustomEvent('beforeRenderEvent', this, true);	
	}
	
	/**
     * For now we use an object literal without methods to store the config params
     * @method _initConfig
     * @param {Object} userConfig The configuration Object literal 
     * containing the configuration that should be set for this module. 
     * See configuration documentation for more details.
     */
	Canvas.Element.prototype._initConfig = function(oConfig) {
		this._oConfig = oConfig;
		this._oElement.setAttribute('width', this._oConfig.width);
	    this._oElement.setAttribute('height', this._oConfig.height);
	};
	
	/**
     * It creates a canvas to contain all the images are not being active
     * @method _createContainer
     * See configuration documentation for more details.
     */
	Canvas.Element.prototype._createContainer = function() {
		var canvasEl = document.createElement('canvas');
		canvasEl.id = 'canvas-container';
		var oContainer = this._oElement.parentNode.insertBefore(canvasEl, this._oElement);
		oContainer.setAttribute('width', this._oConfig.width);
	    oContainer.setAttribute('height', this._oConfig.height);
		// this will contain all images that are not on the top
		this._oContextContainer = oContainer.getContext('2d'); 
	};
	
	/**
     * Adds main mouse listeners to the whole canvas
     * @method _initEvents
     * See configuration documentation for more details.
     */
	Canvas.Element.prototype._initEvents = function() {
		YAHOO.util.Event.on(this._oElement, 'mousedown', this.onMouseDown, this, true);
		YAHOO.util.Event.on(this._oElement, 'mouseup', this.onMouseUp, this, true);
		YAHOO.util.Event.on(this._oElement, 'mousemove', this.onMouseMove, this, true);
		// Canvas.Element.addEventListener("mousedown", function(evt) { startTransform(evt); }, false);
	};
	
	/**
     * For now we use an object literal without methods to store the config params
     * @method _initConfig
     * @param {Object} userConfig The configuration Object literal 
     * containing the configuration that should be set for this module. 
     * See configuration documentation for more details.
     */
	Canvas.Element.prototype.onMouseUp = function(e) {
		if (this._currentTransform) {
			// determine the new coords everytime the image changes its position
			this._currentTransform.target.setImageCoords();
		}
		this._currentTransform = null;
		this.groupSelector = null;
		
		// this is to clear the selector box
		this.renderTop();
	};
	
	/**
     * For now we use an object literal without methods to store the config params
     * @method _initConfig
     * @param {Object} userConfig The configuration Object literal 
     * containing the configuration that should be set for this module. 
     * See configuration documentation for more details.
     */
	Canvas.Element.prototype.onMouseDown = function(e) {
		// ignore if something else is already going on
		if (this._currentTransform != null || this._aImages == null) {
			return;
		}
		
		// determine whether we clicked the image
		var oImg = this.findTargetImage(e, false);
		if (!oImg) {
			this.groupSelector = { ex: e.clientX, ey: e.clientY,
				 					top: 0, left: 0 };
		}
		else { 
			// determine if it's a drag or rotate case
			// rotate and scale will happen at the same time
			var action = (!this.findTargetCorner(e, oImg)) ? 'drag' : 'rotate';
			
			this._currentTransform = { 	target: oImg,
									action: action,
									scalex: oImg.scalex,
									offsetX: e.clientX - oImg.left,
			 						offsetY: e.clientY - oImg.top,
			 						ex: e.clientX, ey: e.clientY,
									left: oImg.left, top: oImg.top,
									theta: oImg.theta };
									
			// we must render all so the active image is placed in the canvastop
			this.renderAll(false);
		}
		// alert(this._oElement.className);
	};
	
	/**
     * For now we use an object literal without methods to store the config params
     * @method _initConfig
     * @param {Object} userConfig The configuration Object literal 
     * containing the configuration that should be set for this module. 
     * See configuration documentation for more details.
     */
	Canvas.Element.prototype.onMouseMove = function(e) {
		if (this._aImages == null) {
			return;
		}
		if (this.groupSelector != null) {
			// We initially clicked in an empty area, so we draw a box for multiple selection.
			this.groupSelector.left = e.clientX - this.groupSelector.ex;
			this.groupSelector.top = e.clientY - this.groupSelector.ey;
			this.renderTop();
		}
		else if (this._currentTransform == null) {
			// Here we are hovering the canvas then we will determine
			// what part of the pictures we are hovering to change the caret symbol.
			// We won't do that while dragging or rotating in order to improve the
			// performance.
			var oImg = this.findTargetImage(e, true);
			if (!oImg) {
				this._oElement.style.cursor = 'default';
			}
			else { 
				var corner = this.findTargetCorner(e, oImg);
				if (!corner) {
					this._oElement.style.cursor = 'move';
				}
				else {
					if(corner == 'tr') {
						this._oElement.style.cursor = 'ne-resize';
					}
					else if(corner == 'br') {
						this._oElement.style.cursor = 'se-resize';
					}
					else if(corner == 'bl') {
						this._oElement.style.cursor = 'sw-resize';
					}
					else if(corner == 'tl') {
						this._oElement.style.cursor = 'nw-resize';
					}									
					else {
						this._oElement.style.cursor = 'default';
					}
				}
			}
		}
		else {
			if (this._currentTransform.action == 'rotate') {
				// rotate action
				var lastAngle = Math.atan2(this._currentTransform.ey - this._currentTransform.top,
				                           this._currentTransform.ex - this._currentTransform.left);
				var curAngle = Math.atan2(e.clientY - this._currentTransform.top,
				                          e.clientX - this._currentTransform.left);
						
				this._currentTransform.target.theta = (curAngle - lastAngle) + this._currentTransform.theta;
			
				// scale action
	            var lastLen = Math.sqrt(Math.pow(this._currentTransform.ey - this._currentTransform.top, 2) +
	                                    Math.pow(this._currentTransform.ex - this._currentTransform.left, 2));
	            var curLen = Math.sqrt(Math.pow(e.clientY - this._currentTransform.top, 2) +
	                                   Math.pow(e.clientX - this._currentTransform.left, 2));
            
	            this._currentTransform.target.scalex = this._currentTransform.scalex * (curLen / lastLen);
	            this._currentTransform.target.scaley = this._currentTransform.target.scalex;
			
			}		
			else {
				// translation action
				this._currentTransform.target.left = e.clientX - this._currentTransform.offsetX;
				this._currentTransform.target.top = e.clientY - this._currentTransform.offsetY;
			}
			// only commit here. when we are actually moving the pictures
			this.renderTop();
		}		
	};
	
	/**
 	 *
     */
	Canvas.Element.prototype.addImage = function(oImg) {
		// this._aImages[this._aImages.length] = oImg;
		if(YAHOO.lang.isNull(this._aImages)) {
			this._aImages = [];
		}
		this._aImages.push(oImg);
		this.renderAll(false);

	};
	
	Canvas.Element.prototype.renderAll = function(allOnTop) {
		var containerCanvas = (allOnTop) ? this._oContextTop : this._oContextContainer;
		
		this.beforeRenderEvent.fire("hhh",{x:'hola'}, {y:'adios'});
		this._oContextTop.clearRect(0,0,parseInt(this._oConfig.width), parseInt(this._oConfig.height));
		containerCanvas.clearRect(0,0,parseInt(this._oConfig.width), parseInt(this._oConfig.height));
		
		if (!this.fillBackground) {
			containerCanvas.fillStyle = "black";
			containerCanvas.fillRect(0, 0, this._oConfig.width, this._oConfig.height);
		}
		// we render the rest of images
		for (var i = 0, l = this._aImages.length-1; i < l; i += 1) {
			this.drawImageElement(containerCanvas, this._aImages[i]);			
		}
		// we render the top context
		this.drawImageElement(this._oContextTop, this._aImages[this._aImages.length-1]);
	};
	
	Canvas.Element.prototype.renderTop = function() {
		this.beforeRenderEvent.fire("hhh",{x:'hola'}, {y:'adios'});
		this._oContextTop.clearRect(0,0,parseInt(this._oConfig.width), parseInt(this._oConfig.height));
		
		// we render the top context
		this.drawImageElement(this._oContextTop, this._aImages[this._aImages.length-1]);
		
		if (this.groupSelector != null) {
			this._oContextTop.fillStyle = "rgba(0, 0, 200, 0.5)";
			this._oContextTop.fillRect(this.groupSelector.ex - ((this.groupSelector.left > 0) ? 0 : - this.groupSelector.left), this.groupSelector.ey - ((this.groupSelector.top > 0) ? 0 : - this.groupSelector.top), Math.abs(this.groupSelector.left), Math.abs(this.groupSelector.top));
			this._oContextTop.strokeRect(this.groupSelector.ex - ((this.groupSelector.left > 0) ? 0 : Math.abs(this.groupSelector.left)), this.groupSelector.ey - ((this.groupSelector.top > 0) ? 0 : Math.abs(this.groupSelector.top)), Math.abs(this.groupSelector.left), Math.abs(this.groupSelector.top));
		}
	};
	
	Canvas.Element.prototype.drawImageElement = function(_oContext, oImg) {
		var offsetY = oImg.height / 2;
		var offsetX = oImg.width / 2;

		_oContext.save();
		_oContext.translate(oImg.left, oImg.top);
		_oContext.rotate(oImg.theta);
		_oContext.scale(oImg.scalex, oImg.scaley);
		
		this.drawBorder(_oContext, oImg, offsetX, offsetY);
		// drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
		// A = oImg.width - oImg._oElement.width = oImg.borderwidth (if any)
		// B = oImg.height - oImg._oElement.height = oImg.borderwidth + oImg.polaroidheight
		// B - A = oImg.polaroidheight
		_oContext.drawImage(oImg._oElement, - oImg._oElement.width/2,  (- oImg._oElement.height)/2 - ((oImg.height - oImg._oElement.height) - (oImg.width - oImg._oElement.width))/2);
		if (oImg.cornervisibility) {
			this.drawCorners(_oContext, oImg, offsetX, offsetY);
		}
		_oContext.restore();
	};
	
	Canvas.Element.prototype._getImageLines = function(oCoords) {
		return {
			topline: { 
				o: oCoords.tl,
				d: oCoords.tr 
			},
			rightline: { 
				o: oCoords.tr,
				d: oCoords.br 
			},
			bottomline: { 
				o: oCoords.br,
				d: oCoords.bl 
			},
			leftline: { 
				o: oCoords.bl,
				d: oCoords.tl 
			}
		}
	};
	
	Canvas.Element.prototype.findTargetImage = function(e, hovering) {
		/* http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html */
		/* http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html */
		var ex = e.clientX;
		var ey = e.clientY;
		for (var i = this._aImages.length-1; i >= 0; i -= 1) {
			// we iterate through each image. If target found then return target
			var iLines = this._getImageLines(this._aImages[i].oCoords);
			var xpoints = this._findCrossPoints(ex, ey, iLines);
			
			// if xcount is odd then we clicked inside the image
			// For the specific case of square images xcount == 1 in all true cases
			if (xpoints % 2 == 1 && xpoints != 0) {
				var target = this._aImages[i];
				//reorder array
				if (!hovering) {
					this._aImages.splice(i, 1);
					this._aImages.push(target);
				}
				return target;
			}
		}
		return false;
	};
	
	Canvas.Element.prototype._findCrossPoints = function(ex, ey, oCoords) {
		var b1, b2, a1, a2, xi, yi;
		var xcount = 0;
		var iLine = null;
		for (lineKey in oCoords) {
			iLine = oCoords[lineKey];
			// optimisation 1: line below dot. no cross
			if ((iLine.o.y < ey) && (iLine.d.y < ey)) {
				continue;
			}
			// optimisation 2: line above dot. no cross
			if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) {
				continue;
			}
			// optimisation 3: vertical line case
			if ((iLine.o.x == iLine.d.x) && (iLine.o.x >= ex)) { 
				xi = iLine.o.x;
				yi = ey;
			}
			// calculate the intersection point
			else {
				b1 = 0; //(y2-ey)/(x2-ex); 
				b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x); 
				a1 = ey-b1*ex;
				a2 = iLine.o.y-b2*iLine.o.x;

				xi = - (a1-a2)/(b1-b2); 
				yi = a1+b1*xi; 
			}
		
			// dont count xi < ex cases
			if (xi >= ex) { 
				xcount += 1;
			}
			// optimisation 4: specific for square images
			if (xcount == 2) {
				break;
			}
		}
		return xcount;
	};
	
	Canvas.Element.prototype.findTargetCorner = function(e, oImg) {
		var ex = e.clientX;
		var ey = e.clientY;
		var xpoints = null;
		
		var corners = ['tl','tr','br','bl'];
		for (var i in oImg.oCoords) {
			xpoints = this._findCrossPoints(ex, ey, this._getImageLines(oImg.oCoords[i].corner));
			if (xpoints % 2 == 1 && xpoints != 0) {
				return i;
			}		
		}
		return false;
	};
	
	Canvas.Element.prototype.drawBorder = function(_oContext, oImg, offsetX, offsetY) {
		var outlinewidth = 2;
		_oContext.fillStyle = 'rgba(0, 0, 0, .3)';
		_oContext.fillRect(-2 - offsetX, -2 - offsetY, oImg.width + (2 * outlinewidth), oImg.height + (2 * outlinewidth));
		_oContext.fillStyle = '#fff';
		_oContext.fillRect(-offsetX, -offsetY, oImg.width, oImg.height);
	};
	
	///helper function
	Canvas.Element.prototype.getImageContainer = function(oImg) {
		
	};
	
	Canvas.Element.prototype.drawCorners = function(_oContext, oImg, offsetX, offsetY) {
		_oContext.fillStyle = "rgba(0, 200, 50, 0.5)";
		_oContext.fillRect(-offsetX, -offsetY, oImg.cornersize, oImg.cornersize);
		_oContext.fillRect(oImg.width - offsetX - oImg.cornersize, -offsetY, oImg.cornersize, oImg.cornersize);
		_oContext.fillRect(-offsetX, oImg.height - offsetY - oImg.cornersize, oImg.cornersize, oImg.cornersize);
		_oContext.fillRect(oImg.width - offsetX - oImg.cornersize, oImg.height - offsetY - oImg.cornersize, oImg.cornersize, oImg.cornersize);
	};
	
	Canvas.Element.prototype.canvasTo = function(format) {
		this.renderAll(true);
		return this._oElement.toDataURL('image/'+format);
	};
	
	
}());