/*******************************************************************************
 * Copyright (c) 2000, 2008 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.swt.graphics;

import intrinsic.Array;
import intrinsic.flash.display.CapsStyle;
import intrinsic.flash.display.Graphics;
import intrinsic.flash.display.JointStyle;
import intrinsic.flash.display.Shape;
import intrinsic.flash.text.TextField;
import intrinsic.flash.text.TextFieldAutoSize;
import intrinsic.flash.text.TextFormat;
import intrinsic.flash.text.TextFormatAlign;
import intrinsic.flash.text.TextLineMetrics;

import org.eclipse.swt.SWT;
import org.eclipse.swt.internal.Compatibility;

public final class TextLayout extends Resource {
	Font font;
	String text;
	int lineSpacing;
	int ascent, descent;
	int alignment;
	int wrapWidth;
	int orientation;
	int indent;
	boolean justify;
	int[] tabs;
	int[] segments;
	StyleItem[] styles;

	TextField textField;
	
	static final int BORDER = 2; 
	static final int TAB_COUNT = 32;
	
	static class StyleItem {
		TextStyle style;
		int start;
		
		public String toString () {
			return "StyleItem {" + start + ", " + style + "}";
		}
	}	
	
public TextLayout (Device device) {
	super(device);
	ascent = descent = wrapWidth = -1;
	text = "";
	styles = new StyleItem[2];
	styles[0] = new StyleItem();
	styles[1] = new StyleItem();
	init();
}

void checkLayout () {
	if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED);
}

void destroy () {
	freeRuns();
	font = null;	
	text = null;
	tabs = null;
	styles = null;
}

void computeRuns () {
	computeRuns (null);
}

int [] computePolyline(int left, int top, int right, int bottom) {
	int height = bottom - top; // can be any number
	int width = 2 * height; // must be even
	int peaks = Compatibility.ceil(right - left, width);
	if (peaks == 0 && right - left > 2) {
		peaks = 1;
	}
	int length = ((2 * peaks) + 1) * 2;
	if (length < 0) return new int[0];
	
	int[] coordinates = new int[length];
	for (int i = 0; i < peaks; i++) {
		int index = 4 * i;
		coordinates[index] = left + (width * i);
		coordinates[index+1] = bottom;
		coordinates[index+2] = coordinates[index] + width / 2;
		coordinates[index+3] = top;
	}
	coordinates[length-2] = left + (width * peaks);
	coordinates[length-1] = bottom;
	return coordinates;
}

void computeRuns (GCData data) {
	if (textField != null) {
		if (data == null) return;
		if (textField.parent == null) {
			intrinsic.Number currentFg = (intrinsic.Number)textField.defaultTextFormat.color;
			intrinsic.Number newFg = new intrinsic.Number(data.foreground.handle);
			if (newFg.equals(currentFg)) return;
		}
	}
	int length = text.length();
	textField = new TextField();
	textField.text = length == 0 ? " " : text;
	if (length == 0) length = 1;
	textField.autoSize = TextFieldAutoSize.LEFT;
	textField.multiline = true;
	textField.selectable = false;
	textField.mouseEnabled = false;
	textField.alwaysShowSelection = true;
	textField.background = false;
	TextFormat format = new TextFormat();
	textField.defaultTextFormat = format;
	Font defaultFont = this.font != null ? this.font : device.systemFont;
	format.font = defaultFont.fontFamily;
	format.size = new intrinsic.Number(defaultFont.fontSize);
	if (data != null) {
		format.color = new intrinsic.Number(data.foreground.handle);
	}
	if (defaultFont.fontStyle.equals("italic")) format.italic = defaultFont.fontStyle;
	if (defaultFont.fontWeight.equals("bold")) format.bold = defaultFont.fontWeight;
	if (wrapWidth != -1) {
		String align = TextFormatAlign.LEFT;
		if (justify) {
			align = TextFormatAlign.JUSTIFY;
		} else {
			switch (alignment) {
				case SWT.CENTER:
					align = TextFormatAlign.CENTER;
					break;
				case SWT.RIGHT:
					align = TextFormatAlign.RIGHT;
			}
		}
		format.align = align;
		textField.width = wrapWidth;
		textField.wordWrap = true;
	}
	format.leading = new intrinsic.Number(lineSpacing);
	format.indent = new intrinsic.Number(indent);	
	int tabWidth = 0;
	int tabX = 0;
	Array tabArray = new Array();
	int tabCount = TAB_COUNT;
	if (tabs != null) {
		tabCount = Math.max(TAB_COUNT, tabs.length);
		if (tabs.length == 1) {
			tabWidth = tabs[0];
		}
		if (tabs.length > 1) {
			tabWidth = tabs[tabs.length - 1] - tabs[tabs.length - 2];
		}
		for (int i = 0; i < tabs.length; i++) {
			tabX += tabs[i];
			tabArray.push(tabX);
			tabCount--;
		}
	}
	while (tabCount > 0) {
		tabX += tabWidth;
		tabArray.push(tabX);
		tabCount--;
	}
	format.tabStops = tabArray;
	textField.setTextFormat(format, 0, length);
	//TODO lineSpacing, ascent, descent, orientation, segments
	int start, end;
	for (int i = 0; i < styles.length - 1; i++) {
		StyleItem run = styles[i];
		if (run.style == null) continue;
		TextStyle style = run.style;
		start = translateOffset(run.start);
		end = translateOffset(styles[i + 1].start);
		format = new TextFormat();
		Font font = style.font;
		if (font != null) {
			format.font = font.fontFamily;
			format.size = new intrinsic.Number(font.fontSize);
			if (font.fontStyle.equals("italic")) format.italic = font.fontStyle;
			if (font.fontWeight.equals("bold")) format.bold = font.fontWeight;
		}
		Color foreground = style.foreground;
		if (foreground != null) {
			format.color = new intrinsic.Number(foreground.handle);
		}
		if (style.rise != 0) {
			//TODO rise
		}
		if (style.metrics != null) {
			//TODO metrics
		}
		textField.setTextFormat(format, start, end);
	}
}

public void draw (GC gc, int x, int y) {
	draw(gc, x, y, -1, -1, null, null);
}

public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) {
	draw(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0);
}

public void draw (GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) {
	checkLayout();
	if (gc == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
	if (gc.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);	
	GCData data = gc.data;
	computeRuns(data);
	if (selectionForeground != null && selectionForeground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	if (selectionBackground != null && selectionBackground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	int length = text.length();
	if (length == 0 && flags == 0) return;
	//TODO flags, selection
	boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1;
	if (hasSelection) {
		textField.setSelection(selectionStart, selectionEnd + 1);
	} else {
		textField.setSelection(-1, -1);
	}
	y -= BORDER;
	x -= BORDER;
	if (data.matrix != null) {
		intrinsic.flash.geom.Point point = new intrinsic.flash.geom.Point(x, y);
		point = data.matrix.transformPoint(point);
		textField.x = point.x;
		textField.y = point.y;		
	} else {
		textField.x = x;
		textField.y = y;
	}
	if (data.matrix != null) textField.transform.matrix = data.matrix;
	if (data.clip != null) textField.mask = data.clip;

	/* Draw background */
	for (int i = 0; i < styles.length - 1; i++) {
		StyleItem run = styles[i];
		TextStyle style = run.style;
		if (style == null || style.background == null) continue;
		int start = translateOffset(run.start);
		int end = translateOffset(styles[i + 1].start);
		double lineY = y + BORDER;
		for (int lineIndex = 0; lineIndex < textField.numLines; lineIndex++) {
			int lineStart = textField.getLineOffset(lineIndex);
			int lineEnd = lineStart + textField.getLineLength(lineIndex);
			TextLineMetrics metrics = textField.getLineMetrics(lineIndex);
			if (!(start > lineEnd || end < lineStart)) {
				int highStart = Math.max(lineStart, start);
				int highEnd = Math.min(lineEnd, end);
				if (highStart != highEnd) {
					intrinsic.flash.geom.Rectangle startRect = textField.getCharBoundaries(highStart);
					intrinsic.flash.geom.Rectangle endRect = textField.getCharBoundaries(highEnd - 1);
					Shape shape = new Shape();
					if (data.matrix != null) shape.transform.matrix = data.matrix;
					if (data.clip != null) shape.mask = data.clip;
					Graphics graphics = shape.graphics;
					graphics.beginFill(style.background.handle, data.alpha / 255f);
					graphics.drawRect(x + startRect.x, lineY, endRect.right - startRect.left, metrics.height);
					graphics.endFill();
					data.sprite.addChild(shape);
				}
			}
			if (lineEnd > end) break;
			lineY += metrics.height;
		}
	}
	
	/* Draw Text */
	data.sprite.addChild(textField);

	/* Draw underline and border */
	for (int j = 0; j < styles.length - 1; j++) {
		StyleItem run = styles[j];
		TextStyle style = run.style;
		if (style == null) continue;
		boolean drawUnderline = style.underline && (j + 1 >= styles.length || !style.isAdherentUnderline(styles[j + 1].style));
		boolean drawBorder = style.borderStyle != SWT.NONE && (j + 1 >= styles.length || !style.isAdherentBorder(styles[j + 1].style));
		boolean drawStrikeout = style.strikeout;
		if (!drawUnderline && !drawBorder && !drawStrikeout) continue;
		
		int end = translateOffset(styles[j + 1].start);
		double lineY = y + BORDER;
		for (int lineIndex = 0; lineIndex < textField.numLines; lineIndex++) {
			int lineStart = textField.getLineOffset(lineIndex);
			int lineEnd = lineStart + textField.getLineLength(lineIndex);
			TextLineMetrics metrics = textField.getLineMetrics(lineIndex);
			if (drawUnderline) {
				int start = run.start;
				for (int k = j; k > 0 && style.isAdherentUnderline(styles[k - 1].style); k--) {
					start = styles[k - 1].start;
				}
				start = translateOffset(start);
				if (!(start > lineEnd || end < lineStart)) {
					int highStart = Math.max(lineStart, start);
					int highEnd = Math.min(lineEnd, end);
					if (highStart != highEnd) {
						/* Draw Underline */
						intrinsic.flash.geom.Rectangle startRect = textField.getCharBoundaries(highStart);
						intrinsic.flash.geom.Rectangle endRect = textField.getCharBoundaries(highEnd - 1);
						Shape shape = new Shape();
						if (data.matrix != null) shape.transform.matrix = data.matrix;
						if (data.clip != null) shape.mask = data.clip;
						Graphics graphics = shape.graphics;
						int color = data.foreground.handle;
						if (style.foreground != null) color = style.foreground.handle;
						if (style.underlineColor != null) color = style.underlineColor.handle;
						double underlineX = x + startRect.x; 
						double underlineY = lineY + metrics.ascent + 1;
						double underlineThickness = 0.5;
						graphics.lineStyle(0, color, data.alpha / 255f, false, "normal", CapsStyle.ROUND, JointStyle.MITER, data.lineMiterLimit);
						switch (style.underlineStyle) {
							case SWT.UNDERLINE_ERROR:
							case SWT.UNDERLINE_SQUIGGLE: 
								int squigglyThickness = 1;
								double squigglyHeight = 2 * squigglyThickness;
								double lineBottom = lineY + metrics.height;
								double squigglyY = Math.min(underlineY - squigglyHeight / 2, lineBottom - squigglyHeight - 1);
								int[] points = computePolyline((int)underlineX, (int)squigglyY, (int)(underlineX + endRect.right - startRect.left), (int)(squigglyY + squigglyHeight));	
								graphics.moveTo(points[0] + 0.5, points[1] + 0.5);
								for (int i = 2; i < points.length; i+= 2) {
									graphics.lineTo(points[i] + 0.5, points[i+1] + 0.5);
								}
								break;
							case SWT.UNDERLINE_DOUBLE:
								graphics.drawRect(underlineX, underlineY + underlineThickness * 4, endRect.right - startRect.left, underlineThickness);
								//FALLTHROUGH
							case SWT.UNDERLINE_SINGLE:
								graphics.drawRect(underlineX, underlineY, endRect.right - startRect.left, underlineThickness);
								break;
						}
						data.sprite.addChild(shape);
					}
				}
			}
			
			if (drawBorder) {
				int start = run.start;
				for (int k = j; k > 0 && style.isAdherentBorder(styles[k - 1].style); k--) {
					start = styles[k - 1].start;
				}
				start = translateOffset(start);
				if (!(start > lineEnd || end < lineStart)) {
					int highStart = Math.max(lineStart, start);
					int highEnd = Math.min(lineEnd, end);
					if (highStart != highEnd) {
						/* Draw Border */
						intrinsic.flash.geom.Rectangle startRect = textField.getCharBoundaries(highStart);
						intrinsic.flash.geom.Rectangle endRect = textField.getCharBoundaries(highEnd - 1);
						Shape shape = new Shape();
						if (data.matrix != null) shape.transform.matrix = data.matrix;
						if (data.clip != null) shape.mask = data.clip;
						int color = data.foreground.handle;
						if (style.foreground != null) color = style.foreground.handle;
						if (style.borderColor != null) color = style.borderColor.handle;
						//TODO border style
						Graphics graphics = shape.graphics;
						graphics.lineStyle(0, color, data.alpha / 255f, false, "normal", CapsStyle.ROUND, JointStyle.MITER, data.lineMiterLimit);
						graphics.drawRect(x + startRect.x, lineY, endRect.right - startRect.left - 1, metrics.height - 1);
						data.sprite.addChild(shape);
					}
				}
			}
			
			if (drawStrikeout) {
				int start = translateOffset(run.start);
				if (!(start > lineEnd || end < lineStart)) {
					int highStart = Math.max(lineStart, start);
					int highEnd = Math.min(lineEnd, end);
					if (highStart != highEnd) {
						/* Draw Strikeout */
						intrinsic.flash.geom.Rectangle startRect = textField.getCharBoundaries(highStart);
						intrinsic.flash.geom.Rectangle endRect = textField.getCharBoundaries(highEnd - 1);
						Shape shape = new Shape();
						if (data.matrix != null) shape.transform.matrix = data.matrix;
						if (data.clip != null) shape.mask = data.clip;
						int color = data.foreground.handle;
						if (style.foreground != null) color = style.foreground.handle;
						if (style.strikeoutColor != null) color = style.strikeoutColor.handle;
						Graphics graphics = shape.graphics;
						graphics.lineStyle(0, color, data.alpha / 255f, false, "normal", CapsStyle.ROUND, JointStyle.MITER, data.lineMiterLimit);
						double striteoutY = lineY + metrics.height / 3 * 2;
						graphics.moveTo(x + startRect.x, striteoutY);
						graphics.lineTo(x + startRect.x + endRect.right - startRect.left, striteoutY);
						data.sprite.addChild(shape);
					}
				}
			}
			if (lineEnd > end) break;
			lineY += metrics.height;
		}
	}			
}

