//------------------------------------------------------------------------------
// Copyright (c) 2005, 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 implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.authoring.gef.figures;

import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureUtilities;
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.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.Image;

import com.ibm.icu.text.BreakIterator;
import com.ibm.icu.util.StringTokenizer;

/**
 * Provides support for text wrap in a label.
 * 
 * @author Shashidhar Kannoori
 * @since 1.0
 */
public class WrappableLabel extends Figure implements PositionConstants {

	private static String ELLIPSIS = "..."; //$NON-NLS-1$

	private Image icon;

	private String text = ""; //$NON-NLS-1$

	private String subStringText;

	private Dimension textSize;

	private Dimension subStringTextSize;

	private Dimension iconSize = new Dimension(0, 0);

	private Point iconLocation;

	private Point textLocation;

	private int textAlignment = CENTER;

	private int iconAlignment = CENTER;

	private int labelAlignment = CENTER;

	private int textPlacement = SOUTH;

	private int iconTextGap = 3;

	private static final int FLAG_SELECTED = MAX_FLAG << 1,
			FLAG_HASFOCUS = MAX_FLAG << 2, FLAG_WRAP = MAX_FLAG << 5,
			FLAG_WRAP_ALIGN = MAX_FLAG << 6;

	// The primary dimensions used to calculate text size.
	private Dimension textDimension = new Dimension(-1, -1);

	private Dimension prefSizeDimension = new Dimension(-1, -1);

	private int wrapWidth;

	public WrappableLabel() {
		super();
	}

	/**
	 * Construct a Label with passed String as its text.
	 * 
	 * @param s
	 *            the label text
	 * @since 2.0
	 */
	public WrappableLabel(String s) {
		setText(s);
	}

	/**
	 * Construct a WrappedLabel with passed Image as its icon.
	 * 
	 * @param i
	 *            the WrappedLabel image
	 * @since 2.0
	 */
	public WrappableLabel(Image i) {
		setIcon(i);
	}

	/**
	 * Construct a WrappedLabel with passed String as text and passed Image as
	 * its icon.
	 * 
	 * @param s
	 *            the WrappedLabel text
	 * @param i
	 *            the WrappedLabel image
	 * @since 2.0
	 */
	public WrappableLabel(String s, Image i) {
		setText(s);
		setIcon(i);
	}

	private void alignOnHeight(Point loc, Dimension size, int alignment) {
		Insets insets = getInsets();
		switch (alignment) {
		case TOP:
			loc.y = insets.top;
			break;
		case BOTTOM:
			loc.y = bounds.height - size.height - insets.bottom;
			break;
		default:
			loc.y = (bounds.height - size.height) / 2;
		}
	}

	private void alignOnWidth(Point loc, Dimension size, int alignment) {
		Insets insets = getInsets();
		switch (alignment) {
		case LEFT:
			loc.x = insets.left;
			break;
		case RIGHT:
			loc.x = bounds.width - size.width - insets.right;
			break;
		default:
			loc.x = (bounds.width - size.width) / 2;
		}
	}

	private void calculateAlignment() {
		switch (textPlacement) {
		case EAST:
		case WEST:
			alignOnHeight(textLocation, getTextSize(), textAlignment);
			alignOnHeight(iconLocation, iconSize, iconAlignment);
			break;
		case NORTH:
		case SOUTH:
			alignOnWidth(textLocation, getSubStringTextSize(), textAlignment);
			alignOnWidth(iconLocation, iconSize, iconAlignment);
			break;
		}
	}

	/**
	 * Calculates the size of the WrappedLabel using the passed Dimension as the
	 * size of the WrappedLabel's text.
	 * 
	 * @param txtSize
	 *            the precalculated size of the WrappedLabel's text
	 * @return the WrappedLabel's size
	 * @since 2.0
	 */
	protected Dimension calculateLabelSize(Dimension txtSize) {
		int gap = iconTextGap;
		if (getIcon() == null || getText().equals("")) //$NON-NLS-1$
			gap = 0;
		Dimension d = new Dimension(0, 0);
		if (textPlacement == WEST || textPlacement == EAST) {
			d.width = iconSize.width + gap + txtSize.width;
			d.height = Math.max(iconSize.height, txtSize.height);
		} else {
			d.width = Math.max(iconSize.width, txtSize.width);
			d.height = iconSize.height + gap + txtSize.height;
		}
		return d;
	}

