/**
 * $Id: mxVertexHandler.java,v 1.11 2010/05/14 14:45:45 gaudenz Exp $
 * Copyright (c) 2008, Gaudenz Alder
 */
package com.mxgraph.swing.handler;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.MouseEvent;

import javax.swing.JComponent;
import javax.swing.JPanel;

import com.mxgraph.model.mxGeometry;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;

/**
 * 
 */
public class mxVertexHandler extends mxCellHandler
{

	/**
	 * 
	 */
	public static Cursor[] CURSORS = new Cursor[] {
			new Cursor(Cursor.NW_RESIZE_CURSOR),
			new Cursor(Cursor.N_RESIZE_CURSOR),
			new Cursor(Cursor.NE_RESIZE_CURSOR),
			new Cursor(Cursor.W_RESIZE_CURSOR),
			new Cursor(Cursor.E_RESIZE_CURSOR),
			new Cursor(Cursor.SW_RESIZE_CURSOR),
			new Cursor(Cursor.S_RESIZE_CURSOR),
			new Cursor(Cursor.SE_RESIZE_CURSOR), new Cursor(Cursor.MOVE_CURSOR) };

	/**
	 * Workaround for alt-key-state not correct in mouseReleased.
	 */
	protected transient boolean gridEnabledEvent = false;

	/**
	 * Workaround for shift-key-state not correct in mouseReleased.
	 */
	protected transient boolean constrainedEvent = false;

	/**
	 * 
	 * @param graphComponent
	 * @param state
	 */
	public mxVertexHandler(mxGraphComponent graphComponent, mxCellState state)
	{
		super(graphComponent, state);
	}

	/**
	 * 
	 */
	protected Rectangle[] createHandles()
	{
		Rectangle[] h = null;

		if (graphComponent.getGraph().isCellResizable(getState().getCell()))
		{
			Rectangle bounds = getState().getRectangle();
			int half = mxConstants.HANDLE_SIZE / 2;

			int left = bounds.x - half;
			int top = bounds.y - half;

			int w2 = bounds.x + (bounds.width / 2) - half;
			int h2 = bounds.y + (bounds.height / 2) - half;

			int right = bounds.x + bounds.width - half;
			int bottom = bounds.y + bounds.height - half;

			h = new Rectangle[9];

			int s = mxConstants.HANDLE_SIZE;
			h[0] = new Rectangle(left, top, s, s);
			h[1] = new Rectangle(w2, top, s, s);
			h[2] = new Rectangle(right, top, s, s);
			h[3] = new Rectangle(left, h2, s, s);
			h[4] = new Rectangle(right, h2, s, s);
			h[5] = new Rectangle(left, bottom, s, s);
			h[6] = new Rectangle(w2, bottom, s, s);
			h[7] = new Rectangle(right, bottom, s, s);
		}
		else
		{
			h = new Rectangle[1];
		}

		int s = mxConstants.LABEL_HANDLE_SIZE;
		mxRectangle bounds = state.getLabelBounds();
		h[h.length - 1] = new Rectangle((int) (bounds.getX()
				+ bounds.getWidth() / 2 - s), (int) (bounds.getY()
				+ bounds.getHeight() / 2 - s), 2 * s, 2 * s);

		return h;
	}

	/**
	 * 
	 */
	protected JComponent createPreview()
	{
		JPanel preview = new JPanel();
		preview.setBorder(mxConstants.PREVIEW_BORDER);
		preview.setOpaque(false);
		preview.setVisible(false);

		return preview;
	}

	/**
	 * 
	 */
	public void mouseDragged(MouseEvent e)
	{
		if (!e.isConsumed() && first != null)
		{
			gridEnabledEvent = graphComponent.isGridEnabledEvent(e);
			constrainedEvent = graphComponent.isConstrainedEvent(e);

			double dx = e.getX() - first.x;
			double dy = e.getY() - first.y;

			if (isLabel(index))
			{
				mxPoint pt = new mxPoint(e.getPoint());

				if (gridEnabledEvent)
				{
					pt = graphComponent.snapScaledPoint(pt);
				}

				int idx = (int) Math.round(pt.getX() - first.x);
				int idy = (int) Math.round(pt.getY() - first.y);

				if (constrainedEvent)
				{
					if (Math.abs(idx) > Math.abs(idy))
					{
						idy = 0;
					}
					else
					{
						idx = 0;
					}
				}

				Rectangle rect = state.getLabelBounds().getRectangle();
				rect.translate(idx, idy);
				preview.setBounds(rect);
			}
			else
			{
				mxGraph graph = graphComponent.getGraph();
				double scale = graph.getView().getScale();

				if (gridEnabledEvent)
				{
					dx = graph.snap(dx / scale) * scale;
					dy = graph.snap(dy / scale) * scale;
				}

				mxRectangle bounds = union(getState(), dx, dy, index);
				bounds.setWidth(bounds.getWidth() + 1);
				bounds.setHeight(bounds.getHeight() + 1);
				preview.setBounds(bounds.getRectangle());
			}

			if (!preview.isVisible() && graphComponent.isSignificant(dx, dy))
			{
				preview.setVisible(true);
			}

			e.consume();
		}
	}

