/** @type Object **/
Cursor = {};
/** @type Object **/
Cursor.DEFAULT 	= "default";
/** @type Object **/
Cursor.POINTER 	= "pointer";
/** @type Object **/
Cursor.MOVE 	= "move";
/** @type Object **/
Cursor.GRABBING = "-moz-grabbing";

/**
 *	@returns void
 *	@param String
 */
Cursor.setCursor = function (type) {
	try {
		document.body.style.cursor = type;
	} catch (gulp) { }
};

/**
 *	@returns String
 */
Cursor.getDefault = function () {
	return Cursor.DEFAULT;
};

/**
 *	@constructor
 */
function Rectangle() {
	this._x;
	this._y;
	this._width;
	this._height;
	if (1 != arguments.length && 3 != arguments.length) {
		throw "IllegalArgumentException: Rectangle constructor accepts " +
			"one or four arguments";
	}
	if (1 == arguments.length) {
		that = arguments[0];
		if (!(that instanceof Rectangle)) {
			throw "IllegalArgumentException: Incorrect usage of Rectangle's " +
				"copy constructor";
		}
		this.setX(that.getX());
		this.setY(that.getY());
		this.setWidth(that.getWidth());
		this.setHeight(that.getHeight());
	} else {
		this.setX(arguments[0]);
		this.setY(arguments[1]);
		this.setWidth(arguments[2]);
		this.setHeight(arguments[3]);
	}	
}

/** 
 *	@param number x
 *	@returns void
 */
Rectangle.prototype.setX = function (x) {
	this._x = x;
};

/** 
 *	@param number y
 *	@returns void
 */
Rectangle.prototype.setY = function (y) {
	this._y = y;
};

/** 
 *	@param number width
 *	@returns void
 */
Rectangle.prototype.setWidth = function (width) {
	this._width = width;
};

/** 
 *	@param number height
 *	@returns void
 */
Rectangle.prototype.setHeight = function (height) {
	this._height = height;
};

/** 
 *	@param number height
 *	@returns void
 */
Rectangle.prototype.contains = function () {
	var x,y;
	if (1 != arguments.length && 2 != arguments.length) {
		throw "IllegalArgumentException: Rectangle constructor accepts " +
			"one or two arguments";
	}
	if (1 == arguments.length) {
		var point = arguments[0];
		if (!(point instanceof Point)) {
			throw "IllegalArgumentException: Incorrect usage of Rectangle's " +
				"contains method";
		}
		x = point.getX();
		y = point.getY();
	}
	if (2 == arguments.length) {
		x = arguments[0];
		y = arguments[1];
	}
	return this.getX() < x &&
		   this.getX()+this.getWidth() > x &&
		   this.getY() < y &&
		   this.getY()+this.getHeight() > y;
};


/**
 *	@constructor
 */
function Point() {
	this._x;
	this._y;
	if (1 != arguments.length && 2 != arguments.length) {
		throw "IllegalArgumentException: Point constructor accepts " +
			"one or two arguments";
	}
	if (1 == arguments.length) {
		that = arguments[0];
		if (!(that instanceof Point)) {
			throw "IllegalArgumentException: Incorrect usage of Rectangle's " +
				"copy constructor";
		}
		this.setX(that.getX());
		this.setY(that.getY());
	} else {
		this.setX(arguments[0]);
		this.setY(arguments[1]);
	}	
}

/** 
 *	@param number x
 *	@returns void
 */
Point.prototype.setX = function (x) {
	this._x = x;
};

/** 
 *	@param number y
 *	@returns void
 */
Point.prototype.setY = function (y) {
	this._y = y;
};

/** 
 *	@param number width
 *	@returns void
 */
Point.prototype.setWidth = function (width) {
	this._width = width;
};

/** 
 *	@param number height
 *	@returns void
 */
Point.prototype.setHeight = function (height) {
	this._height = height;
};


 /** @constructor */
