/*
 * Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org
 * Copyright (c) 1998-2008, University of Maryland
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
 * and the following disclaimer in the documentation and/or other materials provided with the
 * distribution.
 *
 * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
 * contributors may be used to endorse or promote products derived from this software without specific
 * prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package edu.umd.cs.piccolox.handles;

import java.awt.Color;
import java.awt.Shape;
import java.awt.event.InputEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.ObjectInputStream;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PDragSequenceEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.event.PInputEventFilter;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.util.PBounds;
import edu.umd.cs.piccolo.util.PDimension;
import edu.umd.cs.piccolox.util.PLocator;
import edu.umd.cs.piccolox.util.PNodeLocator;

/**
 * <b>PHandle</b> is used to modify some aspect of Piccolo when it is dragged.
 * Each handle has a PLocator that it uses to automatically position itself. See
 * PBoundsHandle for an example of a handle that resizes the bounds of another
 * node.
 * 
 * @version 1.0
 * @author Jesse Grosjean
 */
public class PHandle extends PPath {
    private final class HandleDragHandler extends PDragSequenceEventHandler {
        protected void startDrag(final PInputEvent event) {
            super.startDrag(event);
            startHandleDrag(event.getPositionRelativeTo(PHandle.this), event);
        }

        protected void drag(final PInputEvent event) {
            super.drag(event);
            final PDimension aDelta = event.getDeltaRelativeTo(PHandle.this);
            if (aDelta.getWidth() != 0 || aDelta.getHeight() != 0) {
                dragHandle(aDelta, event);
            }
        }

        protected void endDrag(final PInputEvent event) {
            super.endDrag(event);
            endHandleDrag(event.getPositionRelativeTo(PHandle.this), event);
        }
    }

    private static final long serialVersionUID = 1L;

    /** The default size for a handle. */
    public static float DEFAULT_HANDLE_SIZE = 8;
    /** Default shape to use when drawing handles. */
    public static Shape DEFAULT_HANDLE_SHAPE = new Ellipse2D.Float(0f, 0f, DEFAULT_HANDLE_SIZE, DEFAULT_HANDLE_SIZE);

    /** Default color to paint handles. */
    public static Color DEFAULT_COLOR = Color.white;

    private PLocator locator;
    private transient PDragSequenceEventHandler handleDragger;

    /**
     * Construct a new handle that will use the given locator to locate itself
     * on its parent node.
     * 
     * @param aLocator locator to use when laying out the handle
     */
    public PHandle(final PLocator aLocator) {
        super(DEFAULT_HANDLE_SHAPE);
        locator = aLocator;
        setPaint(DEFAULT_COLOR);
        installHandleEventHandlers();
    }

    /**
     * Installs the handler that notify its subclasses of handle interaction.
     */
    protected void installHandleEventHandlers() {
        handleDragger = new HandleDragHandler();

        addPropertyChangeListener(PNode.PROPERTY_TRANSFORM, new PropertyChangeListener() {
            public void propertyChange(final PropertyChangeEvent evt) {
                relocateHandle();
            }
        });

        handleDragger.setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK));
        handleDragger.getEventFilter().setMarksAcceptedEventsAsHandled(true);
        handleDragger.getEventFilter().setAcceptsMouseEntered(false);
        handleDragger.getEventFilter().setAcceptsMouseExited(false);
        // no need for moved events for handle interaction,
        handleDragger.getEventFilter().setAcceptsMouseMoved(false);
        // so reject them so we don't consume them
        addInputEventListener(handleDragger);
    }

    /**
     * Return the event handler that is responsible for the drag handle
     * interaction.
     * 
     * @return current handler for HandleDrag events
     */
    public PDragSequenceEventHandler getHandleDraggerHandler() {
        return handleDragger;
    }

    /**
     * Get the locator that this handle uses to position itself on its parent
     * node.
     * 
     * @return the locator associated with this handle
     */
    public PLocator getLocator() {
        return locator;
    }

    /**
     * Set the locator that this handle uses to position itself on its parent
     * node.
     * 
     * @param locator the locator to assign to this handle
     */
    public void setLocator(final PLocator locator) {
        this.locator = locator;
        invalidatePaint();
        relocateHandle();
    }

    /**
     * Override this method to get notified when the handle starts to get
     * dragged.
     * 
     * @param aLocalPoint point on the handle at which the event occurred
     * @param aEvent the event responsible for starting the dragging
     */
    public void startHandleDrag(final Point2D aLocalPoint, final PInputEvent aEvent) {
    }

    /**
     * Override this method to get notified as the handle is dragged.
     * 
     * @param aLocalDimension size of the drag in handle coordinates
     * @param aEvent event representing the drag
     */
    public void dragHandle(final PDimension aLocalDimension, final PInputEvent aEvent) {
    }

    /**
     * Override this method to get notified when the handle stops getting
     * dragged.
     * 
     * @param aLocalPoint point in handle coordinate system of the end of the
     *            drag
     * @param aEvent event responsible for ending the drag
     */
    public void endHandleDrag(final Point2D aLocalPoint, final PInputEvent aEvent) {
    }

    /**
     * Set's this handle's parent. Handles respond to changes in their parent's
     * bounds by invalidating themselves.
     * 
     * @param newParent the new parent to assign to this handle
     */
    public void setParent(final PNode newParent) {
        super.setParent(newParent);
        relocateHandle();
    }

    /**
     * Forces the handles to reposition themselves using their associated
     * locator.
     */
    public void parentBoundsChanged() {
        relocateHandle();
    }

    /**
     * Force this handle to relocate itself using its locator.
     */
    public void relocateHandle() {
        if (locator == null) {
            return;
        }

        final PBounds b = getBoundsReference();
        final Point2D aPoint = locator.locatePoint(null);

        if (locator instanceof PNodeLocator) {
            final PNode located = ((PNodeLocator) locator).getNode();
            final PNode parent = getParent();

            located.localToGlobal(aPoint);
            globalToLocal(aPoint);

            if (parent != located && parent instanceof PCamera) {
                ((PCamera) parent).viewToLocal(aPoint);
            }
        }

        final double newCenterX = aPoint.getX();
        final double newCenterY = aPoint.getY();

        if (newCenterX != b.getCenterX() || newCenterY != b.getCenterY()) {

            centerBoundsOnPoint(newCenterX, newCenterY);
        }

    }

    /**
     * Deserializes a PHandle from the input stream provided. Ensures tha all
     * event handles are correctly installed.
     * 
     * @param in stream from which to read the handle
     * @throws IOException is thrown if the underlying input stream fails
     * @throws ClassNotFoundException should never happen but can happen if the
     *             classpath gets messed up
     */
    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        installHandleEventHandlers();
    }
}