/**
 * Drag & drop library
 *
 * @file dragdrop.js
 * @created 2006-04-20
 * @modified 2006-04-27
 *
 */
 
Draggable = Class.create();
Draggable.prototype = {

	/**
	 * Constructor
	 *
	 * @param object The object that should be made draggable
	 *
	 */
	 
	initialize: function(element) {
		var options = Object.extend({
			onstartdrag: function() {},
			ondrag: function() {},
			ondrop: function() {},
			
			axis: false,
			constrain: false 
		}, arguments[1] || {});
		
		this.element = $(element);
		
		this.options = options;
		this.dragging = false;
		
		this.eventMouseDown = this.initDrag.bindAsEventListener(this);
		Event.observe(this.element, "mousedown", this.eventMouseDown);
		if(window.addEventListener) Event.observe(window, "mouseout", this.eventWindowMouseOut);
	},
	
	/**
	 * Return absolute position of element
	 *
	 * @return [x,y] array X and Y coordinates of element
	 */
	 
	getPosition: function() {
		return [
			parseInt (this.element.getStyle('left') || '0'),
			parseInt (this.element.getStyle('top') || '0')
		];
	},

	/** 
	 * Register event handlers, initialize dragging action
	 *
	 * @param event Event
	 *
	 */
	 
	initDrag: function(event) {
		if(!Event.isLeftClick(event))
			return;
			
		if(this.dragging)
			this.endDrag(event);

		this.dragging = true;
		this._pointer = [Event.pointerX(event), Event.pointerY(event)];
		
		// register events
		this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
		this.eventMouseUp = this.endDrag.bindAsEventListener(this);
		this.eventWindowMouseOut = this.windowMouseOut.bindAsEventListener(this);
		
		// cache element container's properties if constrain == true
		if(this.options.constrain) {
			this.parentBoundaries = $(this.element.parentNode).getDimensions();
			this.parentOffset = Position.cumulativeOffset(this.element.parentNode);
			this.elDimensions = this.element.getDimensions();
		}
		
		Event.observe(document, "mousemove", this.eventMouseMove);
		Event.observe(document, "mouseup", this.eventMouseUp);
		Event.observe(window, "mouseout", this.eventWindowMouseOut);
		
		// change cursor
		var theBody = document.getElementsByTagName("body")[0];
		var cursor = (this.options.axis == 'horizontal') ? 'e-resize' : (this.options.axis == 'vertical' ? 'n-resize' : 'move');
		this.originalCursor = theBody.style.cursor;
		theBody.style.cursor = cursor;
		
		// fire onstartdrag event
		if(typeof this.options.onstartdrag == 'function')
			this.options.onstartdrag(this);
			
		Event.stop(event);
	},

	/** 
	 * Update element position
	 *
	 * @param event Event
	 *
	 */

	updateDrag: function(event) {
		if(!this.dragging)
			this.endDrag(event);
		
		var pointer = [Event.pointerX(event), Event.pointerY(event)];
		var pos = this.getPosition();
		
		var dx = pointer[0] - this._pointer[0];
		var dy = pointer[1] - this._pointer[1];
	
		var style = this.element.style;
		
		newX = pos[0] + dx;
		newY = pos[1] + dy;
		
		// check if element is allowed to go out of its parent's boundaries
		
		if(this.options.constrain)
		{
			var boundaries = this.parentBoundaries;
			var elDimensions = this.elDimensions;
			var offset = this.parentOffset;
			
			newX =  (newX < 0 || pointer[0] < offset[0]) 	// check if the requested position is out of bounds on the left side
					? 0 									
					: ((newX + elDimensions.width > boundaries.width || pointer[0] > offset[0] + boundaries.width) // check the right side
						? boundaries.width - elDimensions.width
						: newX); // fallback
				
			newY =  (newY < 0 || pointer[1] < offset[1]) 	// check if the requested position is out of bounds on the top side
					? 0 
					: ((newY + elDimensions.height > boundaries.height || pointer[1] > offset[1] + boundaries.height) // check the bottom side
						? boundaries.height - elDimensions.height 
						: newY); // fallback
		}
		
		// check if element is constrained to move in only one axis
		if((!this.options.axis) || (this.options.axis=='horizontal'))
			style.left = newX + "px";
			
		if((!this.options.axis) || (this.options.axis=='vertical'))
			style.top  = newY + "px";
			
		// save last pointer position
		this._pointer = pointer;
		
		// fire ondrag event
		if(typeof this.options.ondrag == 'function')
			this.options.ondrag(this);
			
		Event.stop(event);
	},
	
	/** 
	 * Stop dragging
	 *
	 * @param event Event
	 *
	 */
	
	endDrag: function(event) {
		this.dragging = false;
		
		Event.stopObserving(document, "mousemove", this.eventMouseMove);
		Event.stopObserving(document, "mouseup", this.eventMouseUp);
		Event.stopObserving(window, "mouseout", this.eventWindowMouseOut);
		
		delete this.eventMouseMove;
		delete this.eventMouseUp;
		delete this.windowMouseOut;
		
		// change cursor
		document.getElementsByTagName("body")[0].style.cursor = this.originalCursor;
		
		// fire ondrop event
		if(typeof this.options.ondrop == 'function')
			this.options.ondrop(this);
			
		Event.stop(event);
	},
	
	/**
	 * Stop dragging if the mouse is out of the browser window (Mozilla)
	 *
	 * @param event Event
	 */
	 
	windowMouseOut: function(event)
	{
		if (!event.relatedTarget) this.eventMouseUp(event);
	}
}