/**
 * @fileoverview
 * 
 * <p>Copyright (c) 2006 Peter Michaux. All rights reserved.<br />
 * Code licensed under the MIT License:<br />
 * http://michaux.ca/svn/dragdrop/trunk/MIT-LICENSE.txt</p>
 *
 * @author Peter Michaux - http://peter.michaux.ca
 * @version 0.1 (August 2006)
 */

/**
 * @class
 *
 * <p>A basic dragdrop manager for proxy-style dragging.</p>
 *
 * <p>Any HTML element with class name "draggable" will be draggable.
 * The entire element can initiate the drag except for 
 * nested anchor elements (ie. <a> tags;  eg. links).</p>
 *
 * <p>This class does not implement drop targets, handles,
 * multiple selection or interaction groups. Only the simplest
 * proxy-style drag and drop is supported by DragManager.</p>
 *
 * <p>When an element is being dragged it will have
 * the CSS class "selected".</p>
 *
 * <h4>interesting moment hooks</h4>
 * <p>The interesting moment hooks fire in the following order and are
 * available for use in your subclasses and instances.</p>
 *
 * <ol>
 *  <li>onMouseDown(e)</li>
 *  <li>onSelect(draggable)</li>
 *  <li>onDragStart(e)</li>
 *  <li>onDrag(e)</li>
 *  <li>onDragEnd(e)</li>
 *  <li>onUnselect(draggable)</li>
 *  <li>onMouseUp(e)</li>
 * </ol>
 *
 * @requires yahoo.js 
 * @requires event.js 
 * @requires dom.js 
 *
 * @constructor
 * @param {Object} proxy A proxy object with properites
 * <ul>
 *   <li>onDragStart(e, selected)</li>
 *   <li>onDrag(e)</li>
 *   <li>onDragEnd(e)</li>
 * </ul>
 */
function DragManager(proxy) {
	this.proxy = proxy;
	YAHOO.util.Event.addListener(document, "mousedown", this.handleMouseDown, this, true);  
}

DragManager.prototype = {

	cacheStartXY: function(e) {
		this.startX = YAHOO.util.Event.getPageX(e);
		this.startY = YAHOO.util.Event.getPageY(e);
	},
	
	addListeners: function() {
		YAHOO.util.Event.addListener(document, "mousemove", this.handleMouseMove, this, true);
		YAHOO.util.Event.addListener(document, "mouseup", this.handleMouseUp, this, true);	  
	},
	
	removeListeners: function() {
		YAHOO.util.Event.removeListener(document, "mousemove", this.handleMouseMove);
		YAHOO.util.Event.removeListener(document, "mouseup", this.handleMouseUp);	  
	},
	
  makeSelection: function(e, draggable) {
    this.unselect();
    this.select(draggable);
	},	

	buttonOk: function(e) {
    var button = e.which || e.button;
    return (button > 1) ? false : true;
  },
  
  handleMouseDownOffDraggable: function() {
    this.unselect();
  },
  
	handleMouseDown: function(e) {
		if(!this.buttonOk(e)) {return;}
		var draggable = this.onDraggable(e);
		if (!draggable) {
		  this.handleMouseDownOffDraggable();
		  return;
		}
	  this.onMouseDown(e);

    /* Don't need to pass draggable but is more efficient since have it now. */
	  this.makeSelection(e, draggable);
	  
    if (!this.isSelected(draggable)) {
    	YAHOO.util.Event.preventDefault(e);
    	return;
    }

    this.cacheStartXY(e);
    this.addListeners();
		this.setTimer(e);
		YAHOO.util.Event.preventDefault(e);		
	},
	
	setTimer: function(e) {
	  /**
	   * Cannot use closure on e to pass the event object to startDrag() in Firefox.
	   * So instead copy all properties of e to a regular JavaScript object eC for use
	   * with closure.
	   */ 
		var eC = {}; for (var i in e) {eC[i] = e[i];}
		var thisC = this;
		this.clickTimeout = setTimeout(function() {thisC.startDrag(eC);}, 1000);	  
	},
	
	clearTimer: function() {
	  clearTimeout(this.clickTimeout);
	},
	
	startDrag: function(e) {
    this.clearTimer();
		this.dragging = true;
		this.proxy.onDragStart(e, this.selected);
		this.onDragStart(e);
	},
	
	isSelected: function(draggable) {
    return (draggable === this.selected) ? true : false;
	},

	select: function(draggable) {
	  this.onSelect(draggable);
		this.selected = draggable;
	},
	
	unselect: function() {
	  if (!this.selected){return;}
	  this.onUnselect(this.selected);
    this.selected = null;
	},

  /* Eventhough this layer doesn't look for handles, still can't drag on an anchor */
  isInvalidHandle: function(element) {
    return (element.tagName && element.tagName.toLowerCase() === 'a') ? true : false;
  },

  isDraggable: function(element) {
    return (YAHOO.util.Dom.hasClass(element, "draggable")) ? true : false;
  },
  
  // returns a draggable HTMLElement
  onDraggable: function(e) {
		var targ = YAHOO.util.Event.getTarget(e);
    while (targ) {
      if (this.isInvalidHandle(targ)) {
        return null;
      }
  		if (this.isDraggable(targ)) {
			  return targ;
			}
			targ = targ.parentNode;
		}
		return null;
  },
  
  makeUnselection: function() {
    this.unselect();
  },
  
  endDrag: function(e) {
		this.proxy.onDragEnd(e);
		this.onDragEnd(e);
		this.dragging = false;    
  },

	handleMouseUp: function(e) {
		this.clearTimer();
		this.removeListeners();
		/* One more handleMouseMove to make ensure that everything is current. */
		this.handleMouseMove(e);
    if (this.dragging) { // TODO makes sense to check this conditional?
      this.endDrag(e);
    }
    this.onMouseUp(e);
		this.makeUnselection();
	},
	
	startDragIfMoveThreshHold: function(e) {
		var dx = Math.abs(this.startX - YAHOO.util.Event.getPageX(e));
		var dy = Math.abs(this.startY - YAHOO.util.Event.getPageY(e));
		var ct = 3; /* click threshhold in pixels */
		if (dx > ct || dy > ct) {
			this.startDrag(e);
		}
	},
		
	handleDrag: function(e) {
		this.proxy.onDrag(e);
    this.onDrag(e);
	},
	
	handleMouseMove: function(e) {
	  /* Internet Explorer hack to see if button was released outside of window. */
		if (YAHOO.util.Event.isIE && !e.button){
			// TODO Really need prevent default?
			YAHOO.util.Event.preventDefault(e);
			this.handleMouseUp(e);
			return;
		}
		
		if (!this.dragging) {
		  this.startDragIfMoveThreshHold(e);
		}
		
		if (this.dragging) {
      this.handleDrag(e);
		}
		
		YAHOO.util.Event.preventDefault(e);
	},
	
  /* Stub and default hooks for the draggables */
  onMouseDown: function(e) {},
  onSelect: function(draggable) {YAHOO.util.Dom.addClass(draggable, "selected");},
  onDragStart: function(e) {},
  onDrag: function(e) {},
  onDragEnd: function(e) {},
  onUnselect: function(draggable) {YAHOO.util.Dom.removeClass(draggable, "selected");},
  onMouseUp: function(e) {}
};
