/*******************************************************************************
 * Copyright (c) 2001, 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
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.sse.ui.internal.provisional.style;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.custom.LineStyleEvent;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.util.Debug;
import org.eclipse.wst.sse.ui.internal.ExtendedConfigurationBuilder;
import org.eclipse.wst.sse.ui.internal.Logger;
import org.eclipse.wst.sse.ui.internal.SSEUIPlugin;
import org.eclipse.wst.sse.ui.internal.preferences.EditorPreferenceNames;
import org.eclipse.wst.sse.ui.internal.util.EditorUtility;

/**
 * This class is to directly mediate between the Structured Document data
 * structure and the text widget's text and events. It assumes there only the
 * model is interested in text events, and all other views will work from that
 * model. Changes to the text widgets input can cause changes in the model,
 * which in turn cause changes to the widget's display.
 * 
 */
public class Highlighter implements IHighlighter {

	/**
	 * A utility class to do various color manipulations
	 */
	private class YUV_RGBConverter {
		/**
		 * This class "holds" the YUV values corresponding to RGB color
		 */
		private class YUV {

			class NormalizedRGB {
				double blue;
				double green;
				private final double maxRGB = 256.0;
				double red;

				public NormalizedRGB(RGB rgb) {
					// first normalize to between 0 - 1
					red = rgb.red / maxRGB;
					green = rgb.green / maxRGB;
					blue = rgb.blue / maxRGB;

					red = gammaNormalized(red);
					green = gammaNormalized(green);
					blue = gammaNormalized(blue);

				}
			}

			private NormalizedRGB normalizedRGB;

			private double u = -1;
			private double v = -1;
			private double y = -1;

			private YUV() {
				super();
			}

			public YUV(double y, double u, double v) {
				this();
				this.y = y;
				this.u = u;
				this.v = v;
			}

			public YUV(RGB rgb) {
				this();
				normalizedRGB = new NormalizedRGB(rgb);
				// force calculations
				getY();
				getV();
				getU();
			}

			/**
			 * normalize to "average" gamma 2.2222 or 1/0.45
			 */
			double gammaNormalized(double colorComponent) {
				if (colorComponent < 0.018) {
					return colorComponent * 0.45;
				}
				else {
					return 1.099 * Math.pow(colorComponent, 0.45) - 0.099;
				}
			}

			/**
			 * @return RGB based on original RGB and current YUV values;
			 */
			public RGB getRGB() {
				RGB result = null;
				double r = getY() + 1.14 * getV();
				double g = getY() - 0.395 * getU() - 0.58 * getV();
				double b = getY() + 2.032 * getU();

				int red = (int) (inverseGammaNormalized(r) * 256);
				int green = (int) (inverseGammaNormalized(g) * 256);
				int blue = (int) (inverseGammaNormalized(b) * 256);
				if (red < 0)
					red = 0;
				else if (red > 255)
					red = 255;
				if (green < 0)
					green = 0;
				else if (green > 255)
					green = 255;
				if (blue < 0)
					blue = 0;
				else if (blue > 255)
					blue = 255;

				result = new RGB(red, green, blue);
				return result;
			}

			public double getU() {
				if (u == -1) {
					u = 0.4949 * (normalizedRGB.blue - getY());
				}
				return u;

			}

			public double getV() {
				if (v == -1) {
					v = 0.877 * (normalizedRGB.red - getY());
				}
				return v;
			}

			public double getY() {
				if (y == -1) {
					y = 0.299 * normalizedRGB.red + 0.587 * normalizedRGB.green + 0.114 * normalizedRGB.blue;
				}
				return y;
			}

			double inverseGammaNormalized(double colorComponent) {
				if (colorComponent < 0.018) {
					return colorComponent * .222;
				}
				else {
					return Math.pow(((.9099 * colorComponent + 0.09)), 2.22);
				}
			}

		}

		public YUV_RGBConverter() {
			super();
		}

		public double calculateYComponent(Color targetColor) {
			return new YUV(targetColor.getRGB()).getY();
		}

