/**
 * $Id: mxGraphTransferHandler.java,v 1.17 2010/07/19 11:25:20 gaudenz Exp $
 * Copyright (c) 2008, Gaudenz Alder
 */
package com.mxgraph.swing.handler;

import java.awt.Color;
import java.awt.Image;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.TransferHandler;

import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxGraphTransferable;
import com.mxgraph.util.mxCellRenderer;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxGraph;

/**
 * 
 */
public class mxGraphTransferHandler extends TransferHandler
{

	/**
	 * 
	 */
	private static final long serialVersionUID = -6443287704811197675L;

	/**
	 * Boolean that specifies if an image of the cells should be created for
	 * each transferable. Default is true.
	 */
	public static boolean DEFAULT_TRANSFER_IMAGE_ENABLED = true;

	/**
	 * Specifies the background color of the transfer image. If no
	 * color is given here then the background color of the enclosing
	 * graph component is used. Default is Color.WHITE.
	 */
	public static Color DEFAULT_BACKGROUNDCOLOR = Color.WHITE;

	/**
	 * Reference to the original cells for removal after a move.
	 */
	protected Object[] originalCells;

	/**
	 * Reference to the last imported cell array.
	 */
	protected Object[] lastImported;

	/**
	 * Counter for the last imported cell array.
	 */
	protected int importCount = 0;

	/**
	 * Specifies if a transfer image should be created for the transferable.
	 * Default is DEFAULT_TRANSFER_IMAGE.
	 */
	protected boolean transferImageEnabled = DEFAULT_TRANSFER_IMAGE_ENABLED;

	/**
	 * Specifies the background color for the transfer image. Default is
	 * DEFAULT_BACKGROUNDCOLOR.
	 */
	protected Color transferImageBackground = DEFAULT_BACKGROUNDCOLOR;

	/**
	 * 
	 */
	protected Point location;

	/**
	 * 
	 */
	protected Point offset;

	/**
	 * 
	 */
	public void setTransferImageEnabled(boolean transferImageEnabled)
	{
		this.transferImageEnabled = transferImageEnabled;
	}

	/**
	 * 
	 */
	public boolean isTransferImageEnabled()
	{
		return this.transferImageEnabled;
	}

	/**
	 * 
	 */
	public void setTransferImageBackground(Color transferImageBackground)
	{
		this.transferImageBackground = transferImageBackground;
	}

	/**
	 * 
	 */
	public Color getTransferImageBackground()
	{
		return this.transferImageBackground;
	}

	/**
	 * Returns true if the DnD operation started from this handler.
	 */
	public boolean isLocalDrag()
	{
		return originalCells != null;
	}

	/**
	 * 
	 */
	public void setLocation(Point value)
	{
		location = value;
	}

	/**
	 * 
	 */
	public void setOffset(Point value)
	{
		offset = value;
	}