void freeRuns () {
	textField = null;
}

public int getAlignment () {
	checkLayout();
	return alignment;
}

public int getAscent () {
	checkLayout();
	return ascent;
}

public Rectangle getBounds () {
	checkLayout();
	computeRuns();
	int width = text.length() > 0 ? (int)textField.textWidth : 0;
	int height = (int)textField.textHeight;
	if (wrapWidth != -1) width = wrapWidth;
	if (ascent != -1 && descent != -1) {
		height = Math.max (height, ascent + descent);
	}
	return new Rectangle(0, 0, width, height);
}

public Rectangle getBounds (int start, int end) {
	checkLayout();
	computeRuns();
	int length = text.length();
	if (length == 0) return new Rectangle(0, 0, 0, 0);
	if (start > end) return new Rectangle(0, 0, 0, 0);
	start = Math.min(Math.max(0, start), length - 1);
	end = Math.min(Math.max(0, end), length - 1);
	start = translateOffset(start);
	end = translateOffset(end);
	intrinsic.flash.geom.Rectangle startRect = textField.getCharBoundaries(start);
	intrinsic.flash.geom.Rectangle endRect = textField.getCharBoundaries(end);
	intrinsic.flash.geom.Rectangle rect = startRect.union(endRect);
	if (textField.getLineIndexOfChar(start) != textField.getLineIndexOfChar(end)) {
		rect.x = BORDER;
		rect.width = textField.width;
	}
	return new Rectangle((int)rect.x - BORDER, (int)rect.y - BORDER, (int)Math.ceil(rect.width), (int)Math.ceil(rect.height));
}

