/*

unbad object library Copyright © 2006-2010 - Sean Claflin

unbad object library is licensed under the MIT License
http://www.opensource.org/licenses/mit-license.php

*/

(ub.mouse = {
	init: function() {
		ub.e.attach(ub, 'unload', function(e) {
			this.destruct();
		}, false, this);
		ub.require('fx');
	},
	destruct: function() {
		//clear out our dragables stack in reverse order
		ub.a.each(this.dragables, function(dragable) {
			dragable.destruct();
		}, this, true);
		//clear out our dropables stack in reverse order
		ub.a.each(this.dropables, function(dropable) {
			dropable.destruct();
		}, this, true);
	},
	dragables: [],
	dropables: [],
	scrollables: [],
	dragStart: function(e, dragable) {
		//do nothing if already dragging
		if(!dragable.mousemove) {
			//set the alpha for the dragable
			if(dragable.options.alphaOnDrag)
				ub.n.setOpacity(dragable.node, 0.75);
			//add a mousemove event handler
			dragable.mousemove = ub.e.attach(document, 'mousemove', function(e) {
				return this.dragMove(e, dragable);
			}, true, this, true);
			//add a mouseup event handler
			dragable.mouseup = ub.e.attach(document, 'mouseup', function(e) {
				return this.dragStop(e, dragable);
			}, true, this, true);
			//trigger events
			dragable.ondragstart(e.chType('dragstart'));
			ub.mouse.ondragstart(e, dragable);
			this.dragMove(e.chType('mousemove'), dragable);
		}
		return false;
	},
	dragMove: function(e, dragable) {
		//calculate the change in position
		var delta = [
			(dragable.options.restrictX) ? 0 : e.coords[0] - dragable.curCoords[0],
			(dragable.options.restrictY) ? 0 : e.coords[1] - dragable.curCoords[1]
		];
		//get the current coords of our node
		var coords = ub.n.getCoords(dragable.node);
		
		//check to see if we are restricting to a container
		if(dragable.options.restrictContainer) {
			if(!dragable.node.parentNode)
				return false;
			var dims = ub.n.getDims(dragable.node);
			var offset = ub.n.realOffset(dragable.node);
			var pNode = dragable.node.parentNode;
			var pDims = ub.n.getDims(pNode);
			var pOffset = ub.n.realOffset(pNode);
			
			if(e.coords[0] < pOffset[0] + dragable.cursorOffset[0]) {
				delta[0] = pOffset[0] - offset[0];
				e.coords[0] = dragable.curCoords[0] + delta[0];
			}
			else if(e.coords[0] > pOffset[0] + pDims[0] - dims[0] + dragable.cursorOffset[0]) {
				delta[0] = (pOffset[0] + pDims[0]) - (offset[0] + dims[0]);
				e.coords[0] = dragable.curCoords[0] + delta[0];
			}
			if(e.coords[1] < pOffset[1] + dragable.cursorOffset[1]) {
				delta[1] = pOffset[1] - offset[1];
				e.coords[1] = dragable.curCoords[1] + delta[1];
			}
			else if(e.coords[1] > pOffset[1] + pDims[1] - dims[1] + dragable.cursorOffset[1]) {
				delta[1] = (pOffset[1] + pDims[1]) - (offset[1] + dims[1]);
				e.coords[1] = dragable.curCoords[1] + delta[1];
			}
		}
		//update curCoords
		dragable.curCoords = e.coords;
		//reposition our node
		ub.n.setCoords(dragable.node, [coords[0] + delta[0], coords[1] + delta[1]]);
		//loop through our dropables
		ub.a.each(this.dropables, function(dropable) {
			//dragable classname match our dragclass?
			if(ub.n.hasClassName(dragable.node, dropable.options.dragClass)) {
				//are the mouse coords within this dropable?
				if(ub.n.within(dropable.node, e.coords)) {
					//is the hoverclass already applied?
					if(!ub.n.hasClassName(dropable.node, dropable.options.hoverClass)) {
						//remove the activeClass
						ub.n.removeClassName(dropable.node, dropable.options.activeClass);
						//add the hoverClass
						ub.n.addClassName(dropable.node, dropable.options.hoverClass);
						//fire the ondropenter event
						dropable.ondropenter(e.chType('enter'), dragable);
						ub.mouse.ondropenter(e, dropable, dragable);
					}
				}
				else {
					//does this dropable have the hoverClass?
					if(ub.n.hasClassName(dropable.node, dropable.options.hoverClass)) {
						//remove the hoverClass
						ub.n.removeClassName(dropable.node, dropable.options.hoverClass);
						//fire the ondropleave event
						dropable.ondropleave(e.chType('leave'), dragable);
						ub.mouse.ondropleave(e, dropable, dragable);
					}
					//does this dropable not have the activeClass?
					if(!ub.n.hasClassName(dropable.node, dropable.options.activeClass)) {
						//add the activeClass
						ub.n.addClassName(dropable.node, dropable.options.activeClass);
						//fire the ondropactive event
						dropable.ondropactive(e.chType('active'), dragable);
						ub.mouse.ondropactive(e, dropable, dragable);
					}
				}
			}
		});
		//trigger events
		dragable.ondragmove(e.chType('dragmove'));
		ub.mouse.ondragmove(e, dragable);
		return false;
	},
	dragStop: function(e, dragable) {
		if(dragable.options.alphaOnDrag)
			ub.n.setOpacity(dragable.node, dragable.options.baseAlpha);
		//detach our mousemove and mouseup events
		if(dragable.mousemove) {
			dragable.mousemove.detach();
			delete dragable.mousemove;
		}
		if(dragable.mouseup) {
			dragable.mouseup.detach();
			delete dragable.mouseup;
		}
		ub.a.each(this.dropables, function(dropable) {
			//check for the hoverClass
			if(ub.n.hasClassName(dropable.node, dropable.options.hoverClass)) {
				ub.n.removeClassName(dropable.node, dropable.options.hoverClass);
				dropable.ondrop(e.chType('drop'), dragable);
				ub.mouse.ondrop(e, dropable, dragable);
			}
			//check for the activeClass
			if(ub.n.hasClassName(dropable.node, dropable.options.activeClass)) {
				ub.n.removeClassName(dropable.node, dropable.options.activeClass);
				dropable.ondropinactive(e.chType('inactive'), dragable);
				ub.mouse.ondropinactive(e, dropable, dragable);
			}
		});
		dragable.ondragstop(e.chType('dragstop'));
		ub.mouse.ondragstop(e, dragable);
		return false;
	},
	dragable: function() {
		var dragable = function(node, options) {
			//add this dragable to the stack
			ub.a.append(ub.mouse.dragables, this);
			/**
			 * Options object contains sane defaults that can be overriden when
			   {@link ub.mouse.dragable} is instanced through the second parameter.
			 * @namespace
			 * @name ub.mouse.dragable.options
			 * @property {Boolean} [restrictX = false] Do not allow the dragable to move horizontally
			 * @property {Boolean} [restrictY = false] Do not allow the dragable to move vertically
			 * @property {Boolean} [restrictContainer = false] Do not allow the dragable to move outside it's parent node
			 * @property {DOM Node} [handle = node] Make handle the active area for starting a drag operation
			 * @property {Integer} [dragZIndex = 10000] z-index style to set on node when a drag operation is active
			 * @property {Boolean} [alphaOnDrag = true] Set the opacity of node when a drag operation is active
			 * @property {Float} [baseAlpha = node.style.opacity || 1] Base alpha that node will revert to after a drag operation
			 */
			this.options = ub.u.extend({
				restrictX: false,
				restrictY: false,
				restrictContainer: false,
				handle: node,
				dragZIndex: 10000,
				alphaOnDrag: true,
				baseAlpha: node.style['opacity'] || 1
			}, options);
			this.node = node;
			//add a mousedown event handler
			this.mousedown = ub.e.attach(this.options.handle, 'mousedown', function(e) {
				this.curCoords = e.coords;
				var offset = ub.n.realOffset(this.node);
				this.cursorOffset = [e.coords[0] - offset[0], e.coords[1] - offset[1]];
				return ub.mouse.dragStart(e, this);
			}, false, this, true);
		};
		dragable.prototype.destruct = function() {
			//remove this dragable from the stack
			ub.a.remove(ub.mouse.dragables, this);
			//detach our mousemove and mouseup events
			if(this.mousemove) {
				this.mousemove.detach();
				delete this.mousemove;
			}
			if(this.mouseup) {
				this.mouseup.detach();
				delete this.mouseup;
			}
			this.mousedown.detach();
			this.mousedown = null;
			this.options.handle = null;
			this.node = null;
			this.destruct = function() {};
		};
		dragable.prototype.ondragstart = function(e) {};
		dragable.prototype.ondragmove = function(e) {};
		dragable.prototype.ondragstop = function(e) {};
		return dragable;
	}(),
	dropable: function() {
		var dropable = function(node, options) {
			//add this dropable to the stack
			ub.a.append(ub.mouse.dropables, this);
			this.node = node;
			/**
			 * Options object contains sane defaults that can be overriden when
			   {@link ub.mouse.dropable} is instanced through the second parameter.
			 * @namespace
			 * @name ub.mouse.dropable.options
			 * @property {String} [dragClass = ''] Node className attached to
			   {@link ub.mouse.dragable} to watch for
			 * @property {String} [activeClass = ''] Classname to append to node
			   when {@link ub.mouse.dragable} having activeClass is being
			   dragged.
			 * @property {String} [hoverClass = ''] Classname to append to node
			   when {@link ub.mouse.dragable} having activeClass is being
			   dragged over node.
			 */
			this.options = ub.u.extend({
				dragClass: '',
				activeClass: '',
				hoverClass: ''
			}, options);
		};
		dropable.prototype.destruct = function() {
			//remove this dropable from the stack
			ub.a.remove(ub.mouse.dropables, this);
			this.node = null;
			this.destruct = function() {};
		};
		dropable.prototype.ondropactive = function(e, dragable) {};
		dropable.prototype.ondropinactive = function(e, dragable) {};
		dropable.prototype.ondropenter = function(e, dragable) {};
		dropable.prototype.ondropleave = function(e, dragable) {};
		dropable.prototype.ondrop = function(e, dragable) {};
		return dropable;
	}(),
	scrollable: function() {
		var scrollable = function(node) {
			//add this scrollable to the stack
			ub.a.append(ub.mouse.scrollables, this);
			this.node = node;
			//add a mousemove event handler
			this.mousemove = ub.e.attach(this.node, 'mousemove', function(e) {
				this.scroll(e.coords);
			}, false, this, true);
		};
		scrollable.prototype.destruct = function() {
			//remove this scrollable from the stack
			ub.a.remove(ub.mouse.scrollables, this);
			this.mousemove.detach();
			this.mousemove = null;
			this.node = null;
			this.destruct = function() {};
		};
		scrollable.prototype.scroll = function(coords) {
			var scrollerOffset = ub.n.cumulativeOffset(this.node);
			var xPos = coords[0] - scrollerOffset[0];
			var yPos = coords[1] - scrollerOffset[1];
			var xHidden = this.node.scrollWidth - this.node.clientWidth;
			var yHidden = this.node.scrollHeight - this.node.clientHeight;
			this.node.scrollTop = (yPos / this.node.clientHeight) * yHidden;
			this.node.scrollLeft = (xPos / this.node.clientWidth) * xHidden;
		};
		return scrollable;
	}(),
	ondragstart: function(e, dragable) {},
	ondragmove: function(e, dragable) {},
	ondragstop: function(e, dragable) {},
	ondropactive: function(e, dropable, dragable) {},
	ondropinactive: function(e, dropable, dragable) {},
	ondropenter: function(e, dropable, dragable) {},
	ondropleave: function(e, dropable, dragable) {},
	ondrop: function(e, dropable, dragable) {}
}).init();
