/*******************************************************************************
 * Copyright (c) 2004, 2006 IBM 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.presentations;

import org.eclipse.jface.util.Geometry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

/**
 * Contains various utility methods for Presentation authors
 * 
 * @since 3.0
 */
public class PresentationUtil {
    private static Point anchor;

    private final static int HYSTERESIS = 16;
    
    private static int initialMouseButton = 0;

    private final static String LISTENER_ID = PresentationUtil.class.getName()
            + ".dragListener"; //$NON-NLS-1$

    private static Event dragEvent;

    private static Listener currentListener = null;

    private static Control dragSource;

    private static Listener dragListener = new Listener() {
        public void handleEvent(Event event) {
            dragEvent = event;
            if (dragSource != event.widget) {
                dragSource = null;
                currentListener = null;
            }
            //HACK: Since there is no mouse move event to activate the tracker...
            else {
               moveListener.handleEvent(event);
            }
        }
    };
    
    /**
     * Returns the location of the given event, in display coordinates
     * @return
     */
    public static Point getEventLoc(Event event) {
        Control ctrl = (Control) event.widget;
        return ctrl.toDisplay(new Point(event.x, event.y));
    }

    /**
     * Returns whether the mouse has moved enough to warrant
     * opening a tracker.
     */
    private static boolean hasMovedEnough(Event event) {
        //UNSUPPORTED - DragUtil in internal package.
//        Point eventLoc = DragUtil.getEventLoc(event);
      Point eventLoc = getEventLoc(event);
//        System.out.println("First Drag Loc: "+eventLoc);
        int distance = Geometry.distanceSquared(eventLoc, anchor);
//        System.out.println(distance + " ENOUGH? "+(distance >= HYSTERESIS
//            * HYSTERESIS));
//        return distance >= HYSTERESIS
//                * HYSTERESIS;
        return distance >= HYSTERESIS;
    }

    private static Listener moveListener = new Listener() {
        public void handleEvent(Event event) {
            handleMouseMove(event);
        }
    };

    private static Listener clickListener = new Listener() {
        public void handleEvent(Event e) {
            handleMouseClick(e);
        }
    };

    private static Listener mouseDownListener = new Listener() {
        public void handleEvent(Event event) {
            if (event.widget instanceof Control) {
                // Remember the button that started the drag so we
                // can forward it on the call to the 'externalDragListener'
                initialMouseButton = event.button;
                
                dragSource = (Control) event.widget;
                currentListener = (Listener) dragSource.getData(LISTENER_ID);
                //UNSUPPORTED - DragUtil in internal package.
//                anchor = DragUtil.getEventLoc(event);
                anchor = getEventLoc(event);
//                System.out.println("Anchor: "+anchor);
                if (dragEvent != null && (dragEvent.widget != dragSource)) {
                    dragEvent = null;
                }
            }
        }
    };

    private static void handleMouseClick(Event event) {
        cancelDrag();
    }

    private static void handleMouseMove(Event e) {
        if (currentListener != null && dragEvent != null && hasMovedEnough(e)) {
            if (dragSource != null && !dragSource.isDisposed()
                    && dragSource == e.widget) {
                Event de = dragEvent;
                
                // cache the current value so we can restore it later
                int originalMouseButton = de.button;
                
                // Update the button field so that the drag listener
                // can detect whether or not it's a 'right button' drag
                de.button = initialMouseButton;
                de.x = e.x;
                de.y = e.y;
                Listener l = currentListener;
                cancelDrag();
                l.handleEvent(de);
                
                // Restore the event's state so that other listeners see 
                // the original values
                de.button = originalMouseButton;
            } else {
                cancelDrag();
            }
        }
    }

    private static void cancelDrag() {
        currentListener = null;
        dragEvent = null;
        dragSource = null;

        initialMouseButton = 0;
    }

    /**
     * Adds a drag listener to the given control. The behavior is very similar
     * to control.addListener(SWT.DragDetect, dragListener), however the listener
     * attached by this method is less sensitive. The drag event is only fired
     * once the user moves the cursor more than HYSTERESIS pixels. 
     * <p>
     * This is useful for registering a listener that will trigger an editor or
     * view drag, since an overly sensitive drag listener can cause users to accidentally
     * drag views when trying to select a tab.</p>
     * <p>
     * Currently, only one such drag listener can be registered at a time. </p> 
     * 
     * @param control the control containing the drag listener
     * @param externalDragListener the drag listener to attach
     */
    public static void addDragListener(Control control,
            Listener externalDragListener) {
        control.addListener(SWT.DragDetect, dragListener);
        control.addListener(SWT.MouseUp, clickListener);
        control.addListener(SWT.MouseDoubleClick, clickListener);
        control.addListener(SWT.MouseDown, mouseDownListener);
        //HACK: for some reason there is no mouse down fired for view tabs, so for now...
//        control.addListener(SWT.Activate, mouseDownListener);
//        control.addListener(SWT.MouseMove, moveListener);
        control.setData(LISTENER_ID, externalDragListener);
    }

    /**
     * Removes a drag listener that was previously attached using addDragListener
     * 
     * @param control the control containing the drag listener
     * @param externalDragListener the drag listener to remove
     */
    public static void removeDragListener(Control control,
            Listener externalDragListener) {
        control.removeListener(SWT.DragDetect, dragListener);
        control.removeListener(SWT.MouseUp, clickListener);
        control.removeListener(SWT.MouseDoubleClick, clickListener);
        control.removeListener(SWT.MouseDown, mouseDownListener);
      //HACK: for some reason there is no mouse down fired for view tabs, so for now...
//        control.removeListener(SWT.Activate, mouseDownListener);
//        control.removeListener(SWT.MouseMove, moveListener);
        control.setData(LISTENER_ID, null);
        if (externalDragListener == currentListener) {
            cancelDrag();
        }
    }

}
