////////////////////////////////////////////////////////////////////////////////
//
//  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.dragClasses
{

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.InteractiveObject;
import flash.events.Event;
import flash.events.IEventDispatcher;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.system.ApplicationDomain;
import flash.utils.getQualifiedClassName;

import mx.core.DragSource;
import mx.core.IFlexModule;
import mx.core.IUIComponent;
import mx.core.UIComponent;
import mx.core.mx_internal;
import mx.effects.EffectInstance;
import mx.effects.Move;
import mx.effects.Zoom;
import mx.events.DragEvent;
import mx.events.EffectEvent;
import mx.events.InterDragManagerEvent;
import mx.events.InterManagerRequest;
import mx.events.SandboxMouseEvent;
import mx.managers.CursorManager;
import mx.managers.DragManager;
import mx.managers.ISystemManager;
import mx.managers.SystemManager;
import mx.modules.ModuleManager;
import mx.styles.CSSStyleDeclaration;
import mx.styles.IStyleManager2;
import mx.styles.StyleManager;

use namespace mx_internal;

[ExcludeClass]

/**
 *  @private
 *  A helper class for DragManager that displays the drag image
 */
public class DragProxy extends UIComponent
{
    include "../../core/Version.as";

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

    /**
     *  Constructor.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function DragProxy(dragInitiator:IUIComponent,
							  dragSource:DragSource)
    {
		super();

        this.dragInitiator = dragInitiator;
        this.dragSource = dragSource;

        var sm:ISystemManager = dragInitiator.systemManager.
									topLevelSystemManager as ISystemManager;
		
		var ed:IEventDispatcher = sandboxRoot = sm.getSandboxRoot();

        ed.addEventListener(MouseEvent.MOUSE_MOVE,
							mouseMoveHandler, true);
        
		ed.addEventListener(MouseEvent.MOUSE_UP,
							mouseUpHandler, true);

        ed.addEventListener(KeyboardEvent.KEY_DOWN,
							keyDownHandler);

        ed.addEventListener(KeyboardEvent.KEY_UP,
							keyUpHandler);
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden methods
    //
    //--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override public function initialize():void
	{
		super.initialize();

		// in case we go offscreen
		dragInitiator.systemManager.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, 
													 mouseLeaveHandler);

		// Make sure someone has focus, otherwise we
		// won't get keyboard events.
		if (!getFocus())
			setFocus();
	}

    /**
     *  @private
     */
    override public function get styleManager():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 (this.dragInitiator is IFlexModule)
            return StyleManager.getStyleManager(IFlexModule(dragInitiator).moduleFactory);
        
        return super.styleManager;
    }
    
    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  Class of current cursor being displayed.
     */
    private var cursorClass:Class = null;

    /**
     *  @private
     *  ID of current cursor.
     */
    private var cursorID:int = CursorManager.NO_CURSOR;

    /**
     *  @private
     *  Last keyboard event received
     */
    private var lastKeyEvent:KeyboardEvent;

    /**
     *  @private
     *  Last Mouse event received
     */
    private var lastMouseEvent:MouseEvent;

    /**
     *  @private
     *  Root of sandbox
     */
    private var sandboxRoot:IEventDispatcher;

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    public var dragInitiator:IUIComponent;

    /**
     *  @private
     */
    public var dragSource:DragSource;

    /**
     *  @private
     */
    public var xOffset:Number;

    /**
     *  @private
     */
    public var yOffset:Number;

    /**
     *  @private
     */
    public var startX:Number;

    /**
     *  @private
     */
    public var startY:Number;

    /**
     *  @private
     */
    public var target:DisplayObject = null;

    /**
     *  @private
     *  Current drag action - NONE, COPY, MOVE or LINK
     */
    public var action:String;

    /**
     *  @private
     *  whether move is allowed or not
     */
    public var allowMove:Boolean = true;

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    public function showFeedback():void
    {
        var newCursorClass:Class = cursorClass;
		var styleSheet:CSSStyleDeclaration =
						styleManager.getMergedStyleDeclaration("mx.managers.DragManager");

        if (action == DragManager.COPY)
            newCursorClass = styleSheet.getStyle("copyCursor");
        else if (action == DragManager.LINK)
            newCursorClass = styleSheet.getStyle("linkCursor");
        else if (action == DragManager.NONE)
            newCursorClass = styleSheet.getStyle("rejectCursor");
        else
            newCursorClass = styleSheet.getStyle("moveCursor");

        if (newCursorClass != cursorClass)
        {
            cursorClass = newCursorClass;
            if (cursorID != CursorManager.NO_CURSOR)
                cursorManager.removeCursor(cursorID);
            cursorID = cursorManager.setCursor(cursorClass, 2, 0, 0);
        }
    }

    /**
     *  @private
     */
    public function checkKeyEvent(event:KeyboardEvent):void
    {
        if (target)
        {
            // Ignore repeat events. We only send the dragOver
            // event when the key state changes.
            if (lastKeyEvent
                && (event.type == lastKeyEvent.type)
                && (event.keyCode == lastKeyEvent.keyCode))
            {
                return;
            }

            lastKeyEvent = event;

            // Dispatch a "dragOver" event.
            var dragEvent:DragEvent = new DragEvent(DragEvent.DRAG_OVER);
			dragEvent.dragInitiator = dragInitiator;
            dragEvent.dragSource = dragSource;
            dragEvent.action = action;
            dragEvent.ctrlKey = event.ctrlKey;
            dragEvent.altKey = event.altKey;
            dragEvent.shiftKey = event.shiftKey;
            var pt:Point = new Point();
            pt.x = lastMouseEvent.localX;
            pt.y = lastMouseEvent.localY;
            pt = DisplayObject(lastMouseEvent.target).localToGlobal(pt);
            pt = DisplayObject(target).globalToLocal(pt);
            dragEvent.localX = pt.x;
            dragEvent.localY = pt.y;

            _dispatchDragEvent(target, dragEvent);

            showFeedback();
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Overridden event handlers
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override protected function keyDownHandler(event:KeyboardEvent):void
    {
        // On every key down call the mouseMove because the drag
        // feedback may change
        checkKeyEvent(event);
    }

    /**
     *  @private
     */
    override protected function keyUpHandler(event:KeyboardEvent):void
    {
        checkKeyEvent(event);
    }

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


    /**
     *  @private
     */
    public function stage_mouseMoveHandler(event:MouseEvent):void
    {
		if (event.target != stage)
			return;

		mouseMoveHandler(event);
	}

	/**
	 *  @private
	 */
	private function dispatchDragEvent(type:String, mouseEvent:MouseEvent, eventTarget:Object):void
	{
		var dragEvent:DragEvent = new DragEvent(type);
		var pt:Point = new Point();
		
		dragEvent.dragInitiator = dragInitiator;
		dragEvent.dragSource = dragSource;
		dragEvent.action = action;
		dragEvent.ctrlKey = mouseEvent.ctrlKey;
		dragEvent.altKey = mouseEvent.altKey;
		dragEvent.shiftKey = mouseEvent.shiftKey;
		pt.x = lastMouseEvent.localX;
		pt.y = lastMouseEvent.localY;
		pt = DisplayObject(lastMouseEvent.target).localToGlobal(pt);
		pt = DisplayObject(eventTarget).globalToLocal(pt);
		dragEvent.localX = pt.x;
		dragEvent.localY = pt.y;
		_dispatchDragEvent(DisplayObject(eventTarget), dragEvent);
	}
	
    /**
     *  @private
     */
    private function _dispatchDragEvent(target:DisplayObject, event:DragEvent):void
    {
		// in trusted mode, the target could be in another application domain
		// in untrusted mode, the mouse events shouldn't work so we shouldn't be here

		// same domain
		if (isSameOrChildApplicationDomain(target))
			target.dispatchEvent(event);
		else
		{
			// wake up all the other DragManagers
			var me:InterManagerRequest = new InterManagerRequest(InterManagerRequest.INIT_MANAGER_REQUEST);
			me.name = "mx.managers::IDragManager";
			sandboxRoot.dispatchEvent(me);
			// bounce this message off the sandbox root and hope
			// another DragManager picks it up
			var mde:InterDragManagerEvent = new InterDragManagerEvent(InterDragManagerEvent.DISPATCH_DRAG_EVENT, false, false,
													event.localX,
													event.localY,
													event.relatedObject,
													event.ctrlKey,
													event.altKey,
													event.shiftKey,
													event.buttonDown,
													event.delta,
													target,
													event.type,
													event.dragInitiator,
													event.dragSource,
													event.action,
													event.draggedItem
													);
			sandboxRoot.dispatchEvent(mde);
		}
	}

	private function isSameOrChildApplicationDomain(target:Object):Boolean
	{
		var swfRoot:DisplayObject = SystemManager.getSWFRoot(target);
		if (swfRoot)
			return true;

        if (ModuleManager.getAssociatedFactory(target))
            return true;
        
		var me:InterManagerRequest = new InterManagerRequest(InterManagerRequest.SYSTEM_MANAGER_REQUEST);
		me.name = "hasSWFBridges";
		sandboxRoot.dispatchEvent(me);
		
		// if no bridges, it might be a private/internal class so return true and hope we're right
		if (!me.value) return true;

		return false;
	}

    /**
     *  @private
     */
    public function mouseMoveHandler(event:MouseEvent):void
    {
        var dragEvent:DragEvent;
        var dropTarget:DisplayObject;
        var i:int;

        lastMouseEvent = event;

        var pt:Point = new Point();
        var point:Point = new Point(event.localX, event.localY);
        var stagePoint:Point = DisplayObject(event.target).localToGlobal(point);
        point = DisplayObject(sandboxRoot).globalToLocal(stagePoint);
        var mouseX:Number = point.x;
        var mouseY:Number = point.y;
        x = mouseX - xOffset;
        y = mouseY - yOffset;

        // The first time through we only want to position the proxy.
        if (!event)
        {
            return;
        }


		// trace("===>DragProxy:mouseMove");
		var targetList:Array; /* of DisplayObject */
		targetList = [];
		DragProxy.getObjectsUnderPoint(DisplayObject(sandboxRoot), stagePoint, targetList);

		var newTarget:DisplayObject = null;
		// trace("   ", targetList.length, "objects under point");
		
		// targetList is in depth order, and we want the top of the list. However, we
		// do not want the target to be a decendent of us.
		var targetIndex:int = targetList.length - 1;
		while (targetIndex >= 0)
		{
			newTarget = targetList[targetIndex];
			if (newTarget != this && !contains(newTarget))
				break;
			targetIndex--;
		}
		// trace("    skipped", targetList.length - targetIndex - 1);
			
        // If we already have a target, send it a dragOver event
        // if we're still over it.
        // If we're not over it, send it a dragExit event.
        if (target)
        {
            var foundIt:Boolean = false;
            var oldTarget:DisplayObject = target;

			dropTarget = newTarget;

			while (dropTarget)
			{
				if (dropTarget == target)
				{
					// trace("    dispatch DRAG_OVER on", dropTarget);
					// Dispatch a "dragOver" event
					dispatchDragEvent(DragEvent.DRAG_OVER, event, dropTarget);
					foundIt = true;
					break;
				} 
				else 
				{
					// trace("    dispatch DRAG_ENTER on", dropTarget);
					// Dispatch a "dragEnter" event and see if a new object
					// steals the target.
					dispatchDragEvent(DragEvent.DRAG_ENTER, event, dropTarget);
					
					// If the potential target accepted the drag, our target
					// now points to the dropTarget. Bail out here, but make 
					// sure we send a dragExit event to the oldTarget.
					if (target == dropTarget)
					{
						foundIt = false;
						break;
					}
				}
				dropTarget = dropTarget.parent;
			}

            if (!foundIt)
            {
				// trace("    dispatch DRAG_EXIT on", oldTarget);
                // Dispatch a "dragExit" event on the old target.
                dispatchDragEvent(DragEvent.DRAG_EXIT, event, oldTarget);

				if (target == oldTarget)
               		target = null;
            }
        }

        // If we don't have an existing target, go look for one.
        if (!target)
        {
            action = DragManager.MOVE;

            // Dispatch a "dragEnter" event.
			dropTarget = newTarget;
			while (dropTarget)
			{
				if (dropTarget != this)
				{
					// trace("    dispatch DRAG_ENTER on", dropTarget);
					dispatchDragEvent(DragEvent.DRAG_ENTER, event, dropTarget);
					if (target)
						break;
				}
				dropTarget = dropTarget.parent;
			}

            if (!target)
                action = DragManager.NONE;
        }
		// trace("<===DragProxy:mouseMove");


        showFeedback();
    }

    /**
     *  @private
     */
	public function mouseLeaveHandler(event:Event):void
	{
		mouseUpHandler(lastMouseEvent);
	}

    /**
     *  @private
     */
    public function mouseUpHandler(event:MouseEvent):void
    {
        var dragEvent:DragEvent;

        var sm:ISystemManager = dragInitiator.systemManager.
									topLevelSystemManager as ISystemManager;
		
		var ed:IEventDispatcher = sandboxRoot;

		ed.removeEventListener(MouseEvent.MOUSE_MOVE,
                               mouseMoveHandler, true);

        ed.removeEventListener(MouseEvent.MOUSE_UP,
                               mouseUpHandler, true);

        ed.removeEventListener(KeyboardEvent.KEY_DOWN,
                               keyDownHandler);

		// in case we go offscreen
		ed.removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, 
							   mouseLeaveHandler);

        ed.removeEventListener(KeyboardEvent.KEY_UP,
                               keyUpHandler);
		var delegate:Object = automationDelegate;
        if (target && action != DragManager.NONE)
        {
			// Dispatch a "dragDrop" event.
            dragEvent = new DragEvent(DragEvent.DRAG_DROP);
			dragEvent.dragInitiator = dragInitiator;
            dragEvent.dragSource = dragSource;
            dragEvent.action = action;
            dragEvent.ctrlKey = event.ctrlKey;
            dragEvent.altKey = event.altKey;
            dragEvent.shiftKey = event.shiftKey;
            var pt:Point = new Point();
            pt.x = lastMouseEvent.localX;
            pt.y = lastMouseEvent.localY;
            pt = DisplayObject(lastMouseEvent.target).localToGlobal(pt);
            pt = DisplayObject(target).globalToLocal(pt);
            dragEvent.localX = pt.x;
            dragEvent.localY = pt.y;
			if (delegate)
            	delegate.recordAutomatableDragDrop(target, dragEvent);
            _dispatchDragEvent(target, dragEvent);
        }
        else
        {
            action = DragManager.NONE;
        }

        // Do the drop effect.
        // If the drop was accepted, zoom the proxy image into
        // the current mouse location.
        // If the drop was rejected, move the proxy image
        // back to its original location.
        if (action == DragManager.NONE)
        {
            // Tween back to original position
            var m1:Move = new Move(this);
            m1.addEventListener(EffectEvent.EFFECT_END, effectEndHandler);
            m1.xFrom = x;
            m1.yFrom = y;
            m1.xTo = startX;
            m1.yTo = startY;
            m1.duration = 200;
            m1.play();
        }
        else
        {
            // Zoom into mouse location to show drag was accepted.
            var e:Zoom = new Zoom(this);
            e.zoomWidthFrom = e.zoomHeightFrom = 1.0;
            e.zoomWidthTo = e.zoomHeightTo = 0;
            e.duration = 200;
            e.play();

            var m:Move = new Move(this);
            m.addEventListener(EffectEvent.EFFECT_END, effectEndHandler);
            m.xFrom = x;
            m.yFrom = y;
            m.xTo = parent.mouseX;
            m.yTo = parent.mouseY;
            m.duration = 200;
            m.play();
        }

        // Dispatch a "dragComplete" event.
        dragEvent = new DragEvent(DragEvent.DRAG_COMPLETE);
		dragEvent.dragInitiator = dragInitiator;
        dragEvent.dragSource = dragSource;
        dragEvent.relatedObject = InteractiveObject(target);
        dragEvent.action = action;
        dragEvent.ctrlKey = event.ctrlKey;
        dragEvent.altKey = event.altKey;
        dragEvent.shiftKey = event.shiftKey;
        dragInitiator.dispatchEvent(dragEvent);

       if (delegate && action == DragManager.NONE)
	   delegate.recordAutomatableDragCancel(dragInitiator, dragEvent);
        // Hide the drag cursor
        cursorManager.removeCursor(cursorID);
        cursorID = CursorManager.NO_CURSOR;

        this.lastMouseEvent = null;
    }

    /**
     *  @private
     */
    private function effectEndHandler(event:EffectEvent):void
    {
        DragManager.endDrag();
    }

	/**
	 *  Player doesn't handle this correctly so we have to do it ourselves
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Flex 3
	 */
	private static function getObjectsUnderPoint(obj:DisplayObject, pt:Point, arr:Array):void
	{
		if (!obj.visible)
			return;

        if ((obj is UIComponent) && !UIComponent(obj).$visible)
			return;

		if (obj.hitTestPoint(pt.x, pt.y, true))
		{
			if (obj is InteractiveObject && InteractiveObject(obj).mouseEnabled)
				arr.push(obj);
			if (obj is DisplayObjectContainer)
			{
				var doc:DisplayObjectContainer = obj as DisplayObjectContainer;
				if (doc.mouseChildren)
				{
					// we use this test so we can test in other application domains
					if ("rawChildren" in doc)
					{
						var rc:Object = doc["rawChildren"];
						n = rc.numChildren;
						for (i = 0; i < n; i++)
						{
							try
							{
								getObjectsUnderPoint(rc.getChildAt(i), pt, arr);
							}
							catch (e:Error)
							{
								//another sandbox?
							}
						}
					}
					else
					{
						if (doc.numChildren)
						{
							var n:int = doc.numChildren;
							for (var i:int = 0; i < n; i++)
							{
								try
								{
									var child:DisplayObject = doc.getChildAt(i);
									getObjectsUnderPoint(child, pt, arr);
								}
								catch (e:Error)
								{
									//another sandbox?
								}
							}
						}
					}
				}
			}
		}
	}

}

}
