////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2006-2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package mx.managers
{

import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.geom.Point;

import mx.core.DragSource;
import mx.core.IFlexDisplayObject;
import mx.core.IFlexModule;
import mx.core.IFlexModuleFactory;
import mx.core.ILayoutDirectionElement;
import mx.core.IUIComponent;
import mx.core.LayoutDirection;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;
import mx.events.DragEvent;
import mx.events.Request;
import mx.managers.dragClasses.DragProxy;
import mx.styles.CSSStyleDeclaration;
import mx.styles.IStyleManager2;
import mx.styles.StyleManager;
import mx.utils.MatrixUtil;

use namespace mx_internal;

[ExcludeClass]

/**
 *  @private
 */
public class DragManagerImpl extends EventDispatcher implements IDragManager
{
    include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class variables
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private static var sm:ISystemManager;

	/**
	 *  @private
	 */
	private static var instance:IDragManager;

	/**
	 * @private
	 * 
	 * Place to hook in additional classes
	 */
	public static var mixins:Array;

	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	public static function getInstance():IDragManager
	{
		if (!instance)
		{
			sm = SystemManagerGlobals.topLevelSystemManagers[0];
			instance = new DragManagerImpl();

		}

		return instance;
	}

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	public function DragManagerImpl()
	{
		super();

		if (instance)
			throw new Error("Instance already exists.");
			
		if (mixins)
		{
			var n:int = mixins.length;
			for (var i:int = 0; i < n; i++)
			{
				new mixins[i](this);
			}
		}

		sandboxRoot = sm.getSandboxRoot();

		if (sm.isTopLevelRoot())
		{
			sm.addEventListener(MouseEvent.MOUSE_DOWN, sm_mouseDownHandler, false, 0, true);
			sm.addEventListener(MouseEvent.MOUSE_UP, sm_mouseUpHandler, false, 0, true);
		}

        if (hasEventListener("initialize"))
		    dispatchEvent(new Event("initialize"));

	}

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------
	

	/**
	 *  @private
	 *  The highest place we can listen for events in our DOM
	 */
	private var sandboxRoot:IEventDispatcher;

	/**
	 *  @private
	 *  Object that initiated the drag.
	 */
	private var dragInitiator:IUIComponent;

	/**
	 *  @private
	 *  Object being dragged around.
	 */
	public var dragProxy:DragProxy;

	/**
	 *  @private
	 */
	public var bDoingDrag:Boolean = false;

	/**
	 *  @private
	 */
	private var mouseIsDown:Boolean = false;

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  Read-only property that returns <code>true</code>
	 *  if a drag is in progress.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function get isDragging():Boolean
	{
		return bDoingDrag;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Initiates a drag and drop operation.
	 *
	 *  @param dragInitiator IUIComponent that specifies the component initiating
	 *  the drag.
	 *
	 *  @param dragSource DragSource object that contains the data
	 *  being dragged.
	 *
	 *  @param mouseEvent The MouseEvent that contains the mouse information
	 *  for the start of the drag.
	 *
	 *  @param dragImage The image to drag. This argument is optional.
	 *  If omitted, a standard drag rectangle is used during the drag and
	 *  drop operation. If you specify an image, you must explicitly set a 
	 *  height and width of the image or else it will not appear.
	 *
	 *  @param xOffset Number that specifies the x offset, in pixels, for the
	 *  <code>dragImage</code>. This argument is optional. If omitted, the drag proxy
	 *  is shown at the upper-left corner of the drag initiator. The offset is expressed
	 *  in pixels from the left edge of the drag proxy to the left edge of the drag
	 *  initiator, and is usually a negative number.
	 *
	 *  @param yOffset Number that specifies the y offset, in pixels, for the
	 *  <code>dragImage</code>. This argument is optional. If omitted, the drag proxy
	 *  is shown at the upper-left corner of the drag initiator. The offset is expressed
	 *  in pixels from the top edge of the drag proxy to the top edge of the drag
	 *  initiator, and is usually a negative number.
	 *
	 *  @param imageAlpha Number that specifies the alpha value used for the
	 *  drag image. This argument is optional. If omitted, the default alpha
	 *  value is 0.5. A value of 0.0 indicates that the image is transparent;
	 *  a value of 1.0 indicates it is fully opaque. 
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function doDrag(
			dragInitiator:IUIComponent, 
			dragSource:DragSource, 
			mouseEvent:MouseEvent,
			dragImage:IFlexDisplayObject = null, // instance of dragged item(s)
			xOffset:Number = 0,
			yOffset:Number = 0,
			imageAlpha:Number = 0.5,
			allowMove:Boolean = true):void
	{
		var proxyWidth:Number;
		var proxyHeight:Number;
		
		// Can't start a new drag if we're already in the middle of one...
		if (bDoingDrag)
			return;
		
		// Can't do a drag if the mouse isn't down
		if (!(mouseEvent.type == MouseEvent.MOUSE_DOWN ||
			  mouseEvent.type == MouseEvent.CLICK ||
			  mouseIsDown ||
			  mouseEvent.buttonDown))
		{
			return;
		}    
			
		bDoingDrag = true;

        if (hasEventListener("doDrag"))
    		dispatchEvent(new Event("doDrag"));

		this.dragInitiator = dragInitiator;

		// The drag proxy is a UIComponent with a single child -
		// an instance of the dragImage.
		dragProxy = new DragProxy(dragInitiator, dragSource);

        var e:Event; 
        if (hasEventListener("popUpChildren"))
            e = new DragEvent("popUpChildren", false, true, dragProxy);
        if (!e || dispatchEvent(e))	
			sm.popUpChildren.addChild(dragProxy);	

		if (!dragImage)
		{
			// No drag image specified, use default
			var dragManagerStyleDeclaration:CSSStyleDeclaration =
                getStyleManager(dragInitiator).getMergedStyleDeclaration("mx.managers.DragManager");
			var dragImageClass:Class =
				dragManagerStyleDeclaration.getStyle("defaultDragImageSkin");
			dragImage = new dragImageClass();
			dragProxy.addChild(DisplayObject(dragImage));
			proxyWidth = dragInitiator.width;
			proxyHeight = dragInitiator.height;
		}
		else
		{
			dragProxy.addChild(DisplayObject(dragImage));
			if (dragImage is ILayoutManagerClient )
				UIComponentGlobals.layoutManager.validateClient(ILayoutManagerClient (dragImage), true);
			if (dragImage is IUIComponent)
			{
				proxyWidth = (dragImage as IUIComponent).getExplicitOrMeasuredWidth();
				proxyHeight = (dragImage as IUIComponent).getExplicitOrMeasuredHeight();
			}
			else
			{
				proxyWidth = dragImage.measuredWidth;
				proxyHeight = dragImage.measuredHeight;
			}
		}

        // Set the layoutDirection of the dragProxy and dragImage to match the dragInitiator
        // to ensure that they will be in the right position and orientation.
        if (dragInitiator is ILayoutDirectionElement &&
            ILayoutDirectionElement(dragInitiator).layoutDirection == LayoutDirection.RTL)
            dragProxy.layoutDirection = LayoutDirection.RTL;
        
		dragImage.setActualSize(proxyWidth, proxyHeight);
		dragProxy.setActualSize(proxyWidth, proxyHeight);
		
		// Alpha
		dragProxy.alpha = imageAlpha;

		dragProxy.allowMove = allowMove;
		
        // Make sure any scale/rotation from the initiator will be reflected.
        var concatenatedMatrix:Matrix = 
            MatrixUtil.getConcatenatedMatrix(DisplayObject(dragInitiator), 
                                             DisplayObject(sandboxRoot));
        
        // Zero out the translation part of the matrix, as we're going to 
        // position the dragProxy explicitly further below.        
        concatenatedMatrix.tx = 0;
        concatenatedMatrix.ty = 0;
        // Combine with the matrix of the dragImage if it has any.
        var m:Matrix = dragImage.transform.matrix;
        if (m)
        {
            concatenatedMatrix.concat(dragImage.transform.matrix);
            dragImage.transform.matrix = concatenatedMatrix;
        }
        
        // Find mouse coordinates in global space
        var nonNullTarget:Object = mouseEvent.target;
		if (nonNullTarget == null)
			nonNullTarget = dragInitiator;
		
		var point:Point = new Point(mouseEvent.localX, mouseEvent.localY);
		point = DisplayObject(nonNullTarget).localToGlobal(point);
		point = DisplayObject(sandboxRoot).globalToLocal(point);
		var mouseX:Number = point.x;
		var mouseY:Number = point.y;

        // Find the proxy origin in global space
        var proxyOrigin:Point = DisplayObject(dragInitiator).localToGlobal(new Point(-xOffset, -yOffset));
        proxyOrigin = DisplayObject(sandboxRoot).globalToLocal(proxyOrigin);
        
        // Set dragProxy.offset to the mouse offset within the drag proxy.
        dragProxy.xOffset = mouseX - proxyOrigin.x;
        dragProxy.yOffset = mouseY - proxyOrigin.y;
                
        // Setup the initial position of the drag proxy.
        dragProxy.x = proxyOrigin.x;
        dragProxy.y = proxyOrigin.y;
		
		// Remember the starting location of the drag proxy so it can be
		// "snapped" back if the drop was refused.
		dragProxy.startX = dragProxy.x;
		dragProxy.startY = dragProxy.y;

		// Turn on caching.
		if (dragImage is DisplayObject) 
			DisplayObject(dragImage).cacheAsBitmap = true;
			

		var delegate:Object = dragProxy.automationDelegate;
		if (delegate)
			delegate.recordAutomatableDragStart(dragInitiator, mouseEvent);
	}
	
	/**
	 *  Call this method from your <code>dragEnter</code> event handler if you accept
	 *  the drag/drop data.
	 *  For example: 
	 *
	 *  <pre>DragManager.acceptDragDrop(event.target);</pre>
	 *
	 *	@param target The drop target accepting the drag.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function acceptDragDrop(target:IUIComponent):void
	{
		// trace("-->acceptDragDrop for DragManagerImpl", sm, target);

		if (dragProxy)
			dragProxy.target = target as DisplayObject;

        if (hasEventListener("acceptDragDrop"))
    		dispatchEvent(new Request("acceptDragDrop", false, false, target));

	}
	
	/**
	 *  Sets the feedback indicator for the drag and drop operation.
	 *  Possible values are <code>DragManager.COPY</code>, <code>DragManager.MOVE</code>,
	 *  <code>DragManager.LINK</code>, or <code>DragManager.NONE</code>.
	 *
	 *  @param feedback The type of feedback indicator to display.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function showFeedback(feedback:String):void
	{
		// trace("-->showFeedback for DragManagerImpl", sm, feedback);
		if (dragProxy)
		{
			if (feedback == DragManager.MOVE && !dragProxy.allowMove)
				feedback = DragManager.COPY;

			dragProxy.action = feedback;
		}

        if (hasEventListener("showFeedback"))
    		dispatchEvent(new Request("showFeedback", false, false, feedback));

	}
	
	/**
	 *  Returns the current drag and drop feedback.
	 *
	 *  @return  Possible return values are <code>DragManager.COPY</code>, 
	 *  <code>DragManager.MOVE</code>,
	 *  <code>DragManager.LINK</code>, or <code>DragManager.NONE</code>.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	public function getFeedback():String
	{
        if (hasEventListener("getFeedback"))
        {
		    var request:Request = new Request("getFeedback", false, true);
		    if (!dispatchEvent(request))
		    {
			    return request.value as String;
		    }
        }

		// trace("<--getFeedback for DragManagerImpl", sm);
		return dragProxy ? dragProxy.action : DragManager.NONE;
	}
	
	/**
	 *  @private
	 */
	public function endDrag():void
	{
        var e:Event;
        if (hasEventListener("endDrag"))
        {
            e = new Event("endDrag", false, true);
        }
        
		if (!e || dispatchEvent(e))
		{
			if (dragProxy)
			{
				sm.popUpChildren.removeChild(dragProxy);	
				
                if (dragProxy.numChildren > 0)
				    dragProxy.removeChildAt(0);	// The drag image is the only child
				dragProxy = null;
			}
		}

		dragInitiator = null;
		bDoingDrag = false;

	}

    /**
     *  @private
     */
    static private function getStyleManager(dragInitiator:IUIComponent):IStyleManager2
    {
        // If the dragInitiator has a styleManager, use that one.
        // In a situation where a main application that loads a module with drag initiator,
        // the main application may not link in the DragManager and appropriate styles.
        // We want to use the styles of the module of the dragInitiator. See SDK-24324.
        if (dragInitiator is IFlexModule)
            return StyleManager.getStyleManager(IFlexModule(dragInitiator).moduleFactory);
        
        return StyleManager.getStyleManager(sm as IFlexModuleFactory);
    }

	//--------------------------------------------------------------------------
	//
	//  Event handlers
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private function sm_mouseDownHandler(event:MouseEvent):void
	{
		mouseIsDown = true;
	}
	
	/**
	 *  @private
	 */
	private function sm_mouseUpHandler(event:MouseEvent):void
	{
		mouseIsDown = false;
	}


}

}