	private void calculateLocations() {
		textLocation = new Point();
		iconLocation = new Point();

		calculatePlacement();
		calculateAlignment();
		Dimension offset = getSize().getDifference(getPreferredSize());
		offset.width += getTextSize().width - getSubStringTextSize().width;
		switch (labelAlignment) {
		case CENTER:
			offset.scale(0.5f);
			break;
		case LEFT:
			offset.scale(0.0f);
			break;
		case RIGHT:
			offset.scale(1.0f);
			break;
		case TOP:
			offset.height = 0;
			offset.scale(0.5f);
			break;
		case BOTTOM:
			offset.height = offset.height * 2;
			offset.scale(0.5f);
			break;
		default:
			offset.scale(0.5f);
			break;
		}

		switch (textPlacement) {
		case EAST:
		case WEST:
			offset.height = 0;
			break;
		case NORTH:
		case SOUTH:
			offset.width = 0;
			break;
		}

		textLocation.translate(offset);
		iconLocation.translate(offset);
	}

	private void calculatePlacement() {
		int gap = iconTextGap;
		if (icon == null || text.equals("")) //$NON-NLS-1$
			gap = 0;
		Insets insets = getInsets();

		switch (textPlacement) {
		case EAST:
			iconLocation.x = insets.left;
			textLocation.x = iconSize.width + gap + insets.left;
			break;
		case WEST:
			textLocation.x = insets.left;
			iconLocation.x = getSubStringTextSize().width + gap + insets.left;
			break;
		case NORTH:
			textLocation.y = insets.top;
			iconLocation.y = getTextSize().height + gap + insets.top;
			break;
		case SOUTH:
			textLocation.y = iconSize.height + gap + insets.top;
			iconLocation.y = insets.top;
		}
	}

	/**
	 * Calculates the size of the Label's text size. The text size calculated
	 * takes into consideration if the Label's text is currently truncated. If
	 * text size without considering current truncation is desired, use
	 * {@link #calculateTextSize()}.
	 * 
	 * @return the size of the label's text, taking into account truncation
	 * @since 2.0
	 */
	protected Dimension calculateSubStringTextSize() {
		return FigureUtilities.getTextExtents(getSubStringText(), getFont());
	}

	/**
	 * Calculates and returns the size of the Label's text. Note that this
	 * Dimension is calculated using the Label's full text, regardless of
	 * whether or not its text is currently truncated. If text size considering
	 * current truncation is desired, use {@link #calculateSubStringTextSize()}.
	 * 
	 * @return the size of the label's text, ignoring truncation
	 * @since 2.0
	 */
	protected Dimension calculateTextSize() {
		// return FigureUtilities.getTextExtents(getText(), getFont());
		return FigureUtilities.getTextExtents(getWrappedText(getSize().width,
				getSize().height), getFont());

	}

	private void clearLocations() {
		iconLocation = textLocation = null;
	}

	/**
	 * Returns the Label's icon.
	 * 
	 * @return the label icon
	 * @since 2.0
	 */
	public Image getIcon() {
		return icon;
	}

	/**
	 * Returns the current alignment of the Label's icon. The default is
	 * {@link PositionConstants#CENTER}.
	 * 
	 * @return the icon alignment
	 * @since 2.0
	 */
	public int getIconAlignment() {
		return iconAlignment;
	}

	/**
	 * Returns the bounds of the Label's icon.
	 * 
	 * @return the icon's bounds
	 * @since 2.0
	 */
	public Rectangle getIconBounds() {
		Rectangle bounds = getBounds();
		return new Rectangle(bounds.getLocation().translate(getIconLocation()),
				iconSize);
	}

	/**
	 * Returns the location of the Label's icon relative to the Label.
	 * 
	 * @return the icon's location
	 * @since 2.0
	 */
	protected Point getIconLocation() {
		if (iconLocation == null)
			calculateLocations();
		return iconLocation;
	}

	/**
	 * Returns the gap in pixels between the Label's icon and its text.
	 * 
	 * @return the gap
	 * @since 2.0
	 */
	public int getIconTextGap() {
		return iconTextGap;
	}

