/******************************************************************************
 * Copyright (c) 2002, 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IBM Corporation - initial API and implementation 
 ****************************************************************************/

package org.eclipse.gmf.runtime.draw2d.ui.figures;

import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;

/**
 * A set of methods that are useful when manipulating figures.  These are often
 * used in the paint routines or locators of IFigure implementors to do complex 
 * geometry calculations that may be common across different parts of a hierarchy.
 * 
 * @author sshaw
 * @canBeSeenBy %partners
 */
public class FigureUtilities extends org.eclipse.draw2d.FigureUtilities {

	/**
	 * Method colorToInteger.
	 * converts from a Color to an Integer representation
	 * @param c
	 * @return Integer
	 */
	public static Integer colorToInteger(Color c) {
		return new Integer(
			(c.getBlue() << 16) | (c.getGreen() << 8) | c.getRed());
	}

	/**
	 * Method integerToColor.
	 * converts from an Integer to a Color representation
	 * 
	 * Note: Normally, colors should be instantiated 
	 * using the AbstractResourceManager.
	 * 
	 * @param i
	 * @return Color
	 */
	public static Color integerToColor(Integer i) {
		if (i == null)
			return null;
		int color = i.intValue();
		return new Color(
			null,
			(color & 0x000000FF),
			(color & 0x0000FF00) >> 8,
			(color & 0x00FF0000) >> 16);
	}

	/**
	 * Method RGBToInteger
	 * converts from an RGB to an Integer representation
	 * @param rgb
	 * @return Integer
	 */
	public static Integer RGBToInteger(RGB rgb) {
		return new Integer((rgb.blue << 16) | (rgb.green << 8) | rgb.red);
	}


	/**
	 * Method integerToRGB
	 * converts from an Integer to an RGB representation
	 * @param color
	 * @return RGB
	 */
	public static RGB integerToRGB(Integer color) {
		int n = color.intValue();
		return new RGB(			
				(n & 0x000000FF),
				(n & 0x0000FF00) >> 8,
				(n & 0x00FF0000) >> 16);
	}

	/** 
	 * Return the location within the supplied constraint the supplied rectangle requires to be
	 * positioned according to the alignment parameter <i>pos</i>.  
	 * 
	 * <pre>
	 * NW-----N------NE
	 * |              |
	 * W    NS/EW     E
	 * |              |
	 * SW-----S------SE
	 * 
	 * </pre>
	 * <P>
	 * @param pos a geographic PositionConstant (N,E,W,S,NW,NW,NS,EW)
	 * @param topos the rectangle to be (re)positioned.
	 * @param constraint the containing bounds.
	 * @return a new location.
	 */
	public static Point getLocation(
		int pos,
		final Rectangle topos,
		final Rectangle constraint) {
		Rectangle b = constraint.getCopy();
		Point svb = new Point(b.x, b.y);

		switch (pos) {
			case PositionConstants.NORTH :
				svb.x += b.width / 2;
				break;

			case PositionConstants.SOUTH :
				svb.x += b.width / 2;
				svb.y += b.height;
				break;

			case PositionConstants.WEST :
				svb.y += b.height / 2 - topos.height / 2;
				break;

			case PositionConstants.EAST :
				svb.x += b.width;
				svb.y += b.height / 2 - topos.height / 2;
				break;

			case PositionConstants.NORTH_EAST : //top right
				svb.x += b.width - topos.width;
				break;

			case PositionConstants.SOUTH_EAST : //bottom right
				svb.x += b.width - topos.width;
				svb.y += b.height - topos.height;
				break;

			case PositionConstants.SOUTH_WEST : //bottom left
				svb.x += b.width;
				svb.y += topos.height;
				break;

			case PositionConstants.NORTH_SOUTH : // center of bounds area
			case PositionConstants.EAST_WEST :
				svb.x += b.width / 2 - topos.width / 2;
				svb.y += b.height / 2 - topos.height / 2;
				break;

			case PositionConstants.NORTH_WEST : //top left
			default :
				break;
		}
		return svb;
	}

