// wDRAG. Thin Drag&Drop Framework
// v1.0 Cedric Savarese 
// Nov. 2005

 if(typeof wh == "undefined") {
	wh = new wHelpers();
 }
 
 function wDrag() { 
	var self     = this; 
	
	this.create = function(id) {
		this.rootId                = id;
		this.root                  = document.getElementById(id);		
		this.options               = arguments[1] || {};
		this.classNameHandle       = this.options.handleClass  || "dragHandle";
		this.classNameDragged 	   = this.options.draggedClass || "dragged"; 
		this.classNameInsertBefore = this.options.insertBefore || "insertBefore";  
		this.classNameInsertAfter  = this.options.insertAfter  || "insertAfter";  
		this.classNameInsertInside = this.options.insertInside || "insertInside";  				
		this.constraint            = this.options.constraint   || "vertical"; // not implemented
		this.draggableElements     = this.options.draggable    || ["li"];	  // not fully implemented
		this.containerElements     = this.options.containers   || ["ul"];		
		this.threshold			   = this.options.threshold    || 3; // pixels ( not implemented )
		this.onUpdate              = this.options.onUpdate; 
		this.isActive              = false;
		this.dropOn                = null;
		this.element 	    	   = null;
		this.ghostElement 	       = null;
		
		if(!this.root) { 
			return false;
		}
		wh.debug('drag&drop initialization');
		var li = this.root.getElementsByTagName('li');
		var lg = li.length;
		for(var i=0;i<lg; i++) {
			wh.addEvent(li[i],'mouseover', this.watch);			
		}	
		wh.addEvent(this.root,'mouseout', this.unwatch);
		wh.addEvent(document,'mousedown', this.start);
		return true;
	}
	
	this.start = function(e) {
		if(self.isLeftClick(e)) {
			
			var n = wh.getSourceElement(e);
			
			wh.debug('Left click on ' + n.tagName + '[id=' + n.id + ', class=' + n.className+']');
			
			if(n.className == self.classNameHandle) {
				wh.debug('Start drag');

				if(!e) e = window.event;
				self.element      = self.getDraggableElement(n);
				self.element.className = self.element.className  + ' ' + self.classNameDragged;
				self.ghostElement = self.getGhostElement(self.element, self.pointerX(e), self.pointerY(e)); 
				self.isActive     = true;
				self.insertType	  = "before";			
				self.originalLeft = self.currentLeft();
				self.originalTop  = self.currentTop();
				self.originalX    = self.element.offsetLeft;
				self.originalY    = self.element.offsetTop;

				wh.addEvent(document.body,'selectstart', self.nothing);
				wh.addEvent(document.body,'drag', self.nothing);
				wh.addEvent(document,'mousemove', self.move);
				wh.addEvent(document,'keypress', self.cancelDrag);
				wh.addEvent(document,'mouseup', self.stop);
				wh.preventEvent(e);
				wh.stopPropagation(e);		
			}
		}
	}
	
	this.stop = function(e) {
		if(self.isActive) {
			self.isActive = false;
			wh.removeEvent(document,'mousemove', self.move);
			wh.removeEvent(document,'keypress', self.cancelDrag);
			wh.removeEvent(document,'mouseup', self.stop);
			wh.removeEvent(document.body,'selectstart', self.nothing);
			wh.removeEvent(document.body,'drag', self.nothing);
			clearTimeout(self.insertToggleTimer);
			clearTimeout(self.insertToggleTimer2);				
			self.ghostElement.parentNode.removeChild(self.ghostElement);	
			self.element.className = self.element.className.replace(self.classNameDragged,"");
			if(self.dropOn && self.dropOn != self.element) {
				self.clearClassNames(self.dropOn);			
				if(self.isTrash(self.dropOn)) {
					if(!window.confirm("Are you sure you want to delete this?")) {
						wh.stopPropagation(e);
						wh.preventEvent(e); 
						return;	
					}
				}
				wh.debug('Stop drag on ' + self.dropOn.tagName + '[id=' + self.dropOn.id + ', class=' + self.dropOn.className+']');
				if(self.insertType=='before') {
					self.dropOn.parentNode.insertBefore(self.element,self.dropOn);
				} else if(self.insertType=='inside') {
					self.getContainerElement(self.dropOn).appendChild(self.element);
				} else {
					self.dropOn.parentNode.insertBefore(self.element,self.dropOn.nextSibling);					
				}
				self.onUpdate(self.root);
			} else {
				wh.debug('Drag canceled. Drop on was ' + self.dropOn);
			}
			wh.stopPropagation(e);
			wh.preventEvent(e); 
		}
	}
	this.cancelDrag = function(e) { 
		if(self.isActive) {
			self.element.className = self.element.className.replace(self.classNameDragged,'');
			self.clearClassNames(self.dropOn);
			self.dropOn = null;
			self.stop(e);
		}
	}
	this.move = function(e) {
		if(self.isActive) {
			if(!e) e = window.event;
			self.ghostElement.style.left = self.pointerX(e)+"px";
			self.ghostElement.style.top  = self.pointerY(e)+"px";
		}
	}
	
	this.watch = function(e) {
		if(self.isActive) {
			var n       = wh.getSourceElement(e);
			var dropOn  = self.getDroppedOnElement(n);
			if(dropOn && self.dropOn != dropOn) {
				self.clearClassNames(self.dropOn);		
				clearTimeout(self.insertToggleTimer);
				clearTimeout(self.insertToggleTimer2);
				self.dropOn = dropOn;
			//	wh.debug('Move over:' + self.dropOn.tagName + ' [id=' + self.dropOn.id + ', class=' + self.dropOn.className+']');
				self.insertType        = 'before';
				self.insertToggleTimer = null;
				self.clearClassNames(self.dropOn);
				if(self.isTrash(self.dropOn)) {
					self.toggleInsertInside();
				} else {
					self.dropOn.className  = self.dropOn.className + ' ' + self.classNameInsertBefore;
					var timer = 1250;
					if(self.hasContainerElement(self.dropOn) ) {
						clearTimeout(self.insertToggleTimer);
						self.insertToggleTimer  = setTimeout(self.toggleInsertInside,timer);
						timer+=timer;
					}		
					if(!self.isDraggable(self.dropOn.nextSibling) ) {
						clearTimeout(self.insertToggleTimer2);
						self.insertToggleTimer2 = setTimeout(self.toggleInsertAfter,timer);
					}
				}
			} 
		}		
	}
	this.unwatch = function(e) {
		
		if(self.isActive && self.dropOn) {
			if(!e) e  = window.event;
			
			var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
			while (reltg != self.root && reltg.nodeName.toUpperCase() != 'BODY') {
				reltg = reltg.parentNode;
			}
			if (reltg == self.root) return;
			
			wh.debug('Move out: '+self.dropOn.tagName+ ' [id='+ self.dropOn.id+ ', class=' + self.dropOn.className+']');
			self.clearClassNames(self.dropOn);		
			self.dropOn			   = null;
			clearTimeout(self.insertToggleTimer);
			clearTimeout(self.insertToggleTimer2);
			wh.stopPropagation(e);
		}		
	}
	this.toggleInsertAfter = function() {
		if(self.isActive && self.dropOn && !self.isDraggable(self.dropOn.nextSibling)) {	
			//wh.debug('toggleInsertAfter');
			self.insertType 	  = "after";
			self.clearClassNames(self.dropOn);			
			self.dropOn.className = self.dropOn.className + ' ' + self.classNameInsertAfter;
		}
	}

	this.toggleInsertInside = function() {
		if(self.isActive && self.dropOn && (self.hasContainerElement(self.dropOn) || self.isTrash(self.dropOn))) {	
			//wh.debug('toggleInsertInside');
			self.insertType 	  = "inside";
			self.clearClassNames(self.dropOn);	
			self.dropOn.className = self.dropOn.className + ' ' + self.classNameInsertInside;
		}
	}
	
	this.clearClassNames = function(n) {
		if(!n) return;
		n.className = n.className.replace(self.classNameInsertBefore,"");
		n.className = n.className.replace(self.classNameInsertAfter,"");
		n.className = n.className.replace(self.classNameInsertInside,"");
	}
	
	this.getDraggableElement = function(n) {
		while(n && n.tagName) {
			if(n.id == self.rootId) 
				return null;
			if(self.isDraggable(n))
				return n;
			n = n.parentNode;
		}
		return null;
	}

	this.isDraggable = function(n) {
		if(!n || n.nodeType!=1) return false;
		for(var i=0;i<self.draggableElements.length;i++) {
			if(n.tagName.toLowerCase() == self.draggableElements[i] && !self.isTrash(n) )
				return true;
		}
		return false;
	}

	this.getContainerElement  = function(n) {
		if(!n || n.nodeType!=1) return false;
		for(var j=0;j<n.childNodes.length;j++) {
			cn = n.childNodes[j];
			if(self.isContainer(cn)) return cn;
		}
		return null;
	}
	this.hasContainerElement  = function(n) {
		if(self.getContainerElement(n)) return true;
		return false;
	}
	this.isContainer = function(n) {
		if(!n || n.nodeType!=1) return false;
		// test current node
		for(var i=0;i<self.containerElements.length;i++) {
			if(n.tagName.toLowerCase() == self.containerElements[i] && !self.isTrash(n) )
				return true;
		}
		return false;
	}
	this.isTrash = function(n) {
		if(n.id && n.id=="dragDropTrash") 
			return true;
		return false;
	}
	this.getDroppedOnElement = function(n) {
		while(n && n.tagName) {
			if(n.id == self.rootId) 
				return null;
			if(self.isDraggable(n))// not implemented, all draggable elements can be dropped on
				return n;
			if(self.isTrash(n)) 
				return n;
			n = n.parentNode;
		}
		return null;
	}
	
	this.getGhostElement = function(n, x, y) {
		if(self.ghostElement && self.ghostElement.parentNode) self.ghostElement.parentNode.removeChild(self.ghostElement);
		self.ghostElement = self.root.insertBefore(n.cloneNode(true),self.root.firstChild);
		self.ghostElement.style.position = "absolute";
		self.ghostElement.style.left     = x+"px";
		self.ghostElement.style.top      = y+"px";
		
		return self.ghostElement;
	}
	this.currentLeft = function() {
		return parseInt(this.element.style.left || '0');
	}
	this.currentTop = function() {
		return parseInt(this.element.style.top || '0')
	}
	this.nothing = function(e) {
		if(!e) e = window.event;
		wh.preventEvent(e);
		wh.stopPropagation(e);		
		return false; 
	}
	
	// Below adapted from prototype.js ( (c) 2005 Sam Stephenson <sam@conio.net> )
	this.isLeftClick = function(e) {
		if(!e) e = window.event;
	    return ((e.which && e.which == 1) || (e.button && e.button == 1));
	}
	this.pointerX = function(e) {
		if(!e) e = window.event;		
		return e.pageX || (e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft));
	}
	this.pointerY = function(e) {
		if(!e) e = window.event;		
		return e.pageY || (e.clientY + (document.documentElement.scrollTop || document.body.scrollTop));
	}
 }