	/**
	 * @see IFigure#getMinimumSize(int, int)
	 */
	public Dimension getMinimumSize(int w, int h) {
		if (minSize != null)
			return minSize;
		minSize = new Dimension();
		if (getLayoutManager() != null)
			minSize.setSize(getLayoutManager().getMinimumSize(this, w, h));

		Dimension labelSize = calculateLabelSize(FigureUtilities
				.getTextExtents(ELLIPSIS, getFont()).intersect(
						FigureUtilities.getTextExtents(getText(), getFont())));
		Insets insets = getInsets();
		labelSize.expand(insets.getWidth(), insets.getHeight());
		minSize.union(labelSize);
		return minSize;
	}

	/**
	 * @see IFigure#getPreferredSize(int, int)
	 */
	public Dimension getPreferredSize(int wHint, int hHint) {
		if (prefSize == null || wHint != prefSizeDimension.width
				|| hHint != prefSizeDimension.height) {
			prefSize = calculateLabelSize(getTextSize(wHint, hHint));
			Insets insets = getInsets();
			prefSize.expand(insets.getWidth(), insets.getHeight());
			if (getLayoutManager() != null)
				prefSize.union(getLayoutManager().getPreferredSize(this, wHint,
						hHint));
		}
		if (wHint >= 0 && wHint < prefSize.width) {
			Dimension minSize = getMinimumSize(wHint, hHint);
			Dimension result = prefSize.getCopy();
			result.width = Math.min(result.width, wHint);
			result.width = Math.max(minSize.width, result.width);
			prefSizeDimension.width = result.width;
			prefSizeDimension.height = result.width;
			return result;
		}
		return prefSize;
	}

	/**
	 * Calculates the amount of the Label's current text will fit in the Label,
	 * including an elipsis "..." if truncation is required.
	 * 
	 * @return the substring
	 * @since 2.0
	 */
	public String getSubStringText() {
		if (subStringText != null)
			return subStringText;

		subStringText = text;
		Dimension shrink = getPreferredSize(getSize().width, getSize().height)
				.getDifference(getSize());
		Dimension effectiveSize = getTextSize().getExpanded(-shrink.width,
				-shrink.height);
		Font currentFont = getFont();
		int fontHeight = FigureUtilities.getFontMetrics(currentFont)
				.getHeight();
		int maxLines = (int) (effectiveSize.height / (double) fontHeight);

		StringBuffer accumlatedText = new StringBuffer();
		StringBuffer remainingText = new StringBuffer(getText());
		int i = 0, j = 0;

		while (remainingText.length() > 0 && j++ < maxLines) {
			i = getLineWrapPosition(remainingText.toString(), currentFont,
					effectiveSize.width);

			if (accumlatedText.length() > 0)
				accumlatedText.append("\n"); //$NON-NLS-1$

			if (i == 0 || (remainingText.length() > i && j == maxLines)) {
				int dotsWidth = FigureUtilities.getTextExtents(ELLIPSIS,
						currentFont).width;
				i = getLargestSubstringConfinedTo(remainingText.toString(),
						currentFont, Math.max(effectiveSize.width - dotsWidth,
								0));
				accumlatedText.append(remainingText.substring(0, i));
				accumlatedText.append(ELLIPSIS);
			} else
				accumlatedText.append(remainingText.substring(0, i));
			remainingText.delete(0, i);
		}
		return subStringText = accumlatedText.toString();
	}

	/**
	 * Returns the size of the Label's current text. If the text is currently
	 * truncated, the truncated text with its ellipsis is used to calculate the
	 * size.
	 * 
	 * @return the size of this label's text, taking into account truncation
	 * @since 2.0
	 */
	protected Dimension getSubStringTextSize() {
		if (subStringTextSize == null)
			subStringTextSize = calculateSubStringTextSize();
		return subStringTextSize;
	}

	/**
	 * Returns the text of the label. Note that this is the complete text of the
	 * label, regardless of whether it is currently being truncated. Call
	 * {@link #getSubStringText()} to return the label's current text contents
	 * with truncation considered.
	 * 
	 * @return the complete text of this label
	 * @since 2.0
	 */
	public String getText() {
		return text;
	}

	/**
	 * Returns the current alignment of the Label's text. The default text
	 * alignment is {@link PositionConstants#CENTER}.
	 * 
	 * @return the text alignment
	 */
	public int getTextAlignment() {
		return textAlignment;
	}