	/**
	 * 
	 */
	public void mouseReleased(MouseEvent e)
	{
		if (!e.isConsumed() && first != null)
		{
			if (preview != null && preview.isVisible())
			{
				if (isLabel(index))
				{
					moveLabel(e);
				}
				else
				{
					resizeCell(e);
				}
			}

			e.consume();
		}

		super.mouseReleased(e);
	}

	/**
	 * 
	 */
	protected void moveLabel(MouseEvent e)
	{
		mxGraph graph = graphComponent.getGraph();
		mxGeometry geometry = graph.getModel().getGeometry(state.getCell());

		if (geometry != null)
		{
			double scale = graph.getView().getScale();
			mxPoint pt = new mxPoint(e.getPoint());

			if (gridEnabledEvent)
			{
				pt = graphComponent.snapScaledPoint(pt);
			}

			double dx = (pt.getX() - first.x) / scale;
			double dy = (pt.getY() - first.y) / scale;

			if (constrainedEvent)
			{
				if (Math.abs(dx) > Math.abs(dy))
				{
					dy = 0;
				}
				else
				{
					dx = 0;
				}
			}

			mxPoint offset = geometry.getOffset();

			if (offset == null)
			{
				offset = new mxPoint();
			}

			dx += offset.getX();
			dy += offset.getY();

			geometry = (mxGeometry) geometry.clone();
			geometry.setOffset(new mxPoint(Math.round(dx), Math.round(dy)));
			graph.getModel().setGeometry(state.getCell(), geometry);
		}
	}

	/**
	 * 
	 * @param e
	 */
	protected void resizeCell(MouseEvent e)
	{
		mxGraph graph = graphComponent.getGraph();
		double scale = graph.getView().getScale();

		Object cell = state.getCell();
		mxGeometry geometry = graph.getModel().getGeometry(cell);

		if (geometry != null)
		{
			double dx = (e.getX() - first.x) / scale;
			double dy = (e.getY() - first.y) / scale;

			if (isLabel(index))
			{
				geometry = (mxGeometry) geometry.clone();

				if (geometry.getOffset() != null)
				{
					dx += geometry.getOffset().getX();
					dy += geometry.getOffset().getY();
				}

				if (gridEnabledEvent)
				{
					dx = graph.snap(dx);
					dy = graph.snap(dy);
				}

				geometry.setOffset(new mxPoint(dx, dy));
				graph.getModel().setGeometry(cell, geometry);
			}
			else
			{
				mxRectangle bounds = union(geometry, dx, dy, index);
				Rectangle rect = bounds.getRectangle();

				// Snaps new bounds to grid (unscaled)
				if (gridEnabledEvent)
				{
					int x = (int) graph.snap(rect.x);
					int y = (int) graph.snap(rect.y);
					rect.width = (int) graph.snap(rect.width - x + rect.x);
					rect.height = (int) graph.snap(rect.height - y + rect.y);
					rect.x = x;
					rect.y = y;
				}

				graph.resizeCell(cell, new mxRectangle(rect));
			}
		}
	}

	/**
	 * 
	 */
	protected Cursor getCursor(MouseEvent e, int index)
	{
		if (index >= 0 && index <= CURSORS.length)
		{
			return CURSORS[index];
		}

		return null;
	}

	/**
	 * 
	 * @param bounds
	 * @param dx
	 * @param dy
	 * @param index
	 */
	protected mxRectangle union(mxRectangle bounds, double dx, double dy,
			int index)
	{
		double left = bounds.getX();
		double right = left + bounds.getWidth();
		double top = bounds.getY();
		double bottom = top + bounds.getHeight();

		if (index > 4 /* Bottom Row */)
		{
			bottom = bottom + dy;
		}
		else if (index < 3 /* Top Row */)
		{
			top = top + dy;
		}

		if (index == 0 || index == 3 || index == 5 /* Left */)
		{
			left += dx;
		}
		else if (index == 2 || index == 4 || index == 7 /* Right */)
		{
			right += dx;
		}

		double width = right - left;
		double height = bottom - top;

		// Flips over left side
		if (width < 0)
		{
			left += width;
			width = Math.abs(width);
		}

		// Flips over top side
		if (height < 0)
		{
			top += height;
			height = Math.abs(height);
		}

		return new mxRectangle(left, top, width, height);
	}

	/**
	 * 
	 */
	protected Color getSelectionColor()
	{
		return mxConstants.VERTEX_SELECTION_COLOR;
	}

	/**
	 * 
	 */
	protected Stroke getSelectionStroke()
	{
		return mxConstants.VERTEX_SELECTION_STROKE;
	}

	/**
	 * 
	 */
	public void paint(Graphics g)
	{
		Rectangle bounds = getState().getRectangle();
		
		if (g.hitClip(bounds.x, bounds.y, bounds.width, bounds.height))
		{
			Graphics2D g2 = (Graphics2D) g;

			Stroke stroke = g2.getStroke();
			g2.setStroke(getSelectionStroke());
			g.setColor(getSelectionColor());
			g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
			g2.setStroke(stroke);
		}

		super.paint(g);
	}

}