function DomUtils() {
	throw 'RuntimeException: DomUtils is a utility class with only static ' +
		' methods and may not be instantiated';
}		

/**
 *	@param string HTMLElement
 *	@param String cursorType
 *	@return void
 */
DomUtils.setCursor = function (target,cursorType) {
	try {
		target.style.cursor = cursorType;
	} catch (gulp) { }
};

/**
 *	@param string HTMLElement
 *	@param number x
 *	@param number y
 *	@return void
 */
DomUtils.setLocation = function (target,x,y) {
	target.style.left = x + "px";
	target.style.top  = y + "px";
};

/** @type Object */
DomUtils.Position = {};
/** @type String */
DomUtils.Position.STATIC   = "static";
/** @type String */
DomUtils.Position.RELATIVE = "relative";
/** @type String */
DomUtils.Position.ABSOLUTE = "absolute";

/**
 *	@param string HTMLElement
 *	@param String type
 *	@return void
 */
DomUtils.setPosition = function (target,type) {
	target.style.position = type;
};

/**
 *	@param string HTMLElement
 *	@return number
 */
DomUtils.setOpacity = function (target,percentage) {
	if (target.filters) {
		target.filters["alpha"] = percentage*100 + "";
	} else if (target.style.opacity) {
		target.style.opacity = "" + percentage;
	} else if (target.style.mozOpacity) {
		target.style.mozOpacity = "" + percentage;
	}
};

/**
 *	@param string HTMLElement
 *	@return number
 */
DomUtils.getX = function (target) {
	return parseInt(target.style.left);
};

/**
 *	@param string HTMLElement
 *	@return number
 */
DomUtils.getY = function (target) {
	return parseInt(target.style.top);
};

/**
 *	@param string HTMLElement
 *	@return number
 */
DomUtils.getWidth = function (target) {
	return parseInt(target.style.width);
};

/**
 *	@param string HTMLElement
 *	@return number
 */
DomUtils.getHeight = function (target) {
	return parseInt(target.style.height);
};

/**
 *	@param string className
 *	@return Element
 */
DomUtils.getFirstAncestorByClassName = function (target,className) {
	var parent = target;
	while (parent = parent.parentNode) {
		if (DomUtils.hasClassName(parent,className)) {
			return parent;
		}
	}
	return null;
};

/**
 *	@param Element target
 *	@param string className
 *	@returns boolean
 */
DomUtils.hasClassName = function (target,className) {
	
	function _isLastOfMultpleClassNames(all,className) {
		var spaceBefore = all.lastIndexOf(className)-1;
		return all.endsWith(className) && 
			all.substring(spaceBefore,spaceBefore+1) == " ";
	}

	className = className.trim();
	var cn = target.className;
	if (!cn) {
		return false;
	}
	cn = cn.trim();
	if (cn == className) {
		return true;
	}
	if (cn.indexOf(className + " ") > -1) {
		return true;
	}
	if (_isLastOfMultpleClassNames(cn,className)) {
		return true;
	}
	return false;
};

/**
 *	String convenience method for checking if the end of this string equals
 *	a given string.
 *
 *	@returns boolean
 */
String.prototype.endsWith = function (s) {
	if ("string" != typeof s) {
		throw "IllegalArgumentException: Must pass a string to " +
				"String.endsWith()";
	}
	var start = this.length - s.length;
	return this.substring(start) == s;
};

/**
 *	String convenience method to trim leading and trailing whitespace.
 *
 *	@returns string
 */
String.prototype.trim = function () {
	return this.replace(/^\s*(.+)/gi,"$1").replace(/\s*$/gi,"");
};

/** @constructor */
function EventUtils() {
	throw 'RuntimeException: EventUtils is a utility class with only static ' +
		' methods and may not be instantiated';
}		

/**
 *  @access static
 *  @param HTMLElement target
 *  @param string type
 *  @param Function callback
 *  @param boolean captures
 */