	/**
	 * Returns the bounds of the label's text. Note that the bounds are
	 * calculated using the label's complete text regardless of whether the
	 * label's text is currently truncated.
	 * 
	 * @return the bounds of this label's complete text
	 * @since 2.0
	 */
	public Rectangle getTextBounds() {
		Rectangle bounds = getBounds();
		return new Rectangle(bounds.getLocation().translate(getTextLocation()),
				textSize);
	}

	/**
	 * Returns the location of the label's text relative to the label.
	 * 
	 * @return the text location
	 * @since 2.0
	 */
	protected Point getTextLocation() {
		if (textLocation != null)
			return textLocation;
		calculateLocations();
		return textLocation;
	}

	/**
	 * Returns the current placement of the label's text relative to its icon.
	 * The default text placement is {@link PositionConstants#EAST}.
	 * 
	 * @return the text placement
	 * @since 2.0
	 */
	public int getTextPlacement() {
		return textPlacement;
	}

	/**
	 * Returns the size of the label's complete text. Note that the text used to
	 * make this calculation is the label's full text, regardless of whether the
	 * label's text is currently being truncated and is displaying an ellipsis.
	 * If the size considering current truncation is desired, call
	 * {@link #getSubStringTextSize()}.
	 * 
	 * @return the size of this label's complete text
	 * @since 2.0
	 */
	protected Dimension getTextSize() {
		if (textSize == null)
			textSize = calculateTextSize();
		return textSize;
	}

	/**
	 * Returns the size of the label's complete text. Note that the text used to
	 * make this calculation is the label's full text, regardless of whether the
	 * label's text is currently being truncated and is displaying an ellipsis.
	 * If the size considering current truncation is desired, call
	 * {@link #getSubStringTextSize()}.
	 * 
	 * @return the size of this label's complete text
	 * @since 2.0
	 */
	protected Dimension getTextSize(int width, int height) {
		if (textSize == null) {
			textSize = calculateTextSize(width, height);
			textDimension.width = width;
			textDimension.height = height;
		}
		return textSize;
	}

	/**
	 * @see IFigure#invalidate()
	 */
	public void invalidate() {
		prefSize = null;
		minSize = null;
		clearLocations();
		textSize = null;
		subStringTextSize = null;
		subStringText = null;
		super.invalidate();
	}

	/**
	 * Returns <code>true</code> if the label's text is currently truncated
	 * and is displaying an ellipsis, <code>false</code> otherwise.
	 * 
	 * @return <code>true</code> if the label's text is truncated
	 * @since 2.0
	 */
	public boolean isTextTruncated() {
		return !getSubStringTextSize().equals(getTextSize());
	}

	/**
	 * @see Figure#paintFigure(Graphics)
	 */
	protected void paintFigure(Graphics graphics) {
		if (isSelected()) {
			graphics.pushState();
			graphics.setBackgroundColor(ColorConstants.menuBackgroundSelected);
			graphics.fillRectangle(getSelectionRectangle());
			graphics.popState();
			graphics.setForegroundColor(ColorConstants.white);
		}
		if (hasFocus()) {
			graphics.pushState();
			graphics.setXORMode(true);
			graphics.setForegroundColor(ColorConstants.menuBackgroundSelected);
			graphics.setBackgroundColor(ColorConstants.white);
			graphics.drawFocus(getSelectionRectangle().resize(-1, -1));
			graphics.popState();
		}
		if (isOpaque())
			super.paintFigure(graphics);
		Rectangle bounds = getBounds();
		graphics.translate(bounds.x, bounds.y);
		if (icon != null)
			graphics.drawImage(icon, getIconLocation());

		if (!isEnabled()) {
			graphics.translate(1, 1);
			graphics.setForegroundColor(ColorConstants.buttonLightest);
			graphicsdrawText(graphics);
			graphics.drawText(getSubStringText(), getTextLocation());
			graphics.translate(-1, -1);
			graphics.setForegroundColor(ColorConstants.buttonDarker);
		}
		graphicsdrawText(graphics);
		graphics.translate(-bounds.x, -bounds.y);
	}