	/**
	 * 
	 */
	public boolean canImport(JComponent comp, DataFlavor[] flavors)
	{
		for (int i = 0; i < flavors.length; i++)
		{
			if (flavors[i] != null
					&& flavors[i].equals(mxGraphTransferable.dataFlavor))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.TransferHandler#createTransferable(javax.swing.JComponent)
	 */
	public Transferable createTransferable(JComponent c)
	{
		if (c instanceof mxGraphComponent)
		{
			mxGraphComponent graphComponent = (mxGraphComponent) c;
			mxGraph graph = graphComponent.getGraph();

			if (!graph.isSelectionEmpty())
			{
				originalCells = graphComponent.getExportableCells(graph
						.getSelectionCells());

				if (originalCells.length > 0)
				{
					ImageIcon icon = (transferImageEnabled) ? createTransferableImage(
							graphComponent, originalCells)
							: null;

					return createGraphTransferable(graphComponent,
							originalCells, icon);
				}
			}
		}

		return null;
	}

	/**
	 * 
	 */
	public mxGraphTransferable createGraphTransferable(
			mxGraphComponent graphComponent, Object[] cells, ImageIcon icon)
	{
		mxGraph graph = graphComponent.getGraph();
		mxPoint tr = graph.getView().getTranslate();
		double scale = graph.getView().getScale();

		mxRectangle bounds = graph.getPaintBounds(cells);

		// Removes the scale and translation from the bounds
		bounds.setX(bounds.getX() / scale - tr.getX());
		bounds.setY(bounds.getY() / scale - tr.getY());
		bounds.setWidth(bounds.getWidth() / scale);
		bounds.setHeight(bounds.getHeight() / scale);

		return createGraphTransferable(graphComponent, graph.cloneCells(cells),
				bounds, icon);
	}

	/**
	 * 
	 */
	public mxGraphTransferable createGraphTransferable(
			mxGraphComponent graphComponent, Object[] cells,
			mxRectangle bounds, ImageIcon icon)
	{
		return new mxGraphTransferable(cells, bounds, icon);
	}

	/**
	 * 
	 */
	public ImageIcon createTransferableImage(mxGraphComponent graphComponent,
			Object[] cells)
	{
		ImageIcon icon = null;
		Color bg = (transferImageBackground != null) ? transferImageBackground
				: graphComponent.getBackground();
		Image img = mxCellRenderer.createBufferedImage(graphComponent
				.getGraph(), cells, 1, bg, graphComponent.isAntiAlias(), null,
				graphComponent.getCanvas());

		if (img != null)
		{
			icon = new ImageIcon(img);
		}

		return icon;
	}

	/**
	 * 
	 */
	public void exportDone(JComponent c, Transferable data, int action)
	{
		if (c instanceof mxGraphComponent
				&& data instanceof mxGraphTransferable)
		{
			// Requires that the graph handler resets the location to null if the drag leaves the
			// component. This is the condition to identify a cross-component move.
			boolean isLocalDrop = location != null;

			if (action == TransferHandler.MOVE && !isLocalDrop)
			{
				removeCells((mxGraphComponent) c, originalCells);
			}
		}

		originalCells = null;
		location = null;
		offset = null;
	}

	/**
	 * 
	 */
	protected void removeCells(mxGraphComponent graphComponent, Object[] cells)
	{
		graphComponent.getGraph().removeCells(cells);
	}

	/**
	 * 
	 */
	public int getSourceActions(JComponent c)
	{
		return COPY_OR_MOVE;
	}

	/**
	 * 
	 */
	public boolean importData(JComponent c, Transferable t)
	{
		boolean result = false;

		if (isLocalDrag())
		{
			// Enables visual feedback on the Mac
			result = true;
		}
		else
		{
			try
			{
				if (c instanceof mxGraphComponent)
				{
					mxGraphComponent graphComponent = (mxGraphComponent) c;

					if (graphComponent.isEnabled()
							&& t
									.isDataFlavorSupported(mxGraphTransferable.dataFlavor))
					{
						mxGraphTransferable gt = (mxGraphTransferable) t
								.getTransferData(mxGraphTransferable.dataFlavor);

						if (gt.getCells() != null)
						{
							result = importCells(graphComponent, gt.getCells(),
									gt.getBounds());
						}

					}
				}
			}
			catch (Exception ex)
			{
				ex.printStackTrace();
			}
		}

		return result;
	}

	/**
	 * Returns true if the cells have been imported.
	 */
	protected boolean importCells(mxGraphComponent graphComponent,
			Object[] cells, mxRectangle bounds)
	{
		boolean result = false;

		try
		{
			mxGraph graph = graphComponent.getGraph();
			double scale = graph.getView().getScale();
			double dx = 0, dy = 0;

			Object target = null;

			// Finds the target cell at the given location and checks if the
			// target is not already the parent of the first imported cell
			if (location != null)
			{
				target = graph.getDropTarget(cells, location, graphComponent
						.getCellAt(location.x, location.y));

				if (cells.length > 0
						&& graph.getModel().getParent(cells[0]) == target)
				{
					target = null;
				}
			}

			// Computes the offset for the placement of the imported cells
			if (location != null && bounds != null)
			{
				mxPoint translate = graph.getView().getTranslate();

				dx = location.getX() - (bounds.getX() + translate.getX())
						* scale;
				dy = location.getY() - (bounds.getY() + translate.getY())
						* scale;

				// Keeps the cells aligned to the grid
				dx = graph.snap(dx / scale);
				dy = graph.snap(dy / scale);
			}
			else
			{
				// Counts the number of times the cells have been imported
				// via paste and increments the offset accordingly
				// FIXME: Cell array loses object identify since the
				// transferable is deserialized on each insert. We need
				// a different way of identifying repeated inserts of the
				// same cell array.
				if (lastImported != cells)
				{
					importCount = 1;
				}
				else
				{
					importCount++;
				}

				int gs = graph.getGridSize();

				dx = importCount * gs;
				dy = importCount * gs;
			}

			if (offset != null)
			{
				dx += offset.x;
				dy += offset.y;
			}

			lastImported = cells;
			cells = graphComponent.getImportableCells(cells);

			if (graph.isSplitEnabled() && graph.isSplitTarget(target, cells))
			{
				graph.splitEdge(target, cells, dx, dy);
			}
			else
			{
				cells = importCells(graphComponent, cells, dx, dy, target,
						location);
				graph.setSelectionCells(cells);
			}

			location = null;
			offset = null;
			result = true;

			// Requests the focus after an import
			graphComponent.requestFocus();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		return result;
	}

	/**
	 * 
	 */
	protected Object[] importCells(mxGraphComponent graphComponent,
			Object[] cells, double dx, double dy, Object target, Point location)
	{
		return graphComponent.importCells(cells, dx, dy, target, location);
	}

}