public int getDescent () {
	checkLayout();
	return descent;
}

public Font getFont () {
	checkLayout();
	return font;
}

public int getIndent () {
	checkLayout();
	return indent;
}

public boolean getJustify () {
	checkLayout();
	return justify;
}

public int getLevel (int offset) {
	checkLayout();
	return 0;
}

public Rectangle getLineBounds (int lineIndex) {
	checkLayout();
	computeRuns();
	if (!(0 <= lineIndex && lineIndex < textField.numLines)) SWT.error(SWT.ERROR_INVALID_RANGE);
	if (text.length() == 0) {
		return getBounds();
	}
	int start = textField.getLineOffset(lineIndex);
	intrinsic.flash.geom.Rectangle rect = textField.getCharBoundaries(start);
	TextLineMetrics metrics = textField.getLineMetrics(lineIndex);
	int height = (int)metrics.height;
	if (ascent != -1 && descent != -1) {
		height = Math.max (height, ascent + descent);
	}
	return new Rectangle((int)rect.x - BORDER, (int)rect.y - BORDER, (int)metrics.width, height);
}

public int getLineCount () {
	checkLayout ();
	computeRuns();	
	return textField.numLines;
}

public int getLineIndex (int offset) {
	checkLayout ();
	computeRuns();
	int length = text.length();
	if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
	if (offset == length) return textField.numLines - 1;
	offset = translateOffset(offset);
	return textField.getLineIndexOfChar(offset);
}