	/**
	 * Sets the label's icon to the passed image.
	 * 
	 * @param image
	 *            the new label image
	 * @since 2.0
	 */
	public void setIcon(Image image) {
		if (icon == image)
			return;
		icon = image;
		// Call repaint, in case the image dimensions are the same.
		repaint();
		if (icon == null)
			setIconDimension(new Dimension());
		else
			setIconDimension(new Dimension(image));
	}

	/**
	 * This method sets the alignment of the icon within the icon dimension. The
	 * dimension should be the same size as the Image itself, so calling this
	 * method should have no affect.
	 * 
	 * @param align
	 *            the icon alignment
	 * @since 2.0
	 */
	public void setIconAlignment(int align) {
		if (iconAlignment == align)
			return;
		iconAlignment = align;
		clearLocations();
		repaint();
	}

	/**
	 * Sets the label's icon size to the passed Dimension.
	 * 
	 * @param d
	 *            the new icon size
	 * @since 2.0
	 */
	public void setIconDimension(Dimension d) {
		if (d.equals(iconSize))
			return;
		iconSize = d;
		revalidate();
	}

	/**
	 * Sets the gap in pixels between the label's icon and text to the passed
	 * value. The default is 4.
	 * 
	 * @param gap
	 *            the gap
	 * @since 2.0
	 */
	public void setIconTextGap(int gap) {
		if (iconTextGap == gap)
			return;
		iconTextGap = gap;
		repaint();
		revalidate();
	}

	/**
	 * Sets the alignment of the entire label (icon and text). If this figure's
	 * bounds are larger than the size needed to display the label, the label
	 * will be aligned accordingly. Valid values are:
	 * <UL>
	 * <LI><EM>{@link PositionConstants#CENTER}</EM>
	 * <LI>{@link PositionConstants#TOP}
	 * <LI>{@link PositionConstants#BOTTOM}
	 * <LI>{@link PositionConstants#LEFT}
	 * <LI>{@link PositionConstants#RIGHT}
	 * </UL>
	 * 
	 * @param align
	 *            label alignment
	 */
	public void setLabelAlignment(int align) {
		if (labelAlignment == align)
			return;
		labelAlignment = align;
		clearLocations();
		repaint();
	}

	/**
	 * Sets the label's text.
	 * 
	 * @param s
	 *            the new label text
	 * @since 2.0
	 */
	public void setText(String s) {
		// "text" will never be null.
		if (s == null)
			s = ""; //$NON-NLS-1$
		if (text.equals(s))
			return;
		text = s;
		revalidate();
		repaint();
	}

	/**
	 * Sets the alignment of the Text relative to the icon. The text alignment
	 * must be orthogonal to the text placement. For example, if the placement
	 * is EAST, then the text can be aligned using TOP, CENTER, or BOTTOM. Valid
	 * values are:
	 * <UL>
	 * <LI><EM>{@link PositionConstants#CENTER}</EM>
	 * <LI>{@link PositionConstants#TOP}
	 * <LI>{@link PositionConstants#BOTTOM}
	 * <LI>{@link PositionConstants#LEFT}
	 * <LI>{@link PositionConstants#RIGHT}
	 * </UL>
	 * 
	 * @see #setLabelAlignment(int)
	 * @param align
	 *            the text alignment
	 * @since 2.0
	 */
	public void setTextAlignment(int align) {
		if (textAlignment == align)
			return;
		textAlignment = align;
		clearLocations();
		repaint();
	}

	/**
	 * Sets the placement of text relative to the label's icon. Valid values
	 * are:
	 * <UL>
	 * <LI><EM>{@link PositionConstants#EAST}</EM>
	 * <LI>{@link PositionConstants#NORTH}
	 * <LI>{@link PositionConstants#SOUTH}
	 * <LI>{@link PositionConstants#WEST}
	 * </UL>
	 * 
	 * @param where
	 *            the text placement
	 * @since 2.0
	 */
	public void setTextPlacement(int where) {
		if (textPlacement == where)
			return;
		textPlacement = where;
		revalidate();
		repaint();
	}

	private Rectangle getSelectionRectangle() {
		Rectangle bounds = getTextBounds();
		bounds.expand(new Insets(2, 2, 0, 0));
		translateToParent(bounds);
		bounds.intersect(getBounds());
		return bounds;
	}

