/**
 * $Id: mxConnectPreview.java,v 1.2 2010/06/01 15:05:17 gaudenz Exp $
 * Copyright (c) 2008, Gaudenz Alder
 */
package com.mxgraph.swing.handler;

import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;

import com.mxgraph.canvas.mxGraphics2DCanvas;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxICell;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;

/**
 * Connection handler creates new connections between cells. This control is used to display the connector
 * icon, while the preview is used to draw the line.
 */
public class mxConnectPreview extends mxEventSource
{
	/**
	 * 
	 */
	protected mxGraphComponent graphComponent;

	/**
	 * 
	 */
	protected mxCellState previewState;

	/**
	 * 
	 */
	protected mxCellState sourceState;

	/**
	 * 
	 */
	protected mxPoint startPoint;

	/**
	 * 
	 * @param graphComponent
	 */
	public mxConnectPreview(mxGraphComponent graphComponent)
	{
		this.graphComponent = graphComponent;

		// Installs the paint handler
		graphComponent.addListener(mxEvent.AFTER_PAINT, new mxIEventListener()
		{
			public void invoke(Object sender, mxEventObject evt)
			{
				Graphics g = (Graphics) evt.getProperty("g");
				paint(g);
			}
		});
	}

	/**
	 * Creates a new instance of mxShape for previewing the edge.
	 */
	protected Object createCell(mxCellState startState, String style)
	{
		mxGraph graph = graphComponent.getGraph();
		mxICell cell = ((mxICell) graph.createEdge(null, null, "", null, null,
				style));
		((mxICell) startState.getCell()).insertEdge(cell, true);

		return cell;
	}

	/**
	 * 
	 */
	public boolean isActive()
	{
		return sourceState != null;
	}

	/**
	 * 
	 */
	public mxCellState getSourceState()
	{
		return sourceState;
	}

	/**
	 * 
	 */
	public mxCellState getPreviewState()
	{
		return previewState;
	}

	/**
	 * 
	 */
	public mxPoint getStartPoint()
	{
		return startPoint;
	}

	/**
	 * Updates the style of the edge preview from the incoming edge
	 */
	public void start(MouseEvent e, mxCellState startState, String style)
	{
		mxGraph graph = graphComponent.getGraph();
		sourceState = startState;
		startPoint = transformScreenPoint(startState.getCenterX(), startState
				.getCenterY());
		previewState = graph.getView().getState(createCell(startState, style),
				true);
		fireEvent(new mxEventObject(mxEvent.START, "event", e, "state",
				previewState));
	}

	/**
	 * 
	 */
	public void update(MouseEvent e, mxCellState targetState, double x, double y)
	{
		mxGraph graph = graphComponent.getGraph();
		mxICell cell = (mxICell) previewState.getCell();

		mxRectangle dirty = graphComponent.getGraph().getPaintBounds(
				new Object[] { previewState.getCell() });

		if (cell.getTerminal(false) != null)
		{
			((mxICell) cell.getTerminal(false)).removeEdge(cell, false);
		}

		if (targetState != null)
		{
			((mxICell) targetState.getCell()).insertEdge(cell, false);
		}

		mxGeometry geo = graph.getCellGeometry(previewState.getCell());

		geo.setTerminalPoint(startPoint, true);
		geo.setTerminalPoint(transformScreenPoint(x, y), false);

		revalidate(graph.getView().getState(graph.getDefaultParent()),
				previewState.getCell());
		fireEvent(new mxEventObject(mxEvent.CONTINUE, "event", e, "x", x, "y",
				y));

		// Repaints the dirty region
		// TODO: Cache the new dirty region for next repaint
		graphComponent.getGraphControl().repaint(getDirtyRect(dirty));
	}

	/**
	 * 
	 */
	protected Rectangle getDirtyRect()
	{
		return getDirtyRect(null);
	}