public FontMetrics getLineMetrics (int lineIndex) {
	checkLayout ();
	computeRuns();
	int lineCount = getLineCount();
	if (!(0 <= lineIndex && lineIndex < lineCount)) SWT.error(SWT.ERROR_INVALID_RANGE);
	TextLineMetrics metrics = textField.getLineMetrics(lineIndex);
	int ascent = Math.max(this.ascent, (int)metrics.ascent);
	int descent = Math.max(this.descent, (int)metrics.descent);	
	return FontMetrics.internal_new(ascent, descent, ascent + descent, 0);
}

public int[] getLineOffsets () {
	checkLayout ();
	computeRuns();
	int[] offsets = new int[textField.numLines + 1];
	for (int i = 0; i < offsets.length - 1; i++) {
		offsets[i] = untranslateOffset(textField.getLineOffset(i));
	}
	offsets[offsets.length - 1] = text.length();
	return offsets;
}

public Point getLocation (int offset, boolean trailing) {
	checkLayout();
	computeRuns();
	int length = text.length();
	if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
	if (length == 0) return new Point(0, 0);
	offset = translateOffset(offset);
	intrinsic.flash.geom.Rectangle rect = textField.getCharBoundaries(offset);
	int x = (int)rect.x;
	if (trailing) x += rect.width;
	return new Point(x - BORDER, (int)rect.y - BORDER);
}