		public RGB transformRGB(RGB originalRGB, double scaleFactor, double target) {
			RGB transformedRGB = null;
			// CCIR601 yuv = new CCIR601(originalRGB);
			YUV yuv = new YUV(originalRGB);
			double y = yuv.getY();
			// zero is black, one is white
			if (y < target) {
				// is "dark" make lighter
				y = y + ((target - y) * scaleFactor);
			}
			else {
				// is "light" make darker
				y = y - ((y - target) * scaleFactor);
			}
			// yuv.setY(y);
			YUV newYUV = new YUV(y, yuv.getU(), yuv.getV());
			// CCIR601 newYUV = new CCIR601(y, yuv.getCb601(),
			// yuv.getCr601());
			transformedRGB = newYUV.getRGB();
			return transformedRGB;
		}

		public RGB transformRGBToGrey(RGB originalRGB, double scaleFactor, double target) {
			RGB transformedRGB = null;
			// we left the "full" API method signature, but this
			// version does not take into account originalRGB, though
			// it might someday.
			// for now, we'll simply make the new RGB grey, either a little
			// lighter, or a little darker than background.
			double y = 0;
			double mid = 0.5;
			// zero is black, one is white
			if (target < mid) {
				// is "dark" make lighter
				y = target + scaleFactor;
			}
			else {
				// is "light" make darker
				y = target - scaleFactor;
			}
			int c = (int) Math.round(y * 255);
			// just to gaurd against mis-use, or scale's values greater
			// than mid point (and possibly rounding error)
			if (c > 255)
				c = 255;
			if (c < 0)
				c = 0;
			transformedRGB = new RGB(c, c, c);
			return transformedRGB;
		}
	}

	private final boolean DEBUG = false;
	private final StyleRange[] EMPTY_STYLE_RANGE = new StyleRange[0];
	static final String LINE_STYLE_PROVIDER_EXTENDED_ID = "linestyleprovider"; //$NON-NLS-1$
	private static final int MAX_NUMBER_STYLES = 500;
	private static final int LEFT_STYLES_SIZE = 200;
	private static final int RIGHT_STYLES_SIZE = 200;
	private static final int MIDDLE_STYLES_SIZE = 1;

	private IPropertyChangeListener fForegroundScaleListener = new IPropertyChangeListener() {
		public void propertyChange(PropertyChangeEvent event) {
			if (EditorPreferenceNames.READ_ONLY_FOREGROUND_SCALE.equals(event.getProperty())) {
				IPreferenceStore editorStore = SSEUIPlugin.getDefault().getPreferenceStore();
				readOnlyForegroundScaleFactor = editorStore.getInt(EditorPreferenceNames.READ_ONLY_FOREGROUND_SCALE);
				disposeColorTable();
				refreshDisplay();
			}
		}
	};
	private List fHoldStyleResults;
	private String fPartitioning = IDocumentExtension3.DEFAULT_PARTITIONING;

	private int fSavedLength = -1;
	private int fSavedOffset = -1;
	private StyleRange[] fSavedRanges = null;

	private IStructuredDocument fStructuredDocument;
	private Map fTableOfProviders;

	private Map fExtendedProviders;

	protected final LineStyleProvider NOOP_PROVIDER = new LineStyleProviderForNoOp();

	private double readOnlyBackgroundScaleFactor = 10;
	private Hashtable readOnlyColorTable;
	double readOnlyForegroundScaleFactor = 30;

	private YUV_RGBConverter rgbConverter;
	private ITextViewer textViewer;
	private StyledText textWidget;

	public Highlighter() {
		super();
		
		// in the 'limitSize' method, we make this strong assumption, so, will check here, 
		// so if tweaked in future, we'll get a quick reminder. 
		if (LEFT_STYLES_SIZE + MIDDLE_STYLES_SIZE + RIGHT_STYLES_SIZE > MAX_NUMBER_STYLES) {
			throw new IllegalStateException("Highligher constants are not defined correctly"); //$NON-NLS-1$
		}
	}