	/**
	 * 
	 */
	protected Rectangle getDirtyRect(mxRectangle dirty)
	{
		if (previewState != null)
		{
			mxRectangle tmp = graphComponent.getGraph().getPaintBounds(
					new Object[] { previewState.getCell() });

			if (dirty != null)
			{
				dirty.add(tmp);
			}
			else
			{
				dirty = tmp;
			}

			if (dirty != null)
			{
				// TODO: Take arrow size into account
				dirty.grow(2);

				return dirty.getRectangle();
			}
		}

		return null;
	}

	/**
	 * 
	 */
	protected mxPoint transformScreenPoint(double x, double y)
	{
		mxGraph graph = graphComponent.getGraph();
		mxPoint tr = graph.getView().getTranslate();
		double scale = graph.getView().getScale();

		return new mxPoint(x / scale - tr.getX(), y / scale - tr.getY());
	}

	/**
	 * 
	 */
	public void revalidate(mxCellState pState, Object cell)
	{
		mxGraph graph = graphComponent.getGraph();
		mxCellState tmp = graph.getView().getState(cell);
		tmp.setInvalid(true);

		graph.getView().validateBounds(pState, cell);
		graph.getView().validatePoints(pState, cell);

		mxIGraphModel model = graph.getModel();
		int childCount = model.getChildCount(cell);

		for (int i = 0; i < childCount; i++)
		{
			revalidate(tmp, model.getChildAt(cell, i));
		}
	}

	/**
	 * 
	 */
	public void paint(Graphics g)
	{
		if (previewState != null)
		{
			mxGraphics2DCanvas canvas = graphComponent.getCanvas();

			if (graphComponent.isAntiAlias())
			{
				mxUtils.setAntiAlias((Graphics2D) g, true, false);
			}

			float alpha = graphComponent.getPreviewAlpha();

			if (alpha < 1)
			{
				((Graphics2D) g).setComposite(AlphaComposite.getInstance(
						AlphaComposite.SRC_OVER, alpha));
			}

			Graphics2D previousGraphics = canvas.getGraphics();
			Point previousTranslate = canvas.getTranslate();
			double previousScale = canvas.getScale();

			try
			{
				canvas.setScale(graphComponent.getGraph().getView().getScale());
				canvas.setTranslate(0, 0);
				canvas.setGraphics((Graphics2D) g);

				paintPreview(canvas);
			}
			finally
			{
				canvas.setScale(previousScale);
				canvas.setTranslate(previousTranslate.x, previousTranslate.y);
				canvas.setGraphics(previousGraphics);
			}
		}
	}

	/**
	 * Draws the preview using the graphics canvas.
	 */
	protected void paintPreview(mxGraphics2DCanvas canvas)
	{
		graphComponent.getGraphControl().drawCell(graphComponent.getCanvas(),
				previewState.getCell());
	}

	/**
	 *
	 */
	public Object stop(boolean commit)
	{
		return stop(commit, null);
	}

	/**
	 *
	 */
	public Object stop(boolean commit, MouseEvent e)
	{
		Object result = (sourceState != null) ? sourceState.getCell() : null;

		if (previewState != null)
		{
			mxGraph graph = graphComponent.getGraph();

			graph.getModel().beginUpdate();
			try
			{
				mxICell cell = (mxICell) previewState.getCell();
				Object src = cell.getTerminal(true);
				Object trg = cell.getTerminal(false);

				if (src != null)
				{
					((mxICell) src).removeEdge(cell, true);
				}

				if (trg != null)
				{
					((mxICell) trg).removeEdge(cell, false);
				}

				if (commit)
				{
					result = graph.addCell(cell, null, null, src, trg);
				}

				fireEvent(new mxEventObject(mxEvent.STOP, "event", e, "commit",
						commit));

				// Clears the state before the model commits
				if (previewState != null)
				{
					Rectangle dirty = getDirtyRect();
					graph.getView().clear(cell, false, true);
					previewState = null;

					if (!commit && dirty != null)
					{
						graphComponent.getGraphControl().repaint(dirty);
					}
				}
			}
			finally
			{
				graph.getModel().endUpdate();
			}
		}

		sourceState = null;
		startPoint = null;

		return result;
	}

}
