/*
 * Created on Mar 14, 2005
 */
package org.flexdock.docking.drag;

import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Map;

import org.flexdock.docking.Dockable;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.DockingPort;
import org.flexdock.docking.DockingStrategy;
import org.flexdock.docking.drag.effects.EffectsManager;
import org.flexdock.docking.event.DockingEvent;
import org.flexdock.docking.floating.policy.FloatPolicyManager;
import org.flexdock.event.EventManager;
import org.flexdock.util.DockingUtility;

/**
 * @author Christopher Butler
 *
 */
public class DragManager extends MouseAdapter implements MouseMotionListener {
    private static final String DRAG_CONTEXT = "DragManager.DRAG_CONTEXT";
    private static final Object LOCK = new Object();
    private static DragOperation currentDragOperation;

    private Dockable dockable;
    private DragPipeline pipeline;
    private boolean enabled;
    private Point dragOrigin;
    private HashMap dragContext;

    public static void prime() {
        // execute static initializer to preload resources
        EffectsManager.prime();
    }

    public DragManager(Dockable dockable) {
        this.dockable = dockable;
    }

    public void mousePressed(MouseEvent e) {
        if(dockable==null || dockable.getDockingProperties().isDockingEnabled()==Boolean.FALSE)
            enabled = false;
        else {
            toggleDragContext(true);
            enabled = !isDragCanceled(dockable, e);
        }
    }

    public void mouseDragged(MouseEvent evt) {
        if(!enabled)
            return;

        if(dragOrigin==null)
            dragOrigin = evt.getPoint();

        if(pipeline==null || !pipeline.isOpen()) {
            if(passedDragThreshold(evt))
                openPipeline(evt);
            else
                evt.consume();
        } else
            pipeline.processDragEvent(evt);
    }

    private boolean passedDragThreshold(MouseEvent evt) {
        double distance = dragOrigin.distance(evt.getPoint());
        float threshold = dockable.getDockingProperties().getDragThreshold().floatValue();
        return distance > threshold;
    }

    private void openPipeline(MouseEvent evt) {
        DragOperation token = new DragOperation(dockable.getComponent(), dragOrigin, evt);
        token.setDragListener(this);
        // initialize listeners on the drag-source
        initializeListenerCaching(token);

        DragPipeline pipeline = new DragPipeline();
        this.pipeline = pipeline;
        pipeline.open(token);
    }

    public void mouseMoved(MouseEvent e) {
        // doesn't do anything
    }

    public void mouseReleased(MouseEvent e) {
        if(pipeline==null || dockable.getDockingProperties().isDockingEnabled()==Boolean.FALSE)
            return;

        finishDrag(dockable, pipeline.getDragToken(), e);
        if(pipeline!=null)
            pipeline.close();
        toggleDragContext(false);
        dragOrigin = null;
        pipeline = null;
    }


    protected void finishDrag(Dockable dockable, DragOperation token, MouseEvent mouseEvt) {
        DockingStrategy docker = DockingManager.getDockingStrategy(dockable);
        DockingPort currentPort = DockingUtility.getParentDockingPort(dockable);
        DockingPort targetPort = token.getTargetPort();
        String region = token.getTargetRegion();

        // remove the listeners from the drag-source and all the old ones back in
        restoreCachedListeners(token);

        // issue a DockingEvent to allow any listeners the chance to cancel the operation.
        DockingEvent evt = new DockingEvent(dockable, currentPort, targetPort, DockingEvent.DROP_STARTED, mouseEvt, getDragContext());
        evt.setRegion(region);
        evt.setOverWindow(token.isOverWindow());
//		EventManager.notifyDockingMonitor(dockable, evt);
        EventManager.dispatch(evt, dockable);


        // attempt to complete the docking operation
        if(!evt.isConsumed())
            docker.dock(dockable, targetPort, region, token);
    }



















    private static void initializeListenerCaching(DragOperation token) {
        // it's easier for us if we remove the MouseMostionListener associated with the dragSource
        // before dragging, so normally we'll try to do that.  However, if developers really want to
        // keep them in there, then they can implement the Dockable interface for their dragSource and
        // let mouseMotionListenersBlockedWhileDragging() return false
//		if (!dockableImpl.mouseMotionListenersBlockedWhileDragging())
//			return;

        Component dragSrc = token.getDragSource();
        EventListener[] cachedListeners = dragSrc.getListeners(MouseMotionListener.class);
        token.setCachedListeners(cachedListeners);
        DragManager dragListener = token.getDragListener();

        // remove all of the MouseMotionListeners
        for (int i = 0; i < cachedListeners.length; i++) {
            dragSrc.removeMouseMotionListener((MouseMotionListener) cachedListeners[i]);
        }
        // then, re-add the DragManager
        if(dragListener!=null)
            dragSrc.addMouseMotionListener(dragListener);
    }

    private static void restoreCachedListeners(DragOperation token) {
        Component dragSrc = token.getDragSource();
        EventListener[] cachedListeners = token.getCachedListeners();
        DragManager dragListener = token.getDragListener();

        // remove the pipeline listener
        if(dragListener!=null)
            dragSrc.removeMouseMotionListener(dragListener);

        // now, re-add all of the original MouseMotionListeners
        for (int i = 0; i < cachedListeners.length; i++)
            dragSrc.addMouseMotionListener((MouseMotionListener) cachedListeners[i]);
    }

    private static boolean isDragCanceled(Dockable dockable, MouseEvent trigger) {
        DockingPort port = DockingUtility.getParentDockingPort(dockable);
        Map dragContext = getDragContext(dockable);
        DockingEvent evt = new DockingEvent(dockable, port, null, DockingEvent.DRAG_STARTED, trigger, dragContext);
        EventManager.dispatch(evt, dockable);
        return evt.isConsumed();
    }

    public static Map getDragContext(Dockable dockable) {
        Object obj = dockable==null? null: dockable.getClientProperty(DRAG_CONTEXT);
        return obj instanceof Map? (Map)obj: null;
    }

    private void toggleDragContext(boolean add) {
        if(add) {
            if(dragContext==null) {
                dragContext = new HashMap();
                dockable.putClientProperty(DRAG_CONTEXT, dragContext);
            }
        } else {
            if(dragContext!=null) {
                dragContext.clear();
                dragContext = null;
            }
            dockable.putClientProperty(DRAG_CONTEXT, null);
        }
    }

    private Map getDragContext() {
        return getDragContext(dockable);
    }

    public static boolean isFloatingAllowed(Dockable dockable) {
        return FloatPolicyManager.isFloatingAllowed(dockable);
    }

    public static DragOperation getCurrentDragOperation() {
        synchronized(LOCK) {
            return currentDragOperation;
        }
    }

    static void setCurrentDragOperation(DragOperation operation) {
        synchronized(LOCK) {
            currentDragOperation = operation;
        }
    }


}