	protected void addEmptyRange(int start, int length, Collection holdResults) {
		StyleRange result = new StyleRange();
		result.start = start;
		result.length = length;
		holdResults.add(result);
	}

	/**
	 * Registers a given line style provider for a particular partition type.
	 * If there is already a line style provider registered for this type, the
	 * new line style provider is registered instead of the old one.
	 * 
	 * @param partitionType
	 *            the partition type under which to register
	 * @param the
	 *            line style provider to register, or <code>null</code> to
	 *            remove an existing one
	 */
	public void addProvider(String partitionType, LineStyleProvider provider) {
		getTableOfProviders().put(partitionType, provider);
	}

	/**
	 * Adjust the style ranges' start and length so that they refer to the
	 * textviewer widget's range instead of the textviewer's document range.
	 * 
	 * @param ranges
	 * @param adjustment
	 */
	protected void adjust(StyleRange[] ranges, int adjustment) {
		ITextViewer viewer = getTextViewer();

		if (adjustment != 0) {
			// just use the adjustment value
			// convert document regions back to widget regions
			for (int i = 0; i < ranges.length; i++) {
				// just adjust the range using the given adjustment
				ranges[i].start += adjustment;
			}
		}
		else if (viewer instanceof ITextViewerExtension5) {
			// use ITextViewerExtension5
			ITextViewerExtension5 extension = (ITextViewerExtension5) viewer;

			// convert document regions back to widget regions
			for (int i = 0; i < ranges.length; i++) {
				// get document range, taking into account folding
				// regions in viewer
				IRegion region = extension.modelRange2WidgetRange(new Region(ranges[i].start, ranges[i].length));
				if (region != null) {
					ranges[i].start = region.getOffset();
					ranges[i].length = region.getLength();
				} // else what happens if region is not found?!
			}
		}
	}


	/**
	 * @deprecated - Read Only areas have unchanged background colors
	 */
	void adjustBackground(StyleRange styleRange) {
		RGB oldRGB = null;
		Color oldColor = styleRange.background;
		if (oldColor == null) {
			oldColor = getTextWidget().getBackground();
		}
		oldRGB = oldColor.getRGB();
		Color newColor = getCachedColorFor(oldRGB);
		if (newColor == null) {
			double target = getRGBConverter().calculateYComponent(oldColor);
			// if background is "light" make it darker, and vice versa
			if (target < 0.5)
				target = 1.0;
			else
				target = 0.0;
			RGB newRGB = getRGBConverter().transformRGB(oldRGB, readOnlyBackgroundScaleFactor / 100.0, target);

			cacheColor(oldRGB, newRGB);
			newColor = getCachedColorFor(oldRGB);
		}
		styleRange.background = newColor;
	}

	private void adjustForeground(StyleRange styleRange) {
		RGB oldRGB = null;
		// Color oldColor = styleRange.foreground;
		Color oldColor = styleRange.background;
		if (oldColor == null) {
			// oldRGB = getTextWidget().getForeground().getRGB();
			oldColor = getTextWidget().getBackground();
			oldRGB = oldColor.getRGB();
		}
		else {
			oldRGB = oldColor.getRGB();
		}
		Color newColor = getCachedColorFor(oldRGB);
		if (newColor == null) {
			// make text "closer to" background lumanence
			double target = getRGBConverter().calculateYComponent(oldColor);
			RGB newRGB = getRGBConverter().transformRGBToGrey(oldRGB, readOnlyForegroundScaleFactor / 100.0, target);

			// save conversion, so calculations only need to be done once
			cacheColor(oldRGB, newRGB);
			newColor = getCachedColorFor(oldRGB);
		}
		styleRange.foreground = newColor;
	}

	/**
	 * Cache read-only color.
	 * 
	 * @param oldRGB
	 * @param newColor
	 */
	private void cacheColor(RGB oldRGB, RGB newColor) {
		if (readOnlyColorTable == null) {
			readOnlyColorTable = new Hashtable();
		}
		readOnlyColorTable.put(oldRGB, newColor);
	}

