/*
 * Copyright (c) 2005, The JUNG Authors 
 *
 * All rights reserved.
 *
 * This software is open-source under the BSD license; see either
 * "license.txt" or
 * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
 * Created on Mar 8, 2005
 *
 */
package edu.uci.ics.jung.visualization.control;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.Collections;

import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.transform.MutableTransformer;

/** 
 * RotatingGraphMouse provides the abiity to rotate the graph using
 * the mouse. By default, it is activated by mouse button one drag
 * with the shift key pressed. The modifiers can be overridden so that
 * a different mouse/key combination activates the rotation
 * 
 * @author Tom Nelson
 */
public class RotatingGraphMousePlugin extends AbstractGraphMousePlugin
    implements MouseListener, MouseMotionListener {

	/**
	 * create an instance with default modifier values
	 */
	public RotatingGraphMousePlugin() {
	    this(MouseEvent.BUTTON1_MASK | MouseEvent.SHIFT_MASK);
	}

	/**
	 * create an instance with passed zoom in/out values
	 * @param modifiers the event modifiers to trigger rotation
	 */
	public RotatingGraphMousePlugin(int modifiers) {
	    super(modifiers);
	    Dimension cd = Toolkit.getDefaultToolkit().getBestCursorSize(16,16);
        BufferedImage cursorImage = 
        		new BufferedImage(cd.width,cd.height,BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = cursorImage.createGraphics();
        g.addRenderingHints(Collections.singletonMap(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
        g.setColor(new Color(0,0,0,0));
        g.fillRect(0,0,16,16);

        int left = 0;
        int top = 0;
        int right = 15;
        int bottom = 15;
        
        g.setColor(Color.white);
        g.setStroke(new BasicStroke(3));
        // top bent line
        g.drawLine(left+2,top+6,right/2+1,top);
        g.drawLine(right/2+1,top,right-2,top+5);
        // bottom bent line
        g.drawLine(left+2,bottom-6,right/2,bottom);
        g.drawLine(right/2,bottom,right-2,bottom-6);
        // top arrow
        g.drawLine(left+2,top+6,left+5,top+6);
        g.drawLine(left+2,top+6,left+2,top+3);
        // bottom arrow
        g.drawLine(right-2,bottom-6,right-6,bottom-6);
        g.drawLine(right-2, bottom-6,right-2,bottom-3);

        
        g.setColor(Color.black);
        g.setStroke(new BasicStroke(1));
        // top bent line
        g.drawLine(left+2,top+6,right/2+1,top);
        g.drawLine(right/2+1,top,right-2,top+5);
        // bottom bent line
        g.drawLine(left+2,bottom-6,right/2,bottom);
        g.drawLine(right/2,bottom,right-2,bottom-6);
        // top arrow
        g.drawLine(left+2,top+6,left+5,top+6);
        g.drawLine(left+2,top+6,left+2,top+3);
        // bottom arrow
        g.drawLine(right-2,bottom-6,right-6,bottom-6);
        g.drawLine(right-2, bottom-6,right-2,bottom-3);

        g.dispose();
        
        cursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(), "RotateCursor");
	}

    /**
     * save the 'down' point and check the modifiers. If the
     * modifiers are accepted, set the cursor to the 'hand' cursor
	 * @param e the event
	 */
    public void mousePressed(MouseEvent e) {
        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
           boolean accepted = checkModifiers(e);
           down = e.getPoint();
          if(accepted) {
               vv.setCursor(cursor);
           }
    }
    
	/**
     * unset the down point and change the cursor back to the default
	 */
    public void mouseReleased(MouseEvent e) {
        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
        down = null;
        vv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
    
    /**
     * check the modifiers. If accepted, use the mouse drag motion
     * to rotate the graph
	 */
    public void mouseDragged(MouseEvent e) {
        if(down == null) return;
        VisualizationViewer<?, ?> vv = (VisualizationViewer<?, ?>)e.getSource();
        boolean accepted = checkModifiers(e);
        if(accepted) {
            MutableTransformer modelTransformer =
                vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
            // rotate
            vv.setCursor(cursor);
            
            Point2D center = vv.getCenter();
            Point2D q = down;
            Point2D p = e.getPoint();
            Point2D v1 = new Point2D.Double(center.getX()-p.getX(), center.getY()-p.getY());
            Point2D v2 = new Point2D.Double(center.getX()-q.getX(), center.getY()-q.getY());
            double theta = angleBetween(v1, v2);
            modelTransformer.rotate(theta, vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.VIEW, center));
            down.x = e.getX();
            down.y = e.getY();
        
            e.consume();
        }
    }
    
    /**
     * Returns the angle between two vectors from the origin
     * to points v1 and v2.
     * @param v1 the first point
     * @param v2 the second point
     * @return the angle between two vectors from the origin through points v1 and v2
     */
    protected double angleBetween(Point2D v1, Point2D v2) {
        double x1 = v1.getX();
        double y1 = v1.getY();
        double x2 = v2.getX();
        double y2 = v2.getY();
        // cross product for direction
        double cross = x1*y2 - x2*y1;
        int cw = 1;
        if(cross > 0) {
            cw = -1;
        } 
        // dot product for angle
        double angle = 
            cw*Math.acos( ( x1*x2 + y1*y2 ) / 
                ( Math.sqrt( x1*x1 + y1*y1 ) * 
                        Math.sqrt( x2*x2 + y2*y2 ) ) );
        if(Double.isNaN(angle)) {
            angle = 0;
        }
        return angle;
    }

    public void mouseClicked(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mouseMoved(MouseEvent e) {
    }
}