	/**
	 * Return the location within the supplied constraint of an object of dimension  <i>dim</i> 
	 * according to the alignment parameter. 
	 * @see #getLocation(int, Rectangle, Rectangle)
	 * @param pos a geographic PositionConstant (N,E,W,S,NW,NW,NS,EW)
	 * @param dim some dimension.
	 * @param constraint the containing bounds.
	 * @return a new location.
	 */
	public static Point getLocation(
		int pos,
		final Dimension dim,
		final Rectangle constraint) {
		return getLocation(
			pos,
			new Rectangle(0, 0, dim.width, dim.height),
			constraint);
	}

	
	/**
	 * Helper method to paint a grid.  Painting is optimized as it is restricted to the
	 * Graphics' clip.
	 * 
	 * @param	g			The Graphics object to be used for painting
	 * @param	f			The figure in which the grid is to be painted
	 * @param	origin		Any point where the grid lines are expected to intersect
	 * @param	distanceX	Distance between vertical grid lines; if 0 or less, vertical grid
	 * 						lines will not be drawn
	 * @param	distanceY	Distance between horizontal grid lines; if 0 or less, horizontal
	 * 						grid lines will not be drawn
	 * @param	lineStyle   Line style to be used for painting the grid lines
	 * @param   dashes      Dash pattern to be used for the grid line (ignored if lineStyle != LINE_CUSTOM)
	 * 
	 */
	public static void paintGridWithStyle(Graphics g, IFigure f, 
			org.eclipse.draw2d.geometry.Point origin, int distanceX, int distanceY, int lineStyle, int[] dashes) {
		Rectangle clip = g.getClip(Rectangle.SINGLETON);
		
		int origLineStyle = g.getLineStyle();		
		
		if (distanceX > 0) {
			if (origin.x >= clip.x)
				while (origin.x - distanceX >= clip.x)
					origin.x -= distanceX;
			else
				while (origin.x < clip.x)
					origin.x += distanceX;
			for (int i = origin.x; i < clip.x + clip.width; i += distanceX) {
				g.setLineStyle(lineStyle);
				if ((dashes != null) && (lineStyle == SWT.LINE_CUSTOM)) g.setLineDash(dashes);
				g.drawLine(i, clip.y, i, clip.y + clip.height);
				g.setLineStyle(origLineStyle);
			}
		}
		
		if (distanceY > 0) {
			if (origin.y >= clip.y)
				while (origin.y - distanceY >= clip.y)
					origin.y -= distanceY;
			else
				while (origin.y < clip.y)
					origin.y += distanceY;
			for (int i = origin.y; i < clip.y + clip.height; i += distanceY) {
				g.setLineStyle(lineStyle);
				if ((dashes != null) && (lineStyle == SWT.LINE_CUSTOM)) g.setLineDash(dashes);
				g.drawLine(clip.x, i, clip.x + clip.width, i);
				g.setLineStyle(origLineStyle);
			}
		}
	}

	/**
	 * Calculates the anchorable figure bounds. There could be cases that a figure
	 * implements both <code>IOvalAnchorableFigure</code> and <code>IPolygonAnchorableFigure</code>
	 * interfaces. The latter interface is more popular because any figure can be approximated
	 * with line segments including ellipse. Therefore, we need to check first if it's an
	 * ellipse like figure and then if it's some kind of a polygon.
	 * 
	 * @param figure the figure
	 * @return the bounding <code>Rectangle</code> of the anchorable part of the figure
	 */
	public static Rectangle getAnchorableFigureBounds(IFigure figure) {
		if (figure instanceof IOvalAnchorableFigure) {
			return ((IOvalAnchorableFigure)figure).getOvalBounds().getCopy();
		} else if (figure instanceof IPolygonAnchorableFigure) {
			return ((IPolygonAnchorableFigure)figure).getPolygonPoints().getBounds();
		} else if (figure instanceof IFigure) {
			return figure.getBounds().getCopy();
		}
		return null;
	}
	
}