	/**
	 * @param result
	 * @return
	 */
	private StyleRange[] convertReadOnlyRegions(StyleRange[] result, int start, int length) {
		IStructuredDocument structuredDocument = getDocument();

		/**
		 * (dmw) For client/provider simplicity (and consistent look and feel)
		 * we'll handle readonly regions in one spot, here in the Highlighter.
		 * Currently it is a fair assumption that each readonly region will be
		 * on an ITextRegion boundary, so we combine consecutive styles when
		 * found to be equivalent. Plus, for now, we'll just adjust
		 * foreground. Eventually will use a "dimming" algrorithm to adjust
		 * color's satuation/brightness.
		 */
		if (structuredDocument.containsReadOnly(start, length)) {
			// something is read-only in the line, so go through each style,
			// and adjust
			for (int i = 0; i < result.length; i++) {
				StyleRange styleRange = result[i];
				if (structuredDocument.containsReadOnly(styleRange.start, styleRange.length)) {
					adjustForeground(styleRange);
				}
			}
		}
		return result;
	}

	/**
	 * Clear out the readOnlyColorTable
	 */
	void disposeColorTable() {
		if (readOnlyColorTable != null) {
			readOnlyColorTable.clear();
		}
		readOnlyColorTable = null;
	}

	/**
	 * This method is just to get existing read-only colors.
	 */
	private Color getCachedColorFor(RGB oldRGB) {
		Color result = null;

		if (readOnlyColorTable != null) {
			RGB readOnlyRGB = (RGB) readOnlyColorTable.get(oldRGB);
			result = EditorUtility.getColor(readOnlyRGB);
		}

		return result;
	}

	protected IStructuredDocument getDocument() {
		return fStructuredDocument;
	}

	/**
	 * Adjust the given widget offset and length so that they are the
	 * textviewer document's offset and length, taking into account what is
	 * actually visible in the document.
	 * 
	 * @param offset
	 * @param length
	 * @return a region containing the offset and length within the
	 *         textviewer's document or null if the offset is not within the
	 *         document
	 */
	private IRegion getDocumentRangeFromWidgetRange(int offset, int length) {
		IRegion styleRegion = null;
		ITextViewer viewer = getTextViewer();
		if (viewer instanceof ITextViewerExtension5) {
			// get document range, taking into account folding regions in
			// viewer
			ITextViewerExtension5 extension = (ITextViewerExtension5) viewer;
			styleRegion = extension.widgetRange2ModelRange(new Region(offset, length));
		}
		else {
			// get document range, taking into account viewer visible region
			// get visible region in viewer
			IRegion vr = null;
			if (viewer != null)
				vr = viewer.getVisibleRegion();
			else
				vr = new Region(0, getDocument().getLength());

			// if offset is not within visible region, then we don't really
			// care
			if (offset <= vr.getLength()) {
				// Adjust the offset to be within visible region
				styleRegion = new Region(offset + vr.getOffset(), length);
			}
		}
		return styleRegion;
	}


	private Map getExtendedProviders() {
		if (fExtendedProviders == null) {
			fExtendedProviders = new HashMap(3);
		}
		return fExtendedProviders;
	}

	/**
	 * Method getProviderFor.
	 * 
	 * @param typedRegion
	 * @return LineStyleProvider
	 */
	private LineStyleProvider getProviderFor(ITypedRegion typedRegion) {
		String type = typedRegion.getType();
		LineStyleProvider result = (LineStyleProvider) fTableOfProviders.get(type);
		if (result == null) {
			// NOT YET FINALIZED - DO NOT CONSIDER AS API
			synchronized (getExtendedProviders()) {
				if (!getExtendedProviders().containsKey(type)) {
					LineStyleProvider provider = (LineStyleProvider) ExtendedConfigurationBuilder.getInstance().getConfiguration(LINE_STYLE_PROVIDER_EXTENDED_ID, type);
					getExtendedProviders().put(type, provider);
					if (provider != null) {
						provider.init(getDocument(), this);
					}
					result = provider;
				}
				else {
					result = (LineStyleProvider) getExtendedProviders().get(type);
				}
			}
		}
		if (result == null) {
			result = NOOP_PROVIDER;
		}
		return result;
	}