public int getNextOffset (int offset, int movement) {
	checkLayout();
	return _getOffset (offset, movement, true);
}

int _getOffset(int offset, int movement, boolean forward) {
	computeRuns();
	int length = text.length();
	if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE);
	if (forward && offset == length) return length;
	if (!forward && offset == 0) return 0;
	int step = forward ? 1 : -1;
	if ((movement & SWT.MOVEMENT_CHAR) != 0) return offset + step;
	if ((movement & SWT.MOVEMENT_CLUSTER) != 0) return offset + step;
	offset = translateOffset(offset);
	
	int lineIndex = textField.getLineIndexOfChar(Math.max(0, Math.min(length - 1, offset)));
	int lineStart = textField.getLineOffset(lineIndex);
	int lineLength = textField.getLineLength(lineIndex);
	int lineBreak = 0;//TODO
	while (lineStart <= offset && offset <= lineStart + lineLength) {
		int newOffset = offset + step; 
		int trailing = 0;
		if (forward) {
			if (newOffset + trailing >= lineStart + lineLength - lineBreak) {
				int lineEnd = lineStart + lineLength;
				if (trailing != 0) lineEnd -= lineBreak;
				return untranslateOffset(Math.min(length, lineEnd)); 
			}
		} else {
			if (newOffset + trailing == lineStart) {
				if (lineIndex == 0) return 0;
				int lineEnd = 0;
				//if (newOffset + trailing == offset) lineEnd = OS.TextLine_NewlineLength(lines[lineIndex - 1]);
				return untranslateOffset(Math.max(0, newOffset + trailing - lineEnd)); 
			}
		}
		offset = newOffset + trailing;

		switch (movement) {
			case SWT.MOVEMENT_CLUSTER:
				return untranslateOffset(offset);
			case SWT.MOVEMENT_WORD:
			case SWT.MOVEMENT_WORD_START: {
				if (offset > 0) {
					boolean letterOrDigit = Character.isLetterOrDigit(text.charAt(offset));
					boolean previousLetterOrDigit = Character.isLetterOrDigit(text.charAt(offset - 1));
					if (letterOrDigit != previousLetterOrDigit || !letterOrDigit) {
						if (!Character.isWhitespace(text.charAt(offset))) {
							return untranslateOffset(offset);
						}
					}
				}
				break;
			}
			case SWT.MOVEMENT_WORD_END: {
				if (offset > 0) {
					boolean isLetterOrDigit = Character.isLetterOrDigit(text.charAt(offset));
					boolean previousLetterOrDigit = Character.isLetterOrDigit(text.charAt(offset - 1));
					if (!isLetterOrDigit && previousLetterOrDigit) {
						return untranslateOffset(offset);
					}
				}
				break;
			}
		}
	}
	return forward ? length : 0;
}