EventUtils.addEventListener = function (target,type,callback,captures) {
	if (target.addEventListener) {
		// EOMB
		target.addEventListener(type,callback,captures);
	} else if (target.attachEvent) {
		// IE
		target.attachEvent('on'+type,callback,captures);
	} else {
		// IE 5 Mac and some others
		target['on'+type] = callback;
	}
}			

/**
 *  @access static
 *  @param HTMLElement target
 *  @param string type
 *  @param Function callback
 *  @param boolean captures
 */
EventUtils.removeEventListener = function (target,type,callback,captures) {
	if (target.removeEventListener) {
		// EOMB
		target.removeEventListener(type,callback,captures);
	} else if (target.detachEvent) {
		// IE
		target.detachEvent('on'+type,callback,captures);
	} else {
		// IE 5 Mac and some others
		target['on'+type] = null;
	}
}			

/**
 *	@constructor
 *	@param Event (EOMB) | undefined (IE)
 *
 */
function Evt(evt) {
	var docEl 	 = document.documentElement;
	var body  	 = document.body;
	
	this._evt 	 = (evt) ? evt : 
				   (window.event) ? window.event : null;
	this._source = (evt.target) ? evt.target : 
				   (evt.srcElement) ? evt.srcElement : null;
	this._x		 = (evt.pageX) ? evt.pageX : 
				   (docEl.scrollLeft) ? (docEl.scrollLeft + evt.clientX) : 
				   (body.scrollLeft) ? (body.scrollLeft + evt.clientX) : evt.clientX;
	this._y 	 = (evt.pageY) ? evt.pageY : 
				   (docEl.scrollTop) ? (docEl.scrollTop + evt.clientY) :
				   (body.scrollTop) ? (body.scrollTop + evt.clientY) : evt.clientY;
}

/** @returns number */
Evt.prototype.getX = function () {
	return this._x;
};

/** @returns number */
Evt.prototype.getY = function () {
	return this._y;
};

/** @returns HTMLElement */
Evt.prototype.getSource = function () {
	return this._source;
};

/** 
 *	@returns void
 */
Evt.prototype.consume = function () {
	if (!this._evt) return;
	if (this._evt.stopPropagation) {
		this._evt.stopPropagation();
		this._evt.preventDefault();
	} else if (typeof this._evt.cancelBubble != undefined) {
		this._evt.cancelBubble = true;
		this._evt.returnValue  = false;
	} else {
		this._evt = null;
	}
};

/** @type String */	
var PAGE_WRAP_ID = "page-wrap";
/** @type String */	
var BOX_CLASS_NAME = "box";
/** @type number */
var BOX_HEIGHT = 280;
/** @type boolean */
var dragging;
/** @type Array */
var boxes;
/** @type HTMLElement */
var box;
/** @type number */
var deltaX, 
	deltaY;
/** @type number */
var pageWidth; 


/** 
 *	@param Event (EOMB) | undefined (IE)
 *	@returns void
 */
function mousePressed(evt) {
	evt = new Evt(evt);
	dragging = true;
	EventUtils.addEventListener(document,"mousemove",mouseDragged);
	EventUtils.addEventListener(document,"mouseup"  ,mouseReleased);
	box = DomUtils.getFirstAncestorByClassName(evt.getSource(),BOX_CLASS_NAME);
	deltaX = evt.getX() - DomUtils.getX(box);
	deltaY = evt.getY() - DomUtils.getY(box);
	DomUtils.setPosition(box,DomUtils.Position.RELATIVE);
	DomUtils.setOpacity(box,.50);
	DomUtils.setCursor(box,Cursor.GRABBING);
	Cursor.setCursor(Cursor.GRABBING);
}

/** 
 *	@param Event (EOMB) | undefined (IE)
 *	@returns void
 */
function mouseDragged(evt) {
	evt = new Evt(evt);
	DomUtils.setLocation(box,
						 evt.getX()-deltaX,
						 evt.getY()-deltaY);
	drawHoverBorder(evt);			 
}