	private YUV_RGBConverter getRGBConverter() {
		if (rgbConverter == null) {
			rgbConverter = new YUV_RGBConverter();
		}
		return rgbConverter;
	}

	private Map getTableOfProviders() {
		if (fTableOfProviders == null) {
			fTableOfProviders = new HashMap();
		}
		return fTableOfProviders;
	}

	/**
	 * Returns the textViewer.
	 * 
	 * @return ITextViewer
	 */
	public ITextViewer getTextViewer() {
		return textViewer;
	}

	/**
	 * @return
	 */
	protected StyledText getTextWidget() {
		return textWidget;
	}

	/**
	 * Installs highlighter support on the given text viewer.
	 * 
	 * @param textViewer
	 *            the text viewer on which content assist will work
	 */
	public void install(ITextViewer newTextViewer) {
		this.textViewer = newTextViewer;

		IPreferenceStore editorStore = SSEUIPlugin.getDefault().getPreferenceStore();
		editorStore.addPropertyChangeListener(fForegroundScaleListener);
		readOnlyForegroundScaleFactor = editorStore.getInt(EditorPreferenceNames.READ_ONLY_FOREGROUND_SCALE);

		if (textWidget != null) {
			textWidget.removeLineStyleListener(this);
		}
		textWidget = newTextViewer.getTextWidget();
		if (textWidget != null) {
			textWidget.addLineStyleListener(this);
		}

		refreshDisplay();
	}

	public StyleRange[] lineGetStyle(int eventLineOffset, int eventLineLength) {
		StyleRange[] eventStyles = EMPTY_STYLE_RANGE;
		try {
			if (getDocument() == null || eventLineLength == 0) {
				// getDocument() == null
				// during initialization, this is sometimes called before our
				// structured
				// is set, in which case we set styles to be the empty style
				// range
				// (event.styles can not be null)

				// eventLineLength == 0
				// we sometimes get odd requests from the very last CRLF in
				// the
				// document
				// it has no length, and there is no node for it!
				eventStyles = EMPTY_STYLE_RANGE;
			}
			else {
				/*
				 * LineStyleProviders work using absolute document offsets. To
				 * support visible regions, adjust the requested range up to
				 * the full document offsets.
				 */
				IRegion styleRegion = getDocumentRangeFromWidgetRange(eventLineOffset, eventLineLength);
				if (styleRegion != null) {
					int start = styleRegion.getOffset();
					int length = styleRegion.getLength();

					ITypedRegion[] partitions = TextUtilities.computePartitioning(getDocument(), fPartitioning, start, length, false);
					eventStyles = prepareStyleRangesArray(partitions, start, length);

					/*
					 * If there is a subtext offset, the style ranges must be
					 * adjusted to the expected offsets just check if
					 * eventLineOffset is different than start then adjust,
					 * otherwise u can leave it alone unless there is special
					 * handling for itextviewerextension5?
					 */
					if (start != eventLineOffset) {
						int offset = 0;
						// figure out visible region to use for adjustment
						// only adjust if need to
						if (!(getTextViewer() instanceof ITextViewerExtension5)) {
							IRegion vr = getTextViewer().getVisibleRegion();
							if (vr != null) {
								offset = vr.getOffset();
							}
						}
						adjust(eventStyles, -offset);
					}

					eventStyles = limitSize(eventStyles);
					
					// for debugging only
					if (DEBUG) {
						if (!valid(eventStyles, eventLineOffset, eventLineLength)) {
							Logger.log(Logger.WARNING, "Highlighter::lineGetStyle found invalid styles at offset " + eventLineOffset); //$NON-NLS-1$
						}
					}
				}

			}

		}
		catch (Exception e) {
			// if ANY exception occurs during highlighting,
			// just return "no highlighting"
			eventStyles = EMPTY_STYLE_RANGE;
			if (Debug.syntaxHighlighting) {
				System.out.println("Exception during highlighting!"); //$NON-NLS-1$
			}
		}

		return eventStyles;
	}