	/**
	 * Calculates and returns the size of the Label's text. Note that this
	 * Dimension is calculated using the Label's full text, regardless of
	 * whether or not its text is currently truncated. If text size considering
	 * current truncation is desired, use {@link #calculateSubStringTextSize()}.
	 * 
	 * @return the size of the label's text, ignoring truncation
	 * @since 2.0
	 */
	protected Dimension calculateTextSize(int width, int height) {
		return FigureUtilities.getTextExtents(getWrappedText(width, height),
				getFont());
	}

	// Code for the Text Wrap.
	// Starts here.

	private String getWrappedText(int width, int height) {
		if (!isTextWrapped() || width == -1)
			return getText();

		if (isIconExist()) {
			switch (textPlacement) {
			case EAST:
			case WEST:
				width -= iconSize.width + getIconTextGap();
				break;
			case NORTH:
			case SOUTH:
				if (height != -1)
					height -= iconSize.height + getIconTextGap();
				break;
			}
		}

		Font f = getFont();
		int maxLines = Integer.MAX_VALUE;
		if (height != -1) {
			int fontHeight = FigureUtilities.getFontMetrics(f).getHeight();
			maxLines = (int) (height / (double) fontHeight);
		}

		StringBuffer accumlatedText = new StringBuffer();
		StringBuffer remainingText = new StringBuffer(getText());
		int i = 0, j = 0;

		while (remainingText.length() > 0 && j++ < maxLines) {
			if ((i = getLineWrapPosition(remainingText.toString(), f, height)) == 0)
				break;

			if (accumlatedText.length() > 0)
				accumlatedText.append("\n"); //$NON-NLS-1$
			accumlatedText.append(remainingText.substring(0, i));
			remainingText.delete(0, i);
		}
		return accumlatedText.toString();
	}

	private boolean isIconExist() {
		return true;
	}

	private int getLineWrapPosition(String string, Font f, int width) {
		BreakIterator iter = BreakIterator.getLineInstance();
		iter.setText(string);
		int start = iter.first();
		int end = iter.next();

		// if the first line segment does not fit in the width,
		// determine the position within it where we need to cut
		if (FigureUtilities.getTextExtents(string.substring(start, end), f).width > width) {
			iter = BreakIterator.getCharacterInstance();
			iter.setText(string);
			start = iter.first();
		}

		// keep iterating as long as width permits
		do
			end = iter.next();
		while (end != BreakIterator.DONE
				&& FigureUtilities.getTextExtents(string.substring(start, end),
						f).width <= width);
		return (end == BreakIterator.DONE) ? iter.last() : iter.previous();
	}

	/**
	 * Returns the largest substring of <i>s</i> in Font <i>f</i> that can be
	 * confined to the number of pixels in <i>availableWidth<i>.
	 * 
	 * @param s
	 *            the original string
	 * @param f
	 *            the font
	 * @param availableWidth
	 *            the available width
	 * @return the largest substring that fits in the given width
	 * @since 2.0 Copied from <class>FigureUtilities<class> Class, to avoid
	 *        call of static.
	 */
	static int getLargestSubstringConfinedTo(String s, Font f,
			int availableWidth) {
		FontMetrics metrics = FigureUtilities.getFontMetrics(f);
		int min, max;
		float avg = metrics.getAverageCharWidth();
		min = 0;
		max = s.length() + 1;

		// The size of the current guess
		int guess = 0, guessSize = 0;
		while ((max - min) > 1) {
			// Pick a new guess size
			// New guess is the last guess plus the missing width in pixels
			// divided by the average character size in pixels
			guess = guess + (int) ((availableWidth - guessSize) / avg);

			if (guess >= max)
				guess = max - 1;
			if (guess <= min)
				guess = min + 1;

			// Measure the current guess
			guessSize = FigureUtilities
					.getTextExtents(s.substring(0, guess), f).width;

			if (guessSize < availableWidth)
				// We did not use the available width
				min = guess;
			else
				// We exceeded the available width
				max = guess;
		}
		return min;
	}

	/**
	 * Sets to wrap text in Label.
	 * 
	 * @param b
	 *            true/false
	 */
	public void setTextWrap(boolean b) {
		if (isTextWrapped() == b)
			return;
		setFlag(FLAG_WRAP, b);
		revalidate();
		repaint();
	}

	/**
	 * @return text wrap.
	 */
	public boolean isTextWrapped() {
		return true;
	}

