/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */

package net.sf.jaxodraw.gui;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeListenerProxy;

import javax.swing.SwingUtilities;
import javax.swing.Timer;

import net.sf.jaxodraw.util.JaxoConstants;
import net.sf.jaxodraw.util.JaxoUtils;


/**
 * A listener to process mouse events that happen on the canvas.
 */
public class JaxoCanvasEventListener
        implements MouseListener, MouseMotionListener, PropertyChangeListener {

    private final JaxoCanvas theCanvas; // make it a JaxoDrawingArea!
    private final PropertyChangeListenerProxy mouseLocationListener;
    private int currentMode;
    private Point mouseLocation; // Mouse location in Graph coordinates
    private final Timer scrollTimer;
    private final Point last = new Point(); // Last mouse position in graph coordinates
    private final Point lastOnScreen = new Point(); // Last mouse position on screen
    private transient MouseEvent lastDraggedEvent;

    /**
     * Set the current editing mode.
     *
     * @param mode the current editing mode.
     */
    public void setMode(final int mode) {
        this.currentMode = mode;
    }

    /**
     * Constructor.
     *
     * @param canvas the {@link JaxoCanvas canvas}. Not null.
     */
    public JaxoCanvasEventListener(final JaxoCanvas canvas) {
        this.theCanvas = canvas;
        this.mouseLocationListener
                = new PropertyChangeListenerProxy("Jaxo.mouseLocation", theCanvas);

        // Scroll to make 'last' (the last known mouse position) visible, and send an extra
        // mouseDragged event because the location within the component has changed.
        // This is similar to what setAutoscrolls(true) would do, but with control
        // over scrolling speed.
        scrollTimer =
            new Timer(250,
                new ActionListener() {
                    public void actionPerformed(final ActionEvent e) {
                        theCanvas.revalidateCanvasFast();

                        theCanvas.scrollRectToVisible(toComponentCoordinates(
                                new Rectangle(last.x - 10, last.y - 10, 20, 20)));

                        if (lastDraggedEvent != null) {
                            final Point p = theCanvas.getLocationOnScreen();

                            mouseDragged(new MouseEvent(
                                    lastDraggedEvent.getComponent(),
                                    lastDraggedEvent.getID(),
                                    lastDraggedEvent.getWhen(),
                                    lastDraggedEvent.getModifiers(),
                                    lastOnScreen.x - p.x,
                                    lastOnScreen.y - p.y,
                                    lastDraggedEvent.getClickCount(),
                                    lastDraggedEvent.isPopupTrigger(),
                                    lastDraggedEvent.getButton()));
                        }
                    }
                });

        scrollTimer.setInitialDelay(0);
    }

    /**
     * The action to be taken when the mouse is clicked on the canvas.
     *
     * @param e The corresponding mouse event.
     */
    public void mouseClicked(final MouseEvent e) {
        if (currentMode == JaxoConstants.SELECT && JaxoUtils.isButton2(e)) {
            if (!theCanvas.unMarkGraph()) {
                theCanvas.repaint(); // handles etc., should not be needed in theory
            }
        } else if (JaxoUtils.isButton3(e) && JaxoUtils.isDoubleClick(e)) {
            theCanvas.editNearestObject(toGraphCoordinates(e.getPoint()));
        }
    }

    /**
     * The action to be taken when the mouse is pressed on the canvas.
     *
     * @param e The corresponding mouse event.
     */
    public void mousePressed(final MouseEvent e) {
        scrollTimer.start();

        if (currentMode == JaxoConstants.ZOOM) {
            return;
        }

        final Point p = toGraphCoordinates(e.getPoint());

        if (JaxoUtils.isButton3(e)) {
            theCanvas.initiateSelect(p);
        } else if (JaxoUtils.isButton1(e)) {
            theCanvas.initiateEdit(p);
        }

        last.setLocation(p);
        lastOnScreen.setLocation(e.getPoint());
        SwingUtilities.convertPointToScreen(lastOnScreen, theCanvas.asComponent());
    }

    /**
     * The action to be taken when the mouse is released on the canvas.
     *
     * @param e The corresponding mouse event.
     */
    public void mouseReleased(final MouseEvent e) {
        lastDraggedEvent = null;
        scrollTimer.stop();

        if (currentMode == JaxoConstants.ZOOM) {
            return;
        }

        final Point p = toGraphCoordinates(e.getPoint());

        if (JaxoUtils.isButton3(e)) {
            theCanvas.finalizeSelect(p);
        } else if (JaxoUtils.isButton1(e)) {
            theCanvas.finalizeEdit();
        }
    }

    /**
     * Process the event when the mouse enters the canvas. Only sets the mouse location.
     *
     * @param e The mouse event to process.
     */
    public void mouseEntered(final MouseEvent e) {
        setMouseLocation(toGraphCoordinates(e.getPoint()), true);
    }

    /**
     * Process the event when the mouse exits the canvas. Only clears the mouse location.
     *
     * @param e The mouse event to process.
     */
    public void mouseExited(final MouseEvent e) {
        setMouseLocation(null, true);
    }

    /**
     * The action to be taken when the mouse is dragged on the canvas.
     *
     * @param e The corresponding mouse event.
     */
    public void mouseDragged(final MouseEvent e) {
        final Point p = toGraphCoordinates(e.getPoint());

        setMouseLocation(p, true);

        lastDraggedEvent = e;

        if (currentMode == JaxoConstants.ZOOM) {
            return;
        }

        if (JaxoUtils.isButton3(e)) {
            theCanvas.continueSelect(p);
        } else if (JaxoUtils.isButton1(e)) {
            theCanvas.continueEdit(p, last);
        }

        last.setLocation(p);
        lastOnScreen.setLocation(e.getPoint());
        SwingUtilities.convertPointToScreen(lastOnScreen, theCanvas.asComponent());
    }

    /**
     * Process the event when the mouse is moved on the canvas.
     * Sets the MouseLocation and PointsAidLocation.
     *
     * @param e The mouse event to process.
     */
    public void mouseMoved(final MouseEvent e) {
        final Point p = toGraphCoordinates(e.getPoint());
        setMouseLocation(p, true);
        theCanvas.updatePointsAid(p); // should be in setMouseLocation?
    }

    /** {@inheritDoc} */
    public void propertyChange(final PropertyChangeEvent evt) {
        if ("Jaxo.mode".equals(evt.getPropertyName())) {
            this.currentMode = ((Integer) evt.getNewValue()).intValue();
        } else if ("Jaxo.mouseLocation".equals(evt.getPropertyName())) {
            setMouseLocation((Point) evt.getNewValue(), false);
        }
    }

      //
     // private methods
    //

    private Point toGraphCoordinates(final Point p) {
        return theCanvas.toGraphCoordinates(p);
    }

    private Rectangle toComponentCoordinates(final Rectangle r) {
        return theCanvas.toComponentCoordinates(r);
    }

    /**
     * Sets the current mouse location in graph coordinates.
     * Notifies any registered PropertyChangeListeners.
     *
     * @param value The new mouse coordinates.
     * @param notify true if the canvas should be notified of the location change.
     */
    private void setMouseLocation(final Point value, final boolean notify) {
        if ((value == null) ? (mouseLocation != null)
                                : (!value.equals(mouseLocation))) {
            final Point old = mouseLocation;

            mouseLocation = (value == null) ? null : (Point) value.clone();

            if (notify) {
                mouseLocationListener.propertyChange(
                        new PropertyChangeEvent(this, "Jaxo.mouseLocation", old,
                        (mouseLocation == null) ? null : mouseLocation.clone()));
            }
        }
    }
}