	/**
	 * This method is to centralize the logic in limiting the overall number of style ranges
	 * that make it to the styled text widget. 
	 * 
	 * Too many styles sent to StyledText results in apparent, but not real, 
	 * hangs of Eclipse Display thread. See  
	 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=108806
	 * 
	 * @param eventStyles
	 * @return
	 */
	 private StyleRange[] limitSize(StyleRange[] eventStyles) {
		
		// quick return with same object if not modification needed
		if (eventStyles.length < MAX_NUMBER_STYLES) {
			return eventStyles;
		}
		else {
			// we could just take the easy way out and truncate, but will 
			// be much better appearing if both the start of the line and the 
			// end of the line are displayed with styles. Since these are both
			// the parts of the line a user is likely to look at. The middle of the 
			// line will still be "plain". Presumably, the user would re-format the 
			// file to avoid long lines, so unlikely to see the middle. 
			StyleRange[] newRanges = new StyleRange[LEFT_STYLES_SIZE + RIGHT_STYLES_SIZE + MIDDLE_STYLES_SIZE]; 
			System.arraycopy(eventStyles, 0, newRanges, 0, LEFT_STYLES_SIZE);
			//
			// do end, before we do middle
			System.arraycopy(eventStyles, eventStyles.length-RIGHT_STYLES_SIZE, newRanges, LEFT_STYLES_SIZE + MIDDLE_STYLES_SIZE, RIGHT_STYLES_SIZE);
			//
			// technically, we should compute the exact middle as one big style range, 
			// with default colors and styles, so if someone does actually type or work with 
			// documnet as is, will still be correct. 
			//
			StyleRange allBlank = new StyleRange();
			StyleRange lastKnown = newRanges[LEFT_STYLES_SIZE - 1];
			allBlank.start = lastKnown.start + lastKnown.length;
			StyleRange nextKnown = newRanges[LEFT_STYLES_SIZE + MIDDLE_STYLES_SIZE + 1];
			allBlank.length = nextKnown.start - allBlank.start;
			newRanges[LEFT_STYLES_SIZE] = allBlank;
			return newRanges;
		}
	}

	/**
	 * A passthrough method that extracts relevant data from the
	 * LineStyleEvent and passes it along. This method was separated for
	 * performance testing purposes.
	 * 
	 * @see org.eclipse.swt.custom.LineStyleListener#lineGetStyle(LineStyleEvent)
	 */
	public void lineGetStyle(LineStyleEvent event) {
		int offset = event.lineOffset;
		int length = event.lineText.length();

		/*
		 * For some reason, we are sometimes asked for the same style range
		 * over and over again. This was found to happen during 'revert' of a
		 * file with one line in it that is 40K long! So, while we don't know
		 * root cause, caching the styled ranges in case the exact same
		 * request is made multiple times seems like cheap insurance.
		 */
		if (offset == fSavedOffset && length == fSavedLength && fSavedRanges != null) {
			event.styles = fSavedRanges;
		}
		else {
			// need to assign this array here, or else the field won't get
			// updated
			event.styles = lineGetStyle(offset, length);
			// now saved "cached data" for repeated requests which are exaclty
			// same
			fSavedOffset = offset;
			fSavedLength = length;
			fSavedRanges = event.styles;
		}
	}

	/**
	 * Note: its very important this method never return null, which is why
	 * the final null check is in a finally clause
	 */