	/**
	 * Set text wrap width.
	 * 
	 * @param i
	 *            The label text wrap width
	 */
	public void setTextWrapWidth(int i) {
		if (this.wrapWidth == i)
			return;
		this.wrapWidth = i;
		revalidate();
		repaint();

	}

	/**
	 * Text Wrap Alignment, use only after using <method>setTextWrap(boolean)</method>
	 * 
	 * @param i
	 *            The label text wrapping width
	 */
	public void setTextWrapAlignment(int i) {
		if (getTextWrapAlignment() == i)
			return;

		setAlignmentFlags(i, FLAG_WRAP_ALIGN);
		repaint();
	}

	/**
	 * setAlignmentFlags
	 * 
	 * @param int
	 *            align
	 * @param int
	 *            flagOffset
	 * 
	 * --- TODO - copied needs to modify
	 */
	private void setAlignmentFlags(int align, int flagOffset) {
		flags &= ~(0x7 * flagOffset);
		switch (align) {
		case CENTER:
			flags |= 0x1 * flagOffset;
			break;
		case TOP:
			flags |= 0x2 * flagOffset;
			break;
		case LEFT:
			flags |= 0x3 * flagOffset;
			break;
		case RIGHT:
			flags |= 0x4 * flagOffset;
			break;
		case BOTTOM:
			flags |= 0x5 * flagOffset;
			break;
		}
	}

	/**
	 * 
	 * @param wrapValue
	 * @return PositionConstant representing the alignment
	 */
	private int getAlignment(int flagOffset) {
		int wrapValue = flags & (0x7 * flagOffset);
		if (wrapValue == 0x1 * flagOffset)
			return CENTER;
		else if (wrapValue == 0x2 * flagOffset)
			return TOP;
		else if (wrapValue == 0x3 * flagOffset)
			return LEFT;
		else if (wrapValue == 0x4 * flagOffset)
			return RIGHT;
		else if (wrapValue == 0x5 * flagOffset)
			return BOTTOM;

		return CENTER;
	}

	/**
	 * @return the label text wrapping width
	 */
	public int getTextWrapAlignment() {
		return getAlignment(FLAG_WRAP_ALIGN);
	}

	/**
	 * @return the focus state of this label
	 */
	public boolean hasFocus() {
		return (flags & FLAG_HASFOCUS) != 0;
	}

	/**
	 * @return the selection state of this label
	 */
	public boolean isSelected() {
		return (flags & FLAG_SELECTED) != 0;
	}

	private void graphicsdrawText(Graphics graphics) {
		String subString = getSubStringText();
		StringTokenizer tokenizer = new StringTokenizer(subString, "\n"); //$NON-NLS-1$

		Font f = getFont();
		int fontHeight = FigureUtilities.getFontMetrics(f).getHeight();
		int textWidth = FigureUtilities.getTextExtents(subString, f).width;
		int y = getTextLocation().y;

		// If the font's leading area is 0 then we need to add an offset to
		// avoid truncating at the top (e.g. Korean fonts)
		if (0 == FigureUtilities.getFontMetrics(f).getLeading()) {
			int offset = 2; // 2 is the leading area for default English
			y += offset;
		}

		while (tokenizer.hasMoreTokens()) {
			String token = tokenizer.nextToken();
			System.out.println("Text added to draw" + token); //$NON-NLS-1$
			int tokenWidth = FigureUtilities.getTextExtents(token, f).width;
			int x = getTextLocation().x;
			switch (getTextWrapAlignment()) {
			case CENTER:
				x += (textWidth - tokenWidth) / 2;
				break;
			case RIGHT:
				x += textWidth - tokenWidth;
				break;
			}
			graphics.drawText(token, x, y);
			y += fontHeight;
		}
	}

	/**
	 * Sets the focus state of this label
	 * 
	 * @param b
	 *            true will cause a focus rectangle to be drawn around the text
	 *            of the Label
	 */
	public void setFocus(boolean b) {
		if (hasFocus() == b)
			return;
		setFlag(FLAG_HASFOCUS, b);
		repaint();
	}

	/**
	 * Sets the selection state of this label
	 * 
	 * @param b
	 *            true will cause the label to appear selected
	 */
	public void setSelected(boolean b) {
		if (isSelected() == b)
			return;
		setFlag(FLAG_SELECTED, b);
		repaint();
	}

}
