/******************************************************************************
 *	Copyright (c) 2004 Actuate Corporation and others.
 *	All rights reserved. This program and the accompanying materials 
 *	are made available under the terms of the Eclipse Public License v1.0
 *	which accompanies this distribution, and is available at
 *		http://www.eclipse.org/legal/epl-v10.html
 *	
 *	Contributors:
 *		Actuate Corporation - Initial implementation.
 *****************************************************************************/

BirtDndManager = function( ) { };

BirtDndManager.prototype = {

	__dropTargetManager: null, //PRIVATE - extending classes must use accessor method
	
	__mousemoveHandlerFunction: null,
	
	__dropHandlerFunction: null,
		
	currentDropTarget: null, //Element that is the current valid drop target
	
	currentDragElement: null, //Element that is currently being dragged
	
	
	/**
	Extending classes should use this method to set the drop target manager if
	custom drop behavior is desired
	@param {DropTargetManager}
	*/
	setDropTargetManager: function( dTargetManager)
	{
		this.__dropTargetManager = dTargetManager;
	},
		
	/**
	Drop targets call this method to register as accepting "dragType" items
	*/		
	addAssociation: function(dragType, targetHtmlId, target)
	{
		if(this.__dropTargetManager == null)
		{
			throw new WRError("BirtDndManager", "DropTargetManager not set");
		}
		this.__dropTargetManager.addAssociation(dragType, targetHtmlId, target);
	},
	
	/**
	Delete an association when the corresponding html element is removed
	@param dragType
	@param dropTargetId id attribute of html element to remove
	*/
	deleteAssociation: function(dragType, htmlId)
	{
		if(this.__dropTargetManager == null)
		{
			throw new WRError("BirtDndManager", "DropTargetManager not set");
		}
		this.__dropTargetManager.deleteAssociation(dragType, htmlId);
	},
	
	/**
	startDrag redefines itself the first time it is called based on the desired type of drag drop behavior.<b>
	*/ 
	startDrag: function(element, event, dragType)
	{
			//If there is no drop target manager, define startDrag as default
		if(this.__dropTargetManager == null)
		{
			this.startDrag = this.__getDefaultStartDrag();			
		}
		else
		{
			this.startDrag = this.__getCustomStartDrag();
		}
		this.startDrag(element, event, dragType);
	},
	
	/**
	Default start drag function handles elements that drag but do not have drop targets
	@returns {function} default start drag function
	*/
	__getDefaultStartDrag: function()
	{
		return function(element, event, dragType)
		{
			debug("default startDrag");
			this.eventMouseout = this.elementMouseout.bindAsEventListener(this);
		
			this.__mousemoveHandlerFunction = this.__moveElement.bindAsEventListener(this);
			this.__dropElementFunction = this.__dropElement.bindAsEventListener(this);
		
			var zIndex = this.__activateDragMask();
			this.__elementSetUp(element, event, zIndex);
			this.__startDragObservers(this.__mousemoveHandlerFunction, this.__dropElementFunction);
		}
	},
	
	/**
	Custom start drag function handles elements that have drop targets
	@returns {function} custome start drag function
	*/
	__getCustomStartDrag: function()
	{	
		return function(element, event, dragType)
		{
			debug("custom startDrag");
			
			if(dragType == null) //There are no drop targets
			{
				this.__mousemoveHandlerFunction = this.__moveElement.bindAsEventListener(this);
				this.__dropElementFunction = this.__dropElement.bindAsEventListener(this);
			}
			else
			{
				this.__dropTargetManager.setUpForDrag(dragType);
				this.__mousemoveHandlerFunction = this.__moveElementWithTarget.bindAsEventListener(this);
				this.__dropElementFunction = this.__dropElementWithTarget.bindAsEventListener(this);				
			}		
			this.eventMouseout = this.elementMouseout.bindAsEventListener(this);
			
			var zIndex = this.__activateDragMask();
			this.__elementSetUp(element, event, zIndex);
			this.__startDragObservers(this.__mousemoveHandlerFunction, this.__dropElementFunction);
		}
	},
	
	__activateDragMask: function()
	{
		return Mask.show();
	},
	
	__deactivateDragMask: function()
	{
		Mask.hide();
	},

	/**
	Set up an element to be dragged
	*/
	__elementSetUp: function(elem, event, zIndex)
	{	
		//TODO if removing selections is kept, move to utility funciton
		//remove any existing selections
		if(document.selection)
		{
			document.selection.empty();
		}
		else if(window.getSelection)
		{
			var selection = window.getSelection();
			if(selection)
			{
				selection.removeAllRanges();
			}
		}
	
		this.currentDragElement = elem;
		this.currentDragElement.oldZIndex = this.currentDragElement.style.zIndex;
		this.currentDragElement.style.zIndex = zIndex;
		this.currentDropTarget = null;
			//Location of mouse
		var mouseX = Event.pointerX(event);
		var mouseY = Event.pointerY(event);
			//Distance from the edge of the element to edge of browser
		var offsets = Position.cumulativeOffset(elem);
			//Distance from the edge of the element of mouse
		elem.offsetX =  (mouseX - offsets[0]);
		elem.offsetY =  (mouseY - offsets[1]);
			
			//Used for revert effect
		elem.originalTop = parseInt(elem.style.top || '0');
		elem.originalLeft = parseInt(elem.style.left || '0');		 
	},
	
	/**
	Start drag event observing
	*/
	__startDragObservers: function(mousemoveHandlerFunction, dropHandlerFunction)
	{	
		Event.observe(document, "mousemove", mousemoveHandlerFunction, false);
		Event.observe(document, "mouseup", dropHandlerFunction, false);
	},	
	
	/**
	Move element
	*/
	__moveElement: function(event)
	{
		var xPos = Event.pointerX(event);
		var yPos = Event.pointerY(event);
		
		this.__moveCurrentDragElement(xPos, yPos);				
	},
		
	/**
	Move element and use DropTargetManager to check if it can be dropped
	*/
	__moveElementWithTarget: function(event)
	{	
		var xPos = Event.pointerX(event);
		var yPos = Event.pointerY(event);
		
		this.__moveCurrentDragElement(xPos, yPos);
		
		var oldDropTarget = this.currentDropTarget; //is the state changing?
		var dropTarget = this.__dropTargetManager.getDropTarget(xPos, yPos);
		
			//Change indicator to drop allowed
		if(dropTarget)
		{
			this.currentDropTarget = dropTarget;				
			if(!oldDropTarget) 
			{
				var nodes = this.currentDragElement.childNodes;
				this.toggleDropIndicator(true, nodes);
			}
		}
		else //Change indicator to drop forbidden
		{
			this.currentDropTarget = null;
			if(oldDropTarget)
			{
				var nodes = this.currentDragElement.childNodes;
				this.toggleDropIndicator(false, nodes);
			}
		}			
	},
	
	__moveCurrentDragElement: function(x, y)
	{
		var offsets = Position.cumulativeOffset(this.currentDragElement);
	    	//subtract the element's current left, top coordinates from the offsets
	    offsets[0] -= parseInt(this.currentDragElement.style.left || '0');
	    offsets[1] -= parseInt(this.currentDragElement.style.top || '0');
	        
	    var style = this.currentDragElement.style;
	     
	    	//take current mouse position, subtract difference in drag object position, s
	    style.left = (x - offsets[0] - this.currentDragElement.offsetX) + "px";
	    style.top  = (y - offsets[1] - this.currentDragElement.offsetY) + "px";   
	},
	
	/**
	Check if the mouse button is no longer pressed, if not, call
	eventDropped. (IE specific)<b>
	*/ 
	__mouseStillDown: function(event)
	{
		/**
		Check that the mouse button is still down
		(used to detect roll off the screen in IE)
		*/
		//debug("check mouse down");
			//TODO refine this Check that element is really moving	
		if(!event.which && event.button == "0")
		{
			this.eventDropped(event);
			return true;
		}
		return false;
	},
	
		
	/**
	Handle mouseout of drag element
	*/
	elementMouseout: function(event)
	{
		//debug("mouseout");
		var target = Event.element( event );
		if(target.tagName.toLowerCase() == "iframe")
		{
			Event.stop(event);
			this.eventDropped(event);
		}
		
	},
	
	__dropElement: function(event)
	{
		debug("elementDropped ");
		this.__deactivateDragMask();
		this.currentDragElement.style.zIndex = this.currentDragElement.oldZIndex;
			//TODO handle case of mouse dragged off browser
	    Event.stopObserving(document, "mousemove", this.__mousemoveHandlerFunction);
	    Event.stopObserving(document, "mouseup", this.__dropElementFunction);
	    Event.stopObserving(document, "mouseout", this.eventMouseout);
	},
	
	__dropElementWithTarget: function(event)
	{
		this.__dropElement(event);
	    
	    this.__dropTargetManager.dropElementWithTarget(event, this.currentDragElement, this.currentDropTarget);
	},
	
     
     /**
     Toggle drop indicator to show if item can be dropped
     @param accept {boolean} Indicates if item is accepted
     @param nodes {Array} Potential imageHolder nodes //TODO once indicator is decided on can be made more efficient
     */
     toggleDropIndicator: function(accept, nodes)
     {     	
		for(var j = 0 ; j < nodes.length ; j++)
		{
			if(nodes[j].imageHolder)
			{
				accept ? nodes[j].childNodes[0].style.display = "none" : nodes[j].childNodes[0].style.display = "block";
				accept ? nodes[j].childNodes[1].style.display = "block" : nodes[j].childNodes[1].style.display = "none";
				break;
			}
		}
     }
       
}