public int getOffset (Point point, int[] trailing) {
	checkLayout();
	computeRuns();
	if (point == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
	return getOffset(point.x, point.y, trailing);
}

public int getOffset (int x, int y, int[] trailing) {
	checkLayout();
	computeRuns();
	if (trailing != null && trailing.length < 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	int length = text.length();
	if (length == 0) return 0;
	double nx = Math.max(BORDER, Math.min(x + BORDER, textField.textWidth + BORDER - 1));
	double ny = Math.max(BORDER, Math.min(y + BORDER, textField.textHeight + BORDER - 1));
	int offset = textField.getCharIndexAtPoint(nx, ny);
	
	/*
	*  Bug in Flex. For some reason getCharIndexAtPoint() fails 
	*  for a valid input. The fix is to test char by char.
	*/
	if (offset == -1) {
		intrinsic.flash.geom.Rectangle rect;
		for (int i = 0; i < text.length(); i++) {
			rect = textField.getCharBoundaries(i);
			if (rect != null && rect.contains(nx, ny)) {
				offset = i;
				break;
			}
		}
	}
	if (offset == -1) return 0;
	
	if (trailing != null) {
		intrinsic.flash.geom.Rectangle rect = textField.getCharBoundaries(offset);
		trailing[0] = (nx - rect.x) > rect.width / 2 ? 1 : 0;
	}
	return Math.min(untranslateOffset(offset), length - 1);
}

public int getOrientation () {
	checkLayout();
	return orientation;
}

public int getPreviousOffset (int offset, int movement) {
	checkLayout();
	return _getOffset (offset, movement, false);
}

public int[] getRanges () {
	checkLayout();
	int[] result = new int[styles.length * 2];
	int count = 0;
	for (int i=0; i<styles.length - 1; i++) {
		if (styles[i].style != null) {
			result[count++] = styles[i].start;
			result[count++] = styles[i + 1].start - 1;
		}
	}
	if (count != result.length) {
		int[] newResult = new int[count];
		System.arraycopy(result, 0, newResult, 0, count);
		result = newResult;
	}
	return result;
}

public int[] getSegments () {
	checkLayout();
	return segments;
}

public int getSpacing () {
	checkLayout();	
	return lineSpacing;
}

public TextStyle getStyle (int offset) {
	checkLayout();
	int length = text.length();
	if (!(0 <= offset && offset < length)) SWT.error(SWT.ERROR_INVALID_RANGE);
	for (int i=1; i<styles.length; i++) {
		if (styles[i].start > offset) {
			return styles[i - 1].style;
		}
	}
	return null;
}

public TextStyle[] getStyles () {
	checkLayout();
	TextStyle[] result = new TextStyle[styles.length];
	int count = 0;
	for (int i=0; i<styles.length; i++) {
		if (styles[i].style != null) {
			result[count++] = styles[i].style;
		}
	}
	if (count != result.length) {
		TextStyle[] newResult = new TextStyle[count];
		System.arraycopy(result, 0, newResult, 0, count);
		result = newResult;
	}
	return result;
}

public int[] getTabs () {
	checkLayout();
	return tabs;
}

public String getText () {
	checkLayout();
	return text;
}

public int getWidth () {
	checkLayout();
	return wrapWidth;
}

public boolean isDisposed () {
	return device == null;
}

public void setAlignment (int alignment) {
	checkLayout();
	int mask = SWT.LEFT | SWT.CENTER | SWT.RIGHT;
	alignment &= mask;
	if (alignment == 0) return;
	if ((alignment & SWT.LEFT) != 0) alignment = SWT.LEFT;
	if ((alignment & SWT.RIGHT) != 0) alignment = SWT.RIGHT;
	if (this.alignment == alignment) return;
	freeRuns();
	this.alignment = alignment;
}

public void setAscent (int ascent) {
	checkLayout();
	if (ascent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	if (this.ascent == ascent) return;
	freeRuns();
	this.ascent = ascent;
}

public void setDescent (int descent) {
	checkLayout();
	if (descent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	if (this.descent == descent) return;
	freeRuns();
	this.descent = descent;
}

public void setFont (Font font) {
	checkLayout();
	if (font != null && font.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	if (this.font == font) return;
	if (font != null && font.equals(this.font)) return;
	freeRuns();
	this.font = font;
}

public void setIndent (int indent) {
	checkLayout();
	if (indent < 0) return;	
	if (this.indent == indent) return;
	freeRuns();
	this.indent = indent;
}

public void setJustify (boolean justify) {
	checkLayout();
	if (this.justify == justify) return;
	freeRuns();
	this.justify = justify;
}

public void setOrientation (int orientation) {
	checkLayout();
	int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
	orientation &= mask;
	if (orientation == 0) return;
	if ((orientation & SWT.LEFT_TO_RIGHT) != 0) orientation = SWT.LEFT_TO_RIGHT;
	if (this.orientation == orientation) return;
	this.orientation = orientation;
	freeRuns();
}

public void setSegments (int[] segments) {
	checkLayout();
	if (this.segments == null && segments == null) return;
	if (this.segments != null && segments != null) {
		if (this.segments.length == segments.length) {
			int i;
			for (i = 0; i <segments.length; i++) {
				if (this.segments[i] != segments[i]) break;
			}
			if (i == segments.length) return;
		}
	}
	freeRuns();
	this.segments = segments;
}

public void setSpacing (int spacing) {
	checkLayout();
	if (spacing < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	if (this.lineSpacing == spacing) return;
	freeRuns();
	this.lineSpacing = spacing;
}

public void setStyle (TextStyle style, int start, int end) {
	checkLayout();
	int length = text.length();
	if (length == 0) return;
	if (start > end) return;
	start = Math.min(Math.max(0, start), length - 1);
	end = Math.min(Math.max(0, end), length - 1);
	int low = -1;
	int high = styles.length;
	while (high - low > 1) {
		int index = (high + low) / 2;
		if (styles[index + 1].start > start) {
			high = index;
		} else {
			low = index;
		}
	}
	if (0 <= high && high < styles.length) {
		StyleItem item = styles[high];
		if (item.start == start && styles[high + 1].start - 1 == end) {
			if (style == null) {
				if (item.style == null) return;
			} else {
				if (style.equals(item.style)) return;
			}
		}
	}
	freeRuns();
	int modifyStart = high;
	int modifyEnd = modifyStart;
	while (modifyEnd < styles.length) {
		if (styles[modifyEnd + 1].start > end) break;
		modifyEnd++;
	}
	if (modifyStart == modifyEnd) {
		int styleStart = styles[modifyStart].start; 
		int styleEnd = styles[modifyEnd + 1].start - 1;
		if (styleStart == start && styleEnd == end) {
			styles[modifyStart].style = style;
			return;
		}
		if (styleStart != start && styleEnd != end) {
			StyleItem[] newStyles = new StyleItem[styles.length + 2];
			System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1);
			StyleItem item = new StyleItem();
			item.start = start;
			item.style = style;
			newStyles[modifyStart + 1] = item;	
			item = new StyleItem();
			item.start = end + 1;
			item.style = styles[modifyStart].style;
			newStyles[modifyStart + 2] = item;
			System.arraycopy(styles, modifyEnd + 1, newStyles, modifyEnd + 3, styles.length - modifyEnd - 1);
			styles = newStyles;
			return;
		}
	}
	if (start == styles[modifyStart].start) modifyStart--;
	if (end == styles[modifyEnd + 1].start - 1) modifyEnd++;
	int newLength = styles.length + 1 - (modifyEnd - modifyStart - 1);
	StyleItem[] newStyles = new StyleItem[newLength];
	System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1);	
	StyleItem item = new StyleItem();
	item.start = start;
	item.style = style;
	newStyles[modifyStart + 1] = item;
	styles[modifyEnd].start = end + 1;
	System.arraycopy(styles, modifyEnd, newStyles, modifyStart + 2, styles.length - modifyEnd);
	styles = newStyles;
}

public void setTabs (int[] tabs) {
	checkLayout();
	if (this.tabs == null && tabs == null) return;
	if (this.tabs != null && tabs !=null) {
		if (this.tabs.length == tabs.length) {
			int i;
			for (i = 0; i <tabs.length; i++) {
				if (this.tabs[i] != tabs[i]) break;
			}
			if (i == tabs.length) return;
		}
	}
	freeRuns();
	this.tabs = tabs;
}

public void setText (String text) {
	checkLayout();
	if (text == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
	if (text.equals(this.text)) return;
	freeRuns();
	this.text = text;
	styles = new StyleItem[2];
	styles[0] = new StyleItem();
	styles[1] = new StyleItem();
	styles[1].start = text.length();
}

public void setWidth (int width) {
	checkLayout();
	if (width < -1 || width == 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
	if (this.wrapWidth == width) return;
	freeRuns();
	this.wrapWidth = width;
}

public String toString () {
	if (isDisposed()) return "TextLayout {*DISPOSED*}";
	return "TextLayout {}";
}

/*
 *  Translate a client offset to an internal offset
 */
int translateOffset (int offset) {
	return offset;
}

/*
 *  Translate an internal offset to a client offset
 */
int untranslateOffset (int offset) {
	return offset;
}

}