	protected StyleRange[] prepareStyleRangesArray(ITypedRegion[] partitions, int start, int length) {

		StyleRange[] result = EMPTY_STYLE_RANGE;

		if (fHoldStyleResults == null) {
			fHoldStyleResults = new ArrayList(partitions.length);
		}
		else {
			fHoldStyleResults.clear();
		}

		// TODO: make some of these instance variables to prevent creation on
		// stack
		LineStyleProvider currentLineStyleProvider = null;
		boolean handled = false;
		for (int i = 0; i < partitions.length; i++) {
			ITypedRegion currentPartition = partitions[i];
			currentLineStyleProvider = getProviderFor(currentPartition);
			currentLineStyleProvider.init(getDocument(), this);
			handled = currentLineStyleProvider.prepareRegions(currentPartition, currentPartition.getOffset(), currentPartition.getLength(), fHoldStyleResults);
			if (Debug.syntaxHighlighting && !handled) {
				System.out.println("Did not handle highlighting in Highlighter inner while"); //$NON-NLS-1$
			}
		}

		int resultSize = fHoldStyleResults.size();
		if (resultSize > 0) {
			result = (StyleRange[]) fHoldStyleResults.toArray(new StyleRange[fHoldStyleResults.size()]);
		}
		else {
			result = EMPTY_STYLE_RANGE;
		}
		result = convertReadOnlyRegions(result, start, length);
		return result;
	}

	public void refreshDisplay() {
		if (textWidget != null && !textWidget.isDisposed())
			textWidget.redraw();
	}

	/**
	 */
	public void refreshDisplay(int start, int length) {
		if (textWidget != null && !textWidget.isDisposed())
			textWidget.redrawRange(start, length, true);
	}

	public void removeProvider(String partitionType) {
		getTableOfProviders().remove(partitionType);
	}

	public void setDocument(IStructuredDocument structuredDocument) {
		fStructuredDocument = structuredDocument;
	}

	public void setDocumentPartitioning(String partitioning) {
		if (partitioning != null) {
			fPartitioning = partitioning;
		}
		else {
			fPartitioning = IDocumentExtension3.DEFAULT_PARTITIONING;
		}
	}

	/**
	 * Uninstalls highlighter support from the text viewer it has previously
	 * be installed on.
	 */
	public void uninstall() {
		if (textWidget != null && !textWidget.isDisposed()) {
			textWidget.removeLineStyleListener(this);
		}
		textWidget = null;

		Collection providers = getTableOfProviders().values();
		Iterator iterator = providers.iterator();
		while (iterator.hasNext()) {
			LineStyleProvider lineStyleProvider = (LineStyleProvider) iterator.next();
			lineStyleProvider.release();
			// this remove probably isn't strictly needed, since
			// typically highlighter instance as a whole will go
			// away ... but in case that ever changes, this seems like
			// a better style.
			iterator.remove();
		}

		synchronized (getExtendedProviders()) {
			providers = new ArrayList(getExtendedProviders().values());
			getExtendedProviders().clear();
		}
		iterator = providers.iterator();
		while (iterator.hasNext()) {
			LineStyleProvider lineStyleProvider = (LineStyleProvider) iterator.next();
			if (lineStyleProvider != null) {
				lineStyleProvider.release();
				iterator.remove();
			}
		}

		IPreferenceStore editorStore = SSEUIPlugin.getDefault().getPreferenceStore();
		editorStore.removePropertyChangeListener(fForegroundScaleListener);
		disposeColorTable();

		// clear out cached variables (d282894)
		fSavedOffset = -1;
		fSavedLength = -1;
		fSavedRanges = null;
	}

	/**
	 * Purely a debugging aide.
	 */
	private boolean valid(StyleRange[] eventStyles, int startOffset, int lineLength) {
		boolean result = false;
		if (eventStyles != null) {
			if (eventStyles.length > 0) {
				StyleRange first = eventStyles[0];
				StyleRange last = eventStyles[eventStyles.length - 1];
				if (startOffset > first.start) {
					result = false;
				}
				else {
					int lineEndOffset = startOffset + lineLength;
					int lastOffset = last.start + last.length;
					if (lastOffset > lineEndOffset) {
						result = false;
					}
					else {
						result = true;
					}
				}
			}
			else {
				// a zero length array is ok
				result = true;
			}
		}
		return result;
	}
}