/** 
 *	@param Event (EOMB) | undefined (IE)
 *	@returns void
 */
function mouseReleased(evt) {
	evt = new Evt(evt);
	setHilite(null);			 
	DomUtils.setOpacity(box,1);
	DomUtils.setPosition(box,DomUtils.Position.STATIC);
	DomUtils.setCursor(box,Cursor.getDefault());
	Cursor.setCursor(Cursor.getDefault());
	reorderElements(evt);
	DomUtils.setLocation(box,0,0);
	EventUtils.removeEventListener(document,"mousemove",mouseDragged);
	EventUtils.removeEventListener(document,"mouseup"  ,mouseReleased);
	dragging = false;
	box = null;
	
}

/** 
 *	@param Evt evt
 *	@return void
 */
function reorderElements(evt) {
	var index = getBoxIndex(box);
	var newOrder = getBoxNewOrder(box,index);
	if (0 == newOrder)
		pageWrap.insertBefore(box,allBoxes[0]);	
	if (1 == newOrder) {
		if (index > 1)
			pageWrap.insertBefore(box,allBoxes[1]);	
		else
			pageWrap.insertBefore(box,allBoxes[2]);	
			
	}
	if (2 == newOrder) {
		if (index > 2)	
			pageWrap.insertBefore(box,allBoxes[2]);	
		else	
			pageWrap.insertBefore(box,allBoxes[3]);
	}
	if (3 == newOrder)
		pageWrap.appendChild(box);	
}

/** 
 *	@param Evt evt
 *	@return void
 */
function drawHoverBorder(evt) {
	var index = getBoxIndex(box);
	var newOrder = getBoxNewOrder(box,index);
	if (0 == newOrder)
		setHilite(allBoxes[0],index);	
	if (1 == newOrder) {
		if (index > 1)
			setHilite(allBoxes[1],index);	
		else
			setHilite(allBoxes[1],index);	
			
	}
	if (2 == newOrder) {
		if (index > 2)	
			setHilite(allBoxes[2],index);	
		else	
			setHilite(allBoxes[2],index);
	}
	if (3 == newOrder)
		setHilite(allBoxes[3],index);	
}

function setHilite(target,index) {
	for (var i = 0; i < allBoxes.length; i++) {
		if (allBoxes[i] == target && i != index) {
			allBoxes[i].style.border = "2px solid gray";
			allBoxes[i].style.padding = "0";
		} else {
			allBoxes[i].style.border = "0";
			allBoxes[i].style.padding = "2px";
		}
	}
}


function getBoxNewOrder(box,index) {
	var boxX  = DomUtils.getX(box);
	if (1 == index || 3 == index) 
		boxX += DomUtils.getWidth(box);
	var boxY  = DomUtils.getY(box);
	if (index > 1) 
		boxY += BOX_HEIGHT;
	var row   = Math.ceil(boxY/BOX_HEIGHT);
	if (0 >= row) row = 1;
	var col   = boxX < pageWidth/2 ? 1 : 2;
	if (0 >= col) col = 1;
	return (1 == row && 1 == col) ? 0 :
		   (1 == row && 2 == col) ? 1 :
		   (2 == row && 1 == col) ? 2 : 3;
	//alert("row: "+row+",col:"+col+",newOrder:"+newOrder);

}
function windowLoaded(evt) {
	pageWrap  = document.getElementById(PAGE_WRAP_ID);
	pageWidth = DomUtils.getWidth(pageWrap);
	allBoxes = pageWrap.getElementsByTagName("div");
	//prevent IE text selection!!!
	document.body.ondrag = function () { return false; };
	document.body.onselectstart = function () { return false; };
}

function getBoxIndex(box) {
	for (var i = 0; i < allBoxes.length; i++) {
		if (box == allBoxes[i]) {
			return i;
		}
	}
	throw "IllegalArgumentException";
}
