/*******************************************************************************
 * Copyright (c) 2001, 2011 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
 *     David Carver (Intalio) - bug 300427 - Comparison of String Objects using == or !=
 *******************************************************************************/
package org.eclipse.wst.sse.core.internal.text;

import java.util.Iterator;
import java.util.List;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IRegion;
import org.eclipse.wst.sse.core.internal.Logger;
import org.eclipse.wst.sse.core.internal.ltk.parser.BlockTagParser;
import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredTextReParser;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionContainer;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.internal.util.Debug;
import org.eclipse.wst.sse.core.internal.util.Utilities;
import org.eclipse.wst.sse.core.utils.StringUtils;


/**
 * This class provides a centralized place to put "reparsing" logic. This is
 * the logic that reparses the text incrementally, as a user types in new
 * characters, or DOM nodes are inserted or deleted. Note: it is not a thread
 * safe class.
 */
public class StructuredDocumentReParser implements IStructuredTextReParser {
	protected IStructuredDocumentRegion dirtyEnd = null;
	protected IStructuredDocumentRegion dirtyStart = null;
	final private String doubleQuote = new String(new char[]{'\"'});
	protected final CoreNodeList EMPTY_LIST = new CoreNodeList();
	protected String fChanges;
	protected String fDeletedText;
	protected boolean fIsEntireDocument;

	private FindReplaceDocumentAdapter fFindReplaceDocumentAdapter = null;
	protected int fLengthDifference;
	protected int fLengthToReplace;
	protected Object fRequester;
	protected int fStart;
	// note: this is the impl class of IStructuredDocument, not the interface
	// FUTURE_TO_DO: I believe some of these can be made private now.?
	protected BasicStructuredDocument fStructuredDocument;

	/**
	 * variable used in anticiapation of multithreading
	 */
	protected boolean isParsing;
	final private String singleQuote = new String(new char[]{'\''});

	public StructuredDocumentReParser() {
		super();
	}

	public StructuredDocumentEvent _checkBlockNodeList(List blockTagList) {
		StructuredDocumentEvent result = null;
		if (blockTagList != null) {
			for (int i = 0; i < blockTagList.size(); i++) {
				org.eclipse.wst.sse.core.internal.ltk.parser.BlockMarker blockTag = (org.eclipse.wst.sse.core.internal.ltk.parser.BlockMarker) blockTagList.get(i);
				String tagName = blockTag.getTagName();
				final String tagStart = "<" + tagName; //$NON-NLS-1$
				result = checkForCriticalName(tagStart); //$NON-NLS-1$
				if (result != null)
					break;
				result = checkForCriticalName("</" + tagName); //$NON-NLS-1$
				if (result != null)
					break;
				result = checkForSelfClosing(tagStart);
				if (result != null)
					break;
				result = checkForTransitionToOpen(tagStart);
				if (result != null)
					break;
			}
		}
		return result;
	}

	/**
	 * Checks if the start region has become self-closing. e.g., &lt;style&gt; -&gt; &lt;style/&gt;
	 */
	private StructuredDocumentEvent checkForSelfClosing(String tagName) {
		StructuredDocumentEvent result = null;
		if (dirtyStart.getText().toLowerCase().indexOf(tagName.toLowerCase()) >= 0) { // within a start-tag
			final int documentLength = fStructuredDocument.getLength();
			int end = fStart + fLengthToReplace + fChanges.length() + 1;
			if (end > documentLength)
				end = documentLength - 1;
			final String oldText = fStructuredDocument.get(fStart, 1);
			final String peek = StringUtils.paste(oldText, fChanges, 0, fLengthToReplace);
			if ("/>".equals(peek)) { // Reparse afterwards if the tag became self-closing
				result = reparse(dirtyStart.getStart(), documentLength - 1);
			}
		}
		return result;
	}

	/**
	 * Checks if the start region has become self-closing. e.g., &lt;style/&gt; -&gt; &lt;style&gt;
	 */
	private StructuredDocumentEvent checkForTransitionToOpen(String tagName) {
		StructuredDocumentEvent result = null;
		if (dirtyStart.getText().toLowerCase().indexOf(tagName.toLowerCase()) >= 0) { // within a start-tag
			final int documentLength = fStructuredDocument.getLength();
			int end = fStart + fLengthToReplace + fChanges.length() + 1;
			if (end > documentLength)
				end = documentLength - 1;
			final String oldText = fStructuredDocument.get(fStart, 2);
			final String peek = StringUtils.paste(oldText, fChanges, 0, fLengthToReplace);
			if ("/>".equals(oldText) && ">".equals(peek)) { // Reparse afterwards if the block tag went from self-closing to open
				result = reparse(dirtyStart.getStart(), documentLength - 1);
			}
		}
		return result;
	}

	/**
	 * Common utility for checking for critical word such as " <SCRIPT>"
	 */
	private StructuredDocumentEvent _checkForCriticalWord(String criticalTarget, boolean checkEnd) {
		StructuredDocumentEvent result = null;
		int documentLength = fStructuredDocument.getLength();
		int propLen = fLengthToReplace;
		if (propLen > documentLength)
			propLen = documentLength;
		int startNeighborhood = fStart - criticalTarget.length();
		int adjustInsert = 0;
		if (startNeighborhood < 0) {
			adjustInsert = 0 - startNeighborhood;
			startNeighborhood = 0;
		}
		int endNeighborhood = fStart + fLengthToReplace + criticalTarget.length() - 1;
		if (endNeighborhood > documentLength)
			endNeighborhood = documentLength - 1;
		int oldlen = endNeighborhood - startNeighborhood; // + 1;
		if (oldlen + startNeighborhood > documentLength) {
			oldlen = documentLength - startNeighborhood;
		}
		String oldText = fStructuredDocument.get(startNeighborhood, oldlen);
		String peek = StringUtils.paste(oldText, fChanges, criticalTarget.length() - adjustInsert, fLengthToReplace);
		boolean isCriticalString = checkTagNames(oldText, criticalTarget, checkEnd);
		boolean toBeCriticalString = checkTagNames(peek, criticalTarget, checkEnd);
		if ((isCriticalString != toBeCriticalString) || // OR if both are
					// critical and there's
					// a change in the end
					// tag ('>')
					((isCriticalString && toBeCriticalString) && (changeInIsEndedState(oldText, peek)))) {
			// if it involves a change of a critical string (making one where
			// there wasn't, or removing
			// one where there was one) then reparse everthing.
			result = reparse(0, documentLength - 1);
		}
		return result;
	}

	private int _computeStartOfDifferences(CoreNodeList oldNodes, CoreNodeList newNodes) {
		int startOfDifferences = -1;
		int newNodesLength = newNodes.getLength();
		boolean foundDifference = false;
		boolean done = false;
		// we'll control our loop based on the old List length
		int oldNodesLength = oldNodes.getLength();
		// be sure to check 'done' first, so startOfDifferences isn't
		// icremented if done is true
		done : while ((!done) && (++startOfDifferences < oldNodesLength)) {
			IStructuredDocumentRegion oldNode = oldNodes.item(startOfDifferences);
			// this lessThanEffectedRegion is to check to be sure the node is
			// infact a candidate
			// to be considered as "old". This check is important for the case
			// where some
			// text is replaceing text that
			// appears identical, but is a different instance. For example, if
			// the text
			// is <P><B></B></P> and <B></B> is inserted at postion 3,
			// resulting in <P><B></B><B></B></P>
			// we do not want the
			// first <B> to be considered old ... it is the new one, the
			// second
			// <B> is the old one.
			if (_lessThanEffectedRegion(oldNode)) {
				// be sure to check that we have new nodes to compare against.
				if (startOfDifferences > newNodesLength) {
					foundDifference = false;
					done = true;
					continue done;
				} else {
					//
					IStructuredDocumentRegion newNode = newNodes.item(startOfDifferences);
					// note: shift is 0 while at beginning of list, before the
					// insertion (or deletion) point. After that, it is
					// fStart+fLengthDifference
					if (!(oldNode.sameAs(newNode, 0))) {
						foundDifference = true;
						done = true;
						continue done;
					} else { // if they are equal, then we will be keeping the
						// old one, so
						// we need to be sure its parentDocument is set back
						// to
						// the right instance
						oldNode.setParentDocument(fStructuredDocument);
					}
				}
			} else {
				// we didn't literally find a difference, but we count it as
				// such by implication
				foundDifference = true;
				done = true;
				continue done;
			}
		}
		// if we literally found a difference, then all is ok and we can
		// return
		// it.
		// if we did not literally find one, then we have to decide why.
		if (!foundDifference) {
			if (newNodesLength == oldNodesLength) { // then lists are
				// identical
				// (and may be of zero
				// length)
				startOfDifferences = -1;
			} else {
				if (newNodesLength > oldNodesLength) { // then lists are
					// identical except for
					// newNodes added
					startOfDifferences = oldNodesLength;
				} else {
					if (newNodesLength < oldNodesLength) { // then lists are
						// identical except
						// for old Nodes
						// deleted
						startOfDifferences = newNodesLength;
					}
				}
			}
		}
		return startOfDifferences;
	}

	private int _computeStartOfDifferences(IStructuredDocumentRegion oldNodeParam, ITextRegionList oldRegions, IStructuredDocumentRegion newNodeParam, ITextRegionList newRegions) {
		int startOfDifferences = -1;
		int newRegionsLength = newRegions.size();
		boolean foundDifference = false;
		boolean done = false;
		// we'll control our loop based on the old List length
		int oldRegionsLength = oldRegions.size();
		// be sure to check 'done' first, so startOfDifferences isn't
		// icremented if done is true
		done : while ((!done) && (++startOfDifferences < oldRegionsLength)) {
			ITextRegion oldRegion = oldRegions.get(startOfDifferences);
			// this lessThanEffectedRegion is to check to be sure the node is
			// infact a candidate
			// to be considered as "old". This check is important for the case
			// where some
			// text is replaceing text that
			// appears identical, but is a different instance. For example, if
			// the text
			// is <P><B></B></P> and <B></B> is inserted at postion 3,
			// resulting in <P><B></B><B></B></P>
			// we do not want the
			// first <B> to be considered old ... it is the new one, the
			// second
			// <B> is the old one.
			if (_lessThanEffectedRegion(oldNodeParam, oldRegion)) {
				// be sure to check that we have new nodes to compare against.
				if (startOfDifferences > newRegionsLength) {
					foundDifference = false;
					done = true;
					continue done;
				} else {
					//
					ITextRegion newRegion = newRegions.get(startOfDifferences);
					// note: shift is 0 while at beginning of list, before the
					// insertion (or deletion) point. After that, it is
					// fStart+fLengthDifference
					if (!(oldNodeParam.sameAs(oldRegion, newNodeParam, newRegion, 0))) {
						foundDifference = true;
						done = true;
						continue done;
					} else {
						// if they are equal, then we will be keeping the old
						// one.
						// unlike the flatnode case, there is no reason to
						// update
						// the textstore, since its the same text store in
						// either case
						// (since its the same flatnode)
						//oldRegion.setTextStore(fStructuredDocument.parentDocument);
					}
				}
			} else {
				// we didn't literally find a difference, but we count it as
				// such by implication
				foundDifference = true;
				done = true;
				continue done;
			}
		}
		// if we literally found a difference, then all is ok and we can
		// return
		// it.
		// if we did not literally find one, then we have to decide why.
		if (!foundDifference) {
			if (newRegionsLength == oldRegionsLength) { // then lists are
				// identical (and may
				// be of zero length)
				startOfDifferences = -1;
			} else {
				if (newRegionsLength > oldRegionsLength) { // then lists are
					// identical except
					// for newRegions
					// added
					startOfDifferences = oldRegionsLength;
				} else {
					if (newRegionsLength < oldRegionsLength) { // then lists
						// are identical
						// except for
						// old Nodes
						// deleted
						startOfDifferences = newRegionsLength;
					}
				}
			}
		}
		return startOfDifferences;
	}

	/**
	 * Part 1 of 2 steps to do a core_reparse
	 * 
	 * Parses a portion of the current text in the IStructuredDocument and
	 * returns the raw result
	 */
	private IStructuredDocumentRegion _core_reparse_text(int rescanStart, int rescanEnd) {
		fStructuredDocument.resetParser(rescanStart, rescanEnd);
		return fStructuredDocument.getParser().getDocumentRegions();
	}

	/**
	 * Part 2 of 2 steps to do a core_reparse
	 * 
	 * Integrates a list of StructuredDocumentRegions based on the current
	 * text contents of the IStructuredDocument into the IStructuredDocument
	 * data structure
	 */
	private StructuredDocumentEvent _core_reparse_update_model(IStructuredDocumentRegion newNodesHead, int rescanStart, int rescanEnd, CoreNodeList oldNodes, boolean firstTime) {
		StructuredDocumentEvent result = null;
		CoreNodeList newNodes = null;
		// rescan
		newNodes = new CoreNodeList(newNodesHead);
		// adjust our newNode chain so the offset positions match
		// our text store (not the simple string of text reparsed)
		StructuredDocumentRegionIterator.adjustStart(newNodesHead, rescanStart);
		// initialize the parentDocument variable of each instance in the new
		// chain
		StructuredDocumentRegionIterator.setParentDocument(newNodesHead, fStructuredDocument);
		// initialize the structuredDocument variable of each instance in the
		// new chain
		//StructuredDocumentRegionIterator.setStructuredDocument(newNodesHead,
		// fStructuredDocument);
		//
		if (firstTime) {
			fStructuredDocument.setCachedDocumentRegion(newNodesHead);
			fStructuredDocument.initializeFirstAndLastDocumentRegion();
			// note: since we are inserting nodes, for the first time, there
			// is
			// no adjustments
			// to downstream stuff necessary.
			result = new StructuredDocumentRegionsReplacedEvent(fStructuredDocument, fRequester, oldNodes, newNodes, fChanges, fStart, fLengthToReplace, fIsEntireDocument);
		} else {
			// note: integrates changes into model as a side effect
			result = minimumEvent(oldNodes, newNodes);
		}
		result.setDeletedText(fDeletedText);
		return result;
	}

	private CoreNodeList _formMinimumList(CoreNodeList flatnodes, int startOfDifferences, int endOfDifferences) {
		CoreNodeList minimalNodes = null;
		// if startOfDifferces is still its initial value, then we have an
		// empty document
		if (startOfDifferences == -1) {
			minimalNodes = EMPTY_LIST;
		} else {
			// if we do not have any flatnode in our flatnode list, then
			// simply
			// return our standard empty list
			if (flatnodes.getLength() == 0) {
				minimalNodes = EMPTY_LIST;
			} else {
				// if startOfDifferences is greater than endOfDifferences,
				// then
				// that means the calculations "crossed" each other, and
				// hence,
				// there really is no differences, so, again, return the empty
				// list
				if (startOfDifferences > endOfDifferences) {
					minimalNodes = EMPTY_LIST;
				} else {
					// the last check be sure we have some differnces
					if ((endOfDifferences > -1)) {
						minimalNodes = new CoreNodeList(flatnodes.item(startOfDifferences), flatnodes.item(endOfDifferences));
					} else {
						// there were no differences, the list wasn't
						// minimized, so simply return it.
						minimalNodes = flatnodes;
					}
				}
			}
		}
		return minimalNodes;
	}

	private boolean _greaterThanEffectedRegion(IStructuredDocumentRegion oldNode) {
		boolean result = false;
		int nodeStart = oldNode.getStartOffset();
		int changedRegionEnd = fStart + fLengthToReplace - 1;
		result = nodeStart > changedRegionEnd;
		return result;
	}

	private boolean _greaterThanEffectedRegion(IStructuredDocumentRegion oldNode, ITextRegion oldRegion) {
		boolean result = false;
		int regionStartOffset = oldNode.getStartOffset(oldRegion);
		int effectedRegionEnd = fStart + fLengthToReplace - 1;
		result = regionStartOffset > effectedRegionEnd;
		return result;
	}

	private boolean _lessThanEffectedRegion(IStructuredDocumentRegion oldNode) {
		boolean result = false;
		int nodeEnd = oldNode.getEndOffset() - 1;
		result = nodeEnd < fStart;
		return result;
	}

	private boolean _lessThanEffectedRegion(IStructuredDocumentRegion oldNode, ITextRegion oldRegion) {
		boolean result = false;
		int nodeEnd = oldNode.getEndOffset(oldRegion) - 1;
		result = nodeEnd < fStart;
		return result;
	}

	private boolean _regionsSameKind(ITextRegion newRegion, ITextRegion oldRegion) {
		boolean result = false;
		// if one region is a container region, and the other not, always
		// return false
		// else, just check their type.
		//      DW druing refactoring, looks like a "typo" here, using 'old' in
		// both.
		//		if (isContainerRegion(oldRegion) != isContainerRegion(oldRegion))
		if (isCollectionRegion(oldRegion) != isCollectionRegion(newRegion))
			result = false;
		else if (oldRegion.getType().equals(newRegion.getType()))
			result = true;
		return result;
	}

	//	private boolean hasCollectionRegions(ITextRegion aRegion) {
	//		boolean result = false;
	//		if (aRegion instanceof ITextRegionCollection) {
	//			ITextRegionCollection regionContainter = (ITextRegionCollection)
	// aRegion;
	//			ITextRegionList regions = regionContainter.getRegions();
	//			Iterator iterator = regions.iterator();
	//			while (iterator.hasNext()) {
	//				if (aRegion instanceof ITextRegionCollection) {
	//					result = true;
	//					break;
	//				}
	//			}
	//		}
	//		return result;
	//	}
	/**
	 * This method is specifically to detect changes in 'isEnded' state,
	 * although it still does so with heuristics. If number of '>' changes,
	 * assume the isEnded state has changed.
	 */
	private boolean changeInIsEndedState(String oldText, String newText) {
		int nOld = StringUtils.occurrencesOf(oldText, '>');
		int nNew = StringUtils.occurrencesOf(newText, '>');
		return !(nOld == nNew);
	}

	private void checkAndAssignParent(IStructuredDocumentRegion oldNode, ITextRegion region) {
		if (region instanceof ITextRegionContainer) {
			((ITextRegionContainer) region).setParent(oldNode);
			return;
		}
		if (region instanceof ITextRegionCollection) {
			ITextRegionCollection textRegionCollection = (ITextRegionCollection) region;
			ITextRegionList regionList = textRegionCollection.getRegions();
			for (int i = 0; i < regionList.size(); i++) {
				ITextRegion innerRegion = regionList.get(i);
				checkAndAssignParent(oldNode, innerRegion);
			}
		}
	}

	/**
	 * A change to a CDATA tag can result in all being reparsed.
	 */
	private StructuredDocumentEvent checkForCDATA() {
		StructuredDocumentEvent result = null;
		result = checkForCriticalKey("<![CDATA["); //$NON-NLS-1$
		if (result == null)
			result = checkForCriticalKey("]]>"); //$NON-NLS-1$
		return result;
	}

	/**
	 * If a comment start or end tag is being added or deleted, we'll rescan
	 * the whole document. The reason is that content that is revealed or
	 * commented out can effect the interpretation of the rest of the
	 * document. Note: for now this is very XML specific, can refactor/improve
	 * later.
	 */
	protected StructuredDocumentEvent checkForComments() {
		StructuredDocumentEvent result = null;
		result = checkForCriticalKey("<!--"); //$NON-NLS-1$
		if (result == null)
			result = checkForCriticalKey("-->"); //$NON-NLS-1$
		// we'll also check for these degenerate cases
		if (result == null)
			result = checkForCriticalKey("<!--->"); //$NON-NLS-1$
		return result;
	}

	/**
	 * Common utility for checking for critical word such as " <SCRIPT>"
	 */
	protected StructuredDocumentEvent checkForCriticalKey(String criticalTarget) {
		return _checkForCriticalWord(criticalTarget, false);
	}

	/**
	 * Common utility for checking for critical word such as " <SCRIPT>"
	 */
	private StructuredDocumentEvent checkForCriticalName(String criticalTarget) {
		return _checkForCriticalWord(criticalTarget, true);
	}

	//	/**
	//	 * Currently this method is pretty specific to ?ML
	//	 * @deprecated - not really deprecated, but plan to make
	//	 * protected ... I'm not sure why its public or misspelled?
	//	 */
	protected StructuredDocumentEvent checkForCrossStructuredDocumentRegionBoundryCases() {
		StructuredDocumentEvent result = null;
		// Case 1: See if the language's syntax requires that multiple
		// StructuredDocumentRegions be rescanned
		if (result == null) {
			result = checkForCrossStructuredDocumentRegionSyntax();
		}
		// Case 2: "block tags" whose content is left unparsed
		if (result == null) {
			Object parser = fStructuredDocument.getParser();
			if (parser instanceof BlockTagParser) {
				List blockTags = ((BlockTagParser) parser).getBlockMarkers();
				result = _checkBlockNodeList(blockTags);
			}
		}
		// FUTURE_TO_DO: is there a better place to do this?
		// or! do we already do it some other more central place?
		if (result != null) {
			result.setDeletedText(fDeletedText);
		}
		return result;
	}

	/**
	 * Allow a reparser to check for extra syntactic cases that require
	 * parsing beyond the flatNode boundary.
	 * 
	 * This implementation is very XML-centric.
	 */
	protected StructuredDocumentEvent checkForCrossStructuredDocumentRegionSyntax() {
		StructuredDocumentEvent result;
		// Case 1: Quote characters are involved
		result = checkForQuotes();
		if (result == null) {
			// Case 2: The input forms or undoes a comment beginning or
			// comment
			// end
			result = checkForComments();
		}
		if (result == null) {
			// Case 3: The input forms or undoes a processing instruction
			result = checkForPI();
		}
		if (result == null) {
			// Case 4: The input forms or undoes a CDATA section
			result = checkForCDATA();
		}
		return result;
	}

	/**
	 * Checks to see if change request exactly matches the text it would be
	 * replacing. (In future, this, or similar method is where to check for
	 * "read only" attempted change.)
	 */
	private StructuredDocumentEvent checkForNoChange() {
		StructuredDocumentEvent result = null;
		// don't check equals unless lengths match
		// should be a tiny bit faster, since usually not
		// of equal lengths (I'm surprised String's equals method
		// doesn't do this.)
		if ((fChanges != null) && (fDeletedText != null) && (fChanges.length() == fDeletedText.length()) && (fChanges.equals(fDeletedText))) {
			result = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace);
			((NoChangeEvent)result).reason = NoChangeEvent.NO_CONTENT_CHANGE;
		}
		return result;
	}

	/**
	 * A change to a PI tag can result in all being reparsed.
	 */
	private StructuredDocumentEvent checkForPI() {
		StructuredDocumentEvent result = null;
		result = checkForCriticalKey("<?"); //$NON-NLS-1$
		if (result == null)
			result = checkForCriticalKey("?>"); //$NON-NLS-1$
		return result;
	}

	/*
	 * For simplicity, if either text to be deleted, or text to be inserted
	 * contains at least one quote, we'll search for previous quote in
	 * document, if any, and use that document region as a dirty start, and we'll use
	 * end of document as dirty end. We need to assume either \" or \' is an
	 * acceptable quote. (NOTE: this is, loosely, an XML assumption -- other
	 * languages would differ, but we'll "hard code" for XML for now.
	 * 
	 * future_TODO: this is a really bad heuristic ... we should be looking
	 * for odd number of quotes within a structuredDocumentRegion (or
	 * something!) This causes way too much reparsing on simple cases, like
	 * deleting a tag with a quoted attribute!
	 */
	private StructuredDocumentEvent checkForQuotes() {
		// routine is supported with null or empty string meaning the same
		// thing: deletion
		if (fChanges == null)
			fChanges = ""; //$NON-NLS-1$
		//
		StructuredDocumentEvent result = null;
		try {
			int dirtyStartPos = -1;
			String proposedDeletion = fStructuredDocument.get(fStart, fLengthToReplace);
			if (fStart < fStructuredDocument.getLength()) {
				if ((fChanges.indexOf(singleQuote) > -1) || (proposedDeletion.indexOf(singleQuote) > -1)) {
					IRegion singleQuoteRegion = getFindReplaceDocumentAdapter().find(fStart, singleQuote, false, false, false, false);
					if (singleQuoteRegion != null) {
						dirtyStartPos = singleQuoteRegion.getOffset();
					}
				} else if ((fChanges.indexOf(doubleQuote) > -1) || (proposedDeletion.indexOf(doubleQuote) > -1)) {
					IRegion doubleQuoteRegion = getFindReplaceDocumentAdapter().find(fStart, doubleQuote, false, false, false, false);
					if (doubleQuoteRegion != null) {
						dirtyStartPos = doubleQuoteRegion.getOffset();
					}
				}
			}
			if (dirtyStartPos > -1) {
				// then we found one, do create new structuredDocument event
				// based on the previous quote to end of document
				// except, we need to be positive that the previous quote is
				// in a "safe start" region (e.g. if in JSP content, we need
				// to
				// backup till we include the whole JSP region, in order for
				// it
				// to be correctly re-parsed. The backing up is done in the
				// reparse/find dirty start from hint
				// method.
				result = reparse(dirtyStartPos, fStructuredDocument.getLength() - 1);
			}
		} catch (BadLocationException e) {
			Logger.logException(e);
		}
		if (result != null) {
			result.setDeletedText(fDeletedText);
		}
		return result;
	}

	private StructuredDocumentEvent checkHeuristics() {
		StructuredDocumentEvent result = null;
		result = checkForNoChange();
		if (result == null) {
			result = checkForCrossStructuredDocumentRegionBoundryCases();
			if (result == null) {
				result = quickCheck();
			}
		}
		return result;
	}

	/**
	 * Takes into account "tag name" rules for comparisons; case-insensitive.
	 */
	private boolean checkTagNames(String compareText, String criticalTarget, boolean checkEnd) {
		boolean result = false;
		if ((compareText == null) || (criticalTarget == null))
			return false;
		int posOfCriticalWord = compareText.toLowerCase().indexOf(criticalTarget.toLowerCase());
		result = posOfCriticalWord > -1;
		if (checkEnd && result) {
			// instead of returning true right away, we'll only return true
			// the
			// potentially matched tag is indeed a tag, for example, if
			// <SCRIPT
			// becomes <SCRIPTS we don't want to say the latter is a critical
			// tag
			int lastPos = posOfCriticalWord + criticalTarget.length();
			if (lastPos < compareText.length()) {
				char lastChar = compareText.charAt(lastPos);
				// Future: check formal definition of this java method, vs.
				// XML
				// parsing rules
				result = (!Character.isLetterOrDigit(lastChar));
			}
		}
		return result;
	}

	/**
	 * The core reparsing method ... after the dirty start and dirty end have
	 * been calculated elsewhere, and the text updated.
	 */
	protected StructuredDocumentEvent core_reparse(int rescanStart, int rescanEnd, CoreNodeList oldNodes, boolean firstTime) {
		IStructuredDocumentRegion newNodesHead = null;
		StructuredDocumentEvent result = null;
		newNodesHead = _core_reparse_text(rescanStart, rescanEnd);
		result = _core_reparse_update_model(newNodesHead, rescanStart, rescanEnd, oldNodes, firstTime);
		return result;
	}

	/**
	 * Resets state to "not parsing"
	 */
	private synchronized void endReParse() {
		isParsing = false;
		dirtyStart = null;
		dirtyEnd = null;
		fChanges = null;
		fDeletedText = null;
		fIsEntireDocument = false;
	}

	protected IStructuredDocumentRegion findDirtyEnd(int end) {
		// Caution: here's one place we have to cast
		IStructuredDocumentRegion result = fStructuredDocument.getRegionAtCharacterOffset(end);
		// if not well formed, get one past, if there is something there
		if ((result != null) && (!result.isEnded())) {
			if (result.getNext() != null) {
				result = result.getNext();
			}
		}
		// also, get one past if exactly equal to the end (this was needed
		// as a simple fix to when a whole exact region is deleted.
		// there's probably a better way.
		if ((result != null) && (end == result.getEnd())) {
			if (result.getNext() != null) {
				result = result.getNext();
			}
		}
		// moved to subclass for quick transition
		// 12/6/2001 - Since we've changed the parser/scanner to allow a lone
		// '<' without
		// always interpretting it as start of a tag name, we need to be a
		// little fancier, in order
		// to "skip" over any plain 'ol content between the lone '<' and any
		// potential meating
		// regions past plain 'ol content.
		//		if (isLoneOpenFollowedByContent(result) && (result.getNext() !=
		// null)) {
		//			result = result.getNext();
		//		}
		if (result != null)
			fStructuredDocument.setCachedDocumentRegion(result);
		dirtyEnd = result;
		return dirtyEnd;
	}

	protected void findDirtyStart(int start) {
		IStructuredDocumentRegion result = fStructuredDocument.getRegionAtCharacterOffset(start);
		// heuristic: if the postion is exactly equal to the start, then
		// go back one more, if it exists. This prevents problems with
		// insertions
		// of text that should be merged with the previous node instead of
		// simply hung
		// off of it as a separate node (ex.: XML content inserted right
		// before
		// an open
		// bracket should become part of the previous content node)
		if (result != null) {
			IStructuredDocumentRegion previous = result.getPrevious();
			if ((previous != null) && ((!(previous.isEnded())) || (start == result.getStart()))) {
				result = previous;
			}
			// If we are now at the end of a "tag dependent" content area (or
			// JSP area)
			// then we need to back up all the way to the beginning of that.
			IStructuredDocumentRegion potential = result;
			// moved to subclass to speed transition
			//			while (isPartOfBlockRegion(potential)) {
			//				potential = potential.getPrevious();
			//			}
			if (potential != null) {
				result = potential;
				fStructuredDocument.setCachedDocumentRegion(result);
			}
		}
		dirtyStart = result;
	}

	protected CoreNodeList formOldNodes(IStructuredDocumentRegion dirtyStart, IStructuredDocumentRegion dirtyEnd) {
		CoreNodeList oldNodes = new CoreNodeList(dirtyStart, dirtyEnd);
		// Now save the old text, that "goes with" the old nodes and regions.
		// Notice we are getting it directly from the text store
		String oldText = null;
		int oldStart = -1;
		int oldEnd = -1;
		// make sure there is some text, if not, use empty string
		// (if one node is not null, the other should ALWAYS be not null too,
		// since it
		// would at least be equal to it.)
		if (dirtyStart != null) {
			oldStart = dirtyStart.getStart();
			oldEnd = dirtyEnd.getEnd();
			oldText = fStructuredDocument.get(oldStart, oldEnd - oldStart);
		} else {
			oldStart = 0;
			oldEnd = 0;
			oldText = ""; //$NON-NLS-1$
		}
		// create a temporary text store for this text
		SubSetTextStore subTextStore = new SubSetTextStore(oldText, oldStart, oldEnd, fStructuredDocument.getLength());
		// Now update the text store of the oldNodes
		StructuredDocumentRegionIterator.setParentDocument(oldNodes, new MinimalDocument(subTextStore));
		return oldNodes;
	}

	/**
	 * @return Returns the findReplaceDocumentAdapter.
	 */
	public FindReplaceDocumentAdapter getFindReplaceDocumentAdapter() {
		if (fFindReplaceDocumentAdapter == null) {
			fFindReplaceDocumentAdapter = new FindReplaceDocumentAdapter(fStructuredDocument);
		}
		return fFindReplaceDocumentAdapter;
	}

	// Note: if thead safety is needed, this and all the other public methods
	// of this class
	// should be synchronized.
	public void initialize(Object requester, int start, int lengthToReplace, String changes) {
		isParsing = true;
		fRequester = requester;
		fStart = start;
		fLengthToReplace = lengthToReplace;
		fChanges = changes;
		// notice this one is derived
		fLengthDifference = Utilities.calculateLengthDifference(fChanges, fLengthToReplace);
		fDeletedText = fStructuredDocument.get(fStart, fLengthToReplace);
		int docLength = fStructuredDocument.getLength();
		fIsEntireDocument = lengthToReplace >= docLength && docLength > 0;
	}

	protected void insertNodes(IStructuredDocumentRegion previousOldNode, IStructuredDocumentRegion nextOldNode, CoreNodeList newNodes) {
		//
		IStructuredDocumentRegion firstNew = null;
		IStructuredDocumentRegion lastNew = null;
		//
		IStructuredDocumentRegion oldPrevious = previousOldNode;
		IStructuredDocumentRegion oldNext = nextOldNode;
		//
		if (newNodes.getLength() > 0) {
			// get pointers
			firstNew = newNodes.item(0);
			lastNew = newNodes.item(newNodes.getLength() - 1);
			// switch surrounding StructuredDocumentRegions' references to
			// lists
			if (oldPrevious != null)
				oldPrevious.setNext(firstNew);
			if (oldNext != null) {
				oldNext.setPrevious(lastNew);
			} else {
				// SIDE EFFECT
				// if oldNext is null, that means we are replaceing the
				// lastNode in the chain,
				// so we need to update the structuredDocuments lastNode as
				// the
				// last of the new nodes.
				fStructuredDocument.setLastDocumentRegion(newNodes.item(newNodes.getLength() - 1));
			}
			if (firstNew != null)
				firstNew.setPrevious(oldPrevious);
			if (lastNew != null)
				lastNew.setNext(oldNext);
		}
		// else nothing to insert
	}

	/**
	 * @param oldRegion
	 */
	private boolean isCollectionRegion(ITextRegion aRegion) {
		return (aRegion instanceof ITextRegionCollection);
	}

	/**
	 * @return boolean
	 */
	public boolean isParsing() {
		return isParsing;
	}

	/**
	 * The minimization algorithm simply checks the old nodes to see if any of
	 * them "survived" the rescan and are unchanged. If so, the instance of
	 * the old node is used instead of the new node. Before the requested
	 * change, need to check type, offsets, and text to determine if the same.
	 * After the requested change, need to check type and text, but adjust the
	 * offsets to what ever the change was.
	 */
	protected StructuredDocumentEvent minimumEvent(CoreNodeList oldNodes, CoreNodeList newNodes) {
		StructuredDocumentEvent event = null;
		CoreNodeList minimalOldNodes = null;
		CoreNodeList minimalNewNodes = null;
		// To minimize nodes, we'll collect all those
		// that are not equal into old and new lists
		// Note: we assume that old and new nodes
		// are basically contiguous -- and we force it to be so,
		// by starting at the beginning to
		// find first difference, and then starting at the end to find
		// last difference. Everything in between we assume is different.
		//
		//
		//
		// startOfDifferences is the index into the core node list where the
		// first difference
		// occurs. But it may point into the old or the new list.
		int startOfDifferences = _computeStartOfDifferences(oldNodes, newNodes);
		int endOfDifferencesOld = -1;
		int endOfDifferencesNew = -1;
		// if one of the lists are shorter than where the differences start,
		// then
		// then some portion of the lists are identical
		if ((startOfDifferences >= oldNodes.getLength()) || (startOfDifferences >= newNodes.getLength())) {
			if (oldNodes.getLength() < newNodes.getLength()) {
				// Then there are new regions to add
				//     these lengths will cause the vector of old ones to not
				//     have any elements, and the vector of new regions to have
				//     just the new ones not in common with the old ones
				//startOfDifferences should equal oldNodes.getLength(),
				// calculated above on _computeStartOfDifferences
				minimalOldNodes = EMPTY_LIST;
				endOfDifferencesNew = newNodes.getLength() - 1;
				minimalNewNodes = _formMinimumList(newNodes, startOfDifferences, endOfDifferencesNew);
			} else {
				if (oldNodes.getLength() > newNodes.getLength()) {
					// delete old
					// then there are old regions to delete
					//    these lengths will cause the vector of old regions to
					//    contain the ones to delete, and the vector of new
					// regions
					//    not have any elements
					//startOfDifferences should equal newNodes.getLength(),
					// calculated above on _computeStartOfDifferences
					endOfDifferencesOld = oldNodes.getLength() - 1;
					minimalOldNodes = _formMinimumList(oldNodes, startOfDifferences, endOfDifferencesOld);
					minimalNewNodes = EMPTY_LIST;
				} else
					// unlikely event
					event = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace);
			}
		} else {
			// We found a normal startOfDiffernces, but have not yet found the
			// ends.
			// We'll look for the end of differences by going backwards down
			// the two lists.
			// Here we need a seperate index for each array, since they may be
			// (and
			// probably are) of different lengths.
			int indexOld = oldNodes.getLength() - 1;
			int indexNew = newNodes.getLength() - 1;
			// The greaterThanEffectedRegion is important to gaurd against
			// incorrect counting
			// when something identical is inserted to what's already there
			// (see minimization test case 5)
			// Note: the indexOld > startOfDifferences keeps indexOld from
			// getting too small,
			// so that the subsequent oldNodes.item(indexOld) is always valid.
			while ((indexOld >= startOfDifferences) && (_greaterThanEffectedRegion(oldNodes.item(indexOld)))) {
				if (!(oldNodes.item(indexOld).sameAs(newNodes.item(indexNew), fLengthDifference))) {
					break;
				} else {
					// if they are equal, then we will be keeping the old one,
					// so
					// we need to be sure its parentDocument is set back to
					// the
					// right instance
					oldNodes.item(indexOld).setParentDocument(fStructuredDocument);
				}
				indexOld--;
				indexNew--;
			}
			endOfDifferencesOld = indexOld;
			endOfDifferencesNew = indexNew;
			minimalOldNodes = _formMinimumList(oldNodes, startOfDifferences, endOfDifferencesOld);
			minimalNewNodes = _formMinimumList(newNodes, startOfDifferences, endOfDifferencesNew);
		} /////////////////////////////////////////
		//
		IStructuredDocumentRegion firstDownStreamNode = null;
		event = regionCheck(minimalOldNodes, minimalNewNodes);
		if (event != null) {
			firstDownStreamNode = minimalOldNodes.item(0).getNext();
			if (firstDownStreamNode != null && fLengthDifference != 0) { // if
				// firstDownStream
				// is
				// null,
				// then
				// we're
				// at
				// the
				// end
				// of
				// the
				// document
				StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference);
			} //
		} else {
			event = nodesReplacedCheck(minimalOldNodes, minimalNewNodes);
			// now splice the new chain of nodes to where the old chain is (or
			// was)
			// the firstDownStreamNode (the first of those after the new
			// nodes)
			// is
			// remembered as a tiny optimization.
			if (minimalOldNodes.getLength() == 0 && minimalNewNodes.getLength() > 0) {
				// if no old nodes are being deleted, then use the
				// the newNodes offset (minus one) to find the point to
				// update downstream nodes, and after updating downstream
				// nodes postions, insert the new ones.
				int insertOffset = minimalNewNodes.item(0).getStartOffset();
				IStructuredDocumentRegion lastOldUnchangedNode = null;
				if (insertOffset > 0) {
					lastOldUnchangedNode = fStructuredDocument.getRegionAtCharacterOffset(insertOffset - 1);
					firstDownStreamNode = lastOldUnchangedNode.getNext();
				} else {
					// we're inserting at very beginning
					firstDownStreamNode = fStructuredDocument.getFirstStructuredDocumentRegion();
					// SIDE EFFECT: change the firstNode pointer if we're
					// inserting at beginning
					fStructuredDocument.setFirstDocumentRegion(minimalNewNodes.item(0));
				}
				StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference);
				insertNodes(lastOldUnchangedNode, firstDownStreamNode, minimalNewNodes);
				// this (nodes replaced) is the only case where we need to
				// update the cached Node
				reSetCachedNode(minimalOldNodes, minimalNewNodes);
			} else {
				firstDownStreamNode = switchNodeLists(minimalOldNodes, minimalNewNodes);
				// no need to adjust the length of the new nodes themselves,
				// they
				// are already correct, but we do need to
				// adjust all "down stream" nodes with the length of the
				// insertion or deletion
				// --- adjustment moved to calling method.
				if (firstDownStreamNode != null) {
					// && event != null
					StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference);
				} //
				// this (nodes replaced) is the only case where we need to
				// update the cached Node
				reSetCachedNode(minimalOldNodes, minimalNewNodes);
			}
		}
		return event;
	}

	// TODO: This should be abstract.
	public IStructuredTextReParser newInstance() {
		return new StructuredDocumentReParser();
	}

	protected StructuredDocumentEvent nodesReplacedCheck(CoreNodeList oldNodes, CoreNodeList newNodes) {
		// actually, nothing to check here, since (and assuming) we've already
		// minimized the number of nodes, and ruled out mere region changes
		StructuredDocumentEvent result = new StructuredDocumentRegionsReplacedEvent(fStructuredDocument, fRequester, oldNodes, newNodes, fChanges, fStart, fLengthToReplace, fIsEntireDocument);
		return result;
	}

	/**
	 * A method to allow any heuristic "quick checks" that might cover many
	 * many cases, before expending the time on a full reparse.
	 *  
	 */
	public StructuredDocumentEvent quickCheck() {
		StructuredDocumentEvent result = null;
		// if the dirty start is null, then we have an empty document.
		// in which case we'll return null so everything can be
		// reparsed "from scratch" . If its not null, we'll give the flatnode
		// a
		// chance
		// to handle, but only if there is one flatnode involved.
		if (dirtyStart != null && dirtyStart == dirtyEnd) {
			IStructuredDocumentRegion targetNode = dirtyStart;
			result = dirtyStart.updateRegion(fRequester, targetNode, fChanges, fStart, fLengthToReplace);
			if (result != null) {
				// at this point only, we need to update the text store and
				// and downstream nodes.
				// FUTURE_TO_DO: can this dependency on structuredDocument
				// method be eliminated?
				fStructuredDocument.updateDocumentData(fStart, fLengthToReplace, fChanges);
				IStructuredDocumentRegion firstDownStreamNode = targetNode.getNext();
				// then flatnode must have been the last one, so need to
				// update
				// any downstream ones
				if (firstDownStreamNode != null) {
					StructuredDocumentRegionIterator.adjustStart(firstDownStreamNode, fLengthDifference);
				}
			}
		}
		if (result != null) {
			result.setDeletedText(fDeletedText);
		}
		return result;
	}

	/**
	 * If only one node is involved, sees how many regions are changed. If
	 * only one, then its a 'regionChanged' event ... if more than one, its a
	 * 'regionsReplaced' event.
	 */
	protected StructuredDocumentEvent regionCheck(CoreNodeList oldNodes, CoreNodeList newNodes) {
		if (Debug.debugStructuredDocument)
			System.out.println("IStructuredDocument::regionsReplacedCheck"); //$NON-NLS-1$
		//$NON-NLS-1$
		//$NON-NLS-1$
		// the "regionsReplaced" event could only be true if and only if the
		// nodelists
		// are each only "1" in length.
		StructuredDocumentEvent result = null;
		int oldLength = oldNodes.getLength();
		int newLength = newNodes.getLength();
		if ((oldLength != 1) || (newLength != 1)) {
			result = null;
		} else {
			IStructuredDocumentRegion oldNode = oldNodes.item(0);
			IStructuredDocumentRegion newNode = newNodes.item(0);
			result = regionCheck(oldNode, newNode);
		}
		return result;
	}

	/**
	 * If only one node is involved, sees how many regions are changed. If
	 * only one, then its a 'regionChanged' event ... if more than one, its a
	 * 'regionsReplaced' event.
	 */
	protected StructuredDocumentEvent regionCheck(IStructuredDocumentRegion oldNode, IStructuredDocumentRegion newNode) {
		//
		StructuredDocumentEvent result = null;
		ITextRegionList oldRegions = oldNode.getRegions();
		ITextRegionList newRegions = newNode.getRegions();
		ITextRegion[] oldRegionsArray = oldRegions.toArray();
		ITextRegion[] newRegionsArray = newRegions.toArray();
		//
		// for the 'regionsReplaced' event, we don't care if
		// the regions changed due to type, or text,
		// we'll just collect all those that are not equal
		// into the old and new region lists.
		// Note: we, of course, assume that old and new regions
		// are basically contiguous -- and we force it to be so,
		// even if not literally so, by starting at beginning to
		// find first difference, and then starting at end to find
		// last difference. Everything in between we assume is different.
		//
		// going up is easy, we start at zero in each, and continue
		// till regions are not the same.
		int startOfDifferences = _computeStartOfDifferences(oldNode, oldRegions, newNode, newRegions);
		int endOfDifferencesOld = -1;
		int endOfDifferencesNew = -1;
		//
		//
		// if one of the lists are shorter than where the differences start,
		// then
		// then some portion of the lists are identical
		if ((startOfDifferences >= oldRegions.size()) || (startOfDifferences >= newRegions.size())) {
			if (oldRegions.size() < newRegions.size()) {
				// INSERT CASE
				// then there are new regions to add
				//     these lengths will cause the vector of old ones to not
				//     have any elements, and the vector of new regions to have
				//     just the new ones.
				startOfDifferences = oldRegionsArray.length;
				endOfDifferencesOld = oldRegionsArray.length - 1;
				endOfDifferencesNew = newRegionsArray.length - 1;
			} else {
				if (oldRegions.size() > newRegions.size()) {
					// DELETE CASE
					// then there are old regions to delete
					//    these lengths will cause the vector of old regions to
					//    contain the ones to delete, and the vector of new
					// regions
					//    not have any elements
					startOfDifferences = newRegionsArray.length;
					endOfDifferencesOld = oldRegionsArray.length - 1;
					endOfDifferencesNew = newRegionsArray.length - 1;
				} else {
					// else the lists are identical!
					// unlikely event, probably error in current design, since
					// we check for identity at the very beginning of
					// reparsing.
					result = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace);
				}
			}
		} else {
			if ((startOfDifferences > -1) && (endOfDifferencesOld < 0) && (endOfDifferencesNew < 0)) {
				// We found a normal startOfDiffernces, but have not yet found
				// the ends.
				// We'll look for the end of differences by going backwards
				// down the two lists.
				// Here we need a seperate index for each array, since they
				// may
				// be (and
				// probably are) of different lengths.
				int indexOld = oldRegionsArray.length - 1;
				int indexNew = newRegionsArray.length - 1;
				while ((indexOld >= startOfDifferences) && (_greaterThanEffectedRegion(oldNode, oldRegionsArray[indexOld]))) {
					if ((!(oldNode.sameAs(oldRegionsArray[indexOld], newNode, newRegionsArray[indexNew], fLengthDifference)))) {
						//endOfDifferencesOld = indexOne;
						//endOfDifferencesNew = indexTwo;
						break;
					}
					indexOld--;
					indexNew--;
				}
				endOfDifferencesOld = indexOld;
				endOfDifferencesNew = indexNew;
			}
		}
		//
		// result != null means the impossible case above occurred
		if (result == null) {
			// Now form the two vectors of different regions
			ITextRegionList holdOldRegions = new TextRegionListImpl();
			ITextRegionList holdNewRegions = new TextRegionListImpl();
			if (startOfDifferences > -1 && endOfDifferencesOld > -1) {
				for (int i = startOfDifferences; i <= endOfDifferencesOld; i++) {
					holdOldRegions.add(oldRegionsArray[i]);
				}
			}
			if (startOfDifferences > -1 && endOfDifferencesNew > -1) {
				for (int i = startOfDifferences; i <= endOfDifferencesNew; i++) {
					holdNewRegions.add(newRegionsArray[i]);
				}
			}
			if (holdOldRegions.size() == 0 && holdNewRegions.size() == 0) {
				// then this means the regions were identical, which means
				// someone
				// pasted exactly the same thing they had selected, or !!!
				// someone deleted the end bracket of the tag. !!!?
				result = new NoChangeEvent(fStructuredDocument, fRequester, fChanges, fStart, fLengthToReplace);
			} else {
				//If both holdOldRegions and holdNewRegions are of length 1,
				// then its
				// a "region changed" event, else a "regions replaced" event.
				// so we want the new instance of region to become part of the
				// old instance of old node
				if ((holdOldRegions.size() == 1) && (holdNewRegions.size() == 1) && _regionsSameKind((holdNewRegions.get(0)), (holdOldRegions.get(0)))) {
					ITextRegion newOldRegion = swapNewForOldRegion(oldNode, holdOldRegions.get(0), newNode, holdNewRegions.get(0));
					// -- need to update any down stream regions, within this
					// 'oldNode'
					updateDownStreamRegions(oldNode, newOldRegion);
					result = new RegionChangedEvent(fStructuredDocument, fRequester, oldNode, newOldRegion, fChanges, fStart, fLengthToReplace);
				} else {
					replaceRegions(oldNode, holdOldRegions, newNode, holdNewRegions);
					// -- need to update any down stream regions, within this
					// 'oldNode'
					// don't need with the way replaceRegions is implemented.
					// It handles.
					//if(holdNewRegions.size() > 0)
					//updateDownStreamRegions(oldNode, (ITextRegion)
					// holdNewRegions.lastElement());
					result = new RegionsReplacedEvent(fStructuredDocument, fRequester, oldNode, holdOldRegions, holdNewRegions, fChanges, fStart, fLengthToReplace);
				}
			}
		}
		return result;
	}

	/**
	 * An entry point for reparsing. It calculates the dirty start and dirty
	 * end flatnodes based on the start point and length of the changes.
	 *  
	 */
	public StructuredDocumentEvent reparse() {
		StructuredDocumentEvent result = null;
		// if we do not have a cachedNode, then the document
		// must be empty, so simply use 'null' as the dirtyStart and dirtyEnd
		// otherwise, find them.
		if (fStructuredDocument.getCachedDocumentRegion() != null) {
			findDirtyStart(fStart);
			int end = fStart + fLengthToReplace;
			findDirtyEnd(end);
		}
		if (fStructuredDocument.getCachedDocumentRegion() != null) {
			result = checkHeuristics();
		}
		if (result == null) {
			result = reparse(dirtyStart, dirtyEnd);
		}
		endReParse();
		return result;
	}

	/**
	 * An entry point for reparsing. It calculates the dirty start and dirty
	 * end flatnodes based on suggested positions to begin and end. This is
	 * needed for cases where parsing must go beyond the immediate node and
	 * its direct neighbors.
	 *  
	 */
	protected StructuredDocumentEvent reparse(int reScanStartHint, int reScanEndHint) {
		StructuredDocumentEvent result = null;
		// if we do not have a cachedNode, then the document
		// must be empty, so simply use 'null' as the dirtyStart and dirtyEnd
		if (fStructuredDocument.getCachedDocumentRegion() != null) {
			findDirtyStart(reScanStartHint);
			findDirtyEnd(reScanEndHint);
		}
		result = reparse(dirtyStart, dirtyEnd);
		isParsing = false;
		// debug
		//verifyStructured(result);
		return result;
	}

	/**
	 * The core reparsing method ... after the dirty start and dirty end have
	 * been calculated elsewhere.
	 */
	protected StructuredDocumentEvent reparse(IStructuredDocumentRegion dirtyStart, IStructuredDocumentRegion dirtyEnd) {
		StructuredDocumentEvent result = null;
		int rescanStart = -1;
		int rescanEnd = -1;
		boolean firstTime = false;
		//
		// "save" the oldNodes (that may be replaced) in a list
		CoreNodeList oldNodes = formOldNodes(dirtyStart, dirtyEnd);
		if (dirtyStart == null || dirtyEnd == null) {
			// dirtyStart or dirty end are null, then that means we didn't
			// have
			// a
			// cached node, which means we have an empty document, so we
			// just need to rescan the changes
			rescanStart = 0;
			rescanEnd = fChanges.length();
			firstTime = true;
		} else {
			// set the start of the text to rescan
			rescanStart = dirtyStart.getStart();
			//
			// set the end of the text to rescan
			// notice we use the same rationale as for the rescanStart,
			// with the added caveat that length has to be added to it,
			// to compensate for the new text which has been added or deleted.
			// If changes has zero length, then "length" will be negative,
			// since
			// we are deleting text. Otherwise, use the difference between
			// what's selected to be replaced and the length of the new text.
			rescanEnd = dirtyEnd.getEnd() + fLengthDifference;
		}
		// now that we have the old stuff "saved" away, update the document
		// with the changes.
		// FUTURE_TO_DO -- don't fire "document changed" event till later
		fStructuredDocument.updateDocumentData(fStart, fLengthToReplace, fChanges);
		// ------------------ now the real work
		result = core_reparse(rescanStart, rescanEnd, oldNodes, firstTime);
		//
		// event is returned to the caller, incase there is
		// some opitmization they can do
		return result;
	}

	protected void replaceRegions(IStructuredDocumentRegion oldNode, ITextRegionList oldRegions, IStructuredDocumentRegion newNode, ITextRegionList newRegions) {
		int insertPos = -1;
		ITextRegionList regions = oldNode.getRegions();
		// make a fake flatnode to be new parent of oldRegions, so their text
		// will be right.
		//IStructuredDocumentRegion holdOldStructuredDocumentRegion = new
		// BasicStructuredDocumentRegion(oldNode);
		//
		// need to reset the parent of the new to-be-inserted regions to be
		// the
		// same oldNode that is the one having its regions changed
		// DW, 4/16/2003, removed since ITextRegion no longer has parent.
		// 		ITextRegionContainer oldParent = oldNode;
		//		for (int i = 0; i < newRegions.size(); i++) {
		//			AbstractRegion region = (AbstractRegion) newRegions.elementAt(i);
		//			region.setParent(oldParent);
		//		}
		// if there are no old regions, insert the new regions according to
		// offset
		if (oldRegions.size() == 0) {
			ITextRegion firstNewRegion = newRegions.get(0);
			int firstOffset = newNode.getStartOffset(firstNewRegion);
			// if at beginning, insert there
			if (firstOffset == 0) {
				insertPos = 0;
			} else {
				//
				ITextRegion regionAtOffset = oldNode.getRegionAtCharacterOffset(firstOffset);
				if (regionAtOffset == null)
					insertPos = regions.size();
				else
					insertPos = regions.indexOf(regionAtOffset);
			}
		} else {
			// else, delete old ones before inserting new ones in their place
			ITextRegion firstOldRegion = oldRegions.get(0);
			insertPos = regions.indexOf(firstOldRegion);
			regions.removeAll(oldRegions);
		}
		regions.addAll(insertPos, newRegions);
		// now regions vector of each node should be of equal length,
		// so go through each, and make sure the old regions
		// offsets matches the new regions offsets
		// (we'll just assign them all, but could be slightly more effiecient)
		ITextRegionList allNewRegions = newNode.getRegions();
		for (int i = 0; i < regions.size(); i++) {
			ITextRegion nextOldishRegion = regions.get(i);
			ITextRegion nextNewRegion = allNewRegions.get(i);
			nextOldishRegion.equatePositions(nextNewRegion);
			checkAndAssignParent(oldNode, nextOldishRegion);
		}
		oldNode.setLength(newNode.getLength());
		oldNode.setEnded(newNode.isEnded());
		oldNode.setParentDocument(newNode.getParentDocument());
		// removed concept of part of these regions, so no longer need to do.
		//		for (int i = 0; i < oldRegions.size(); i++) {
		//			ITextRegion region = (ITextRegion) oldRegions.elementAt(i);
		//			region.setParent(holdOldStructuredDocumentRegion);
		//		}
	}

	private void reSetCachedNode(CoreNodeList oldNodes, CoreNodeList newNodes) {
		// use the last newNode as the new cachedNode postion, unless its null
		// (e.g. when nodes are deleted) in which case, assign
		// it to a "safe" node so we don't lose reference to the
		// structuredDocument!
		if (newNodes.getLength() > 0) {
			// use last new node as the cache
			fStructuredDocument.setCachedDocumentRegion(newNodes.item(newNodes.getLength() - 1));
		} else {
			// if cachedNode is an old node, then we're in trouble:
			// we can't leave it as the cached node! and its already
			// been disconnected from the model, so we can't do getNext
			// or getPrevious, so we'll get one that is right before
			// (or right after) the offset of the old nodes that are being
			// deleted.
			// 
			// if newNodesHead and cachedNode are both null, then
			// it means we were asked to insert an empty string into
			// an empty document. So we have nothing to do here
			// (that is, we have no node to cache)
			// similarly if there are no new nodes and no old nodes then
			// nothing to do (but that should never happen ... we shouldn't
			// get there if there is no event to generate).
			if ((fStructuredDocument.getCachedDocumentRegion() != null) && (oldNodes.getLength() > 0)) {
				// note: we can't simple use nodeAtCharacterOffset, since it
				// depends on cachedNode.
				if (oldNodes.includes(fStructuredDocument.getCachedDocumentRegion()))
					fStructuredDocument.setCachedDocumentRegion(fStructuredDocument.getFirstStructuredDocumentRegion());
			}
			if ((fStructuredDocument.getCachedDocumentRegion() == null) && (Debug.displayWarnings)) {
				// this will happen now legitamately when all text is deleted
				// from a document
				System.out.println("Warning: StructuredDocumentReParser::reSetCachedNode: could not find a node to cache! (its ok if all text deleted)"); //$NON-NLS-1$
			}
		}
	}

	public void setStructuredDocument(IStructuredDocument newStructuredDocument) {
		// NOTE: this method (and class) depend on being able to
		// do the following cast (i.e. references some fields directly)
		fStructuredDocument = (BasicStructuredDocument) newStructuredDocument;
		fFindReplaceDocumentAdapter = null;
	}

	private IStructuredDocumentRegion splice(CoreNodeList oldNodes, CoreNodeList newNodes) {
		//
		IStructuredDocumentRegion firstOld = null;
		IStructuredDocumentRegion firstNew = null;
		IStructuredDocumentRegion lastOld = null;
		IStructuredDocumentRegion lastNew = null;
		//
		IStructuredDocumentRegion oldPrevious = null;
		IStructuredDocumentRegion oldNext = null;
		IStructuredDocumentRegion newPrevious = null;
		IStructuredDocumentRegion newNext = null;
		//
		// if called with both arguments empty lists, we can disregard.
		// this happens, for example, when some text is replaced with the
		// identical text.
		if ((oldNodes.getLength() == 0) && (newNodes.getLength() == 0)) {
			return null;
		}
		// get pointers
		if (newNodes.getLength() > 0) {
			firstNew = newNodes.item(0);
			lastNew = newNodes.item(newNodes.getLength() - 1);
		}
		//
		if (oldNodes.getLength() > 0) {
			firstOld = oldNodes.item(0);
			lastOld = oldNodes.item(oldNodes.getLength() - 1);
			if (firstOld != null)
				oldPrevious = firstOld.getPrevious();
			if (lastOld != null)
				oldNext = lastOld.getNext();
		}
		// handle switch
		if (newNodes.getLength() > 0) {
			// switch surrounding StructuredDocumentRegions' references to
			// lists
			if (oldPrevious != null)
				oldPrevious.setNext(firstNew);
			if (newPrevious != null)
				newPrevious.setNext(firstOld);
			if (oldNext != null)
				oldNext.setPrevious(lastNew);
			if (newNext != null)
				newNext.setPrevious(lastOld);
			// switch list pointers to surrounding StructuredDocumentRegions
			if (firstOld != null)
				firstOld.setPrevious(newPrevious);
			if (lastOld != null)
				lastOld.setNext(newNext);
			if (firstNew != null)
				firstNew.setPrevious(oldPrevious);
			if (lastNew != null)
				lastNew.setNext(oldNext);
		} else {
			// short circuit when there are no new nodes
			if (oldPrevious != null)
				oldPrevious.setNext(oldNext);
			if (oldNext != null)
				oldNext.setPrevious(oldPrevious);
		}
		//
		// SIDE EFFECTs
		// if we have oldNodes, and if oldNext or oldPrevious is null,
		// that means we are replacing
		// the lastNode or firstNode the structuredDocuments's chain of nodes,
		// so we need to update the structuredDocuments last or first Node
		// as the last or first of the new nodes.
		// (and sometimes even these will be null! such as when deleting all
		// text in a document).
		if ((oldNext == null) && (oldNodes.getLength() > 0)) {
			if (newNodes.getLength() > 0) {
				fStructuredDocument.setLastDocumentRegion(lastNew);
			} else {
				// in this case, the last node is being deleted, but not
				// replaced
				// with anything. In this case, we can just back up one
				// from the first old node
				fStructuredDocument.setLastDocumentRegion(firstOld.getPrevious());
			}
		}
		if ((oldPrevious == null) && (oldNodes.getLength() > 0)) {
			if (newNodes.getLength() > 0) {
				fStructuredDocument.setFirstDocumentRegion(firstNew);
			} else {
				// in this case the first node is being deleted, but not
				// replaced
				// with anything. So, we just go one forward past the last old
				// node.
				fStructuredDocument.setFirstDocumentRegion(lastOld.getNext());
			}
		}
		// as a tiny optimization, we return the first of the downstream
		// nodes,
		// if any
		return oldNext;
	}

	/**
	 * The purpose of this method is to "reuse" the old container region, when
	 * found to be same (so same instance doesn't change). The goal is to
	 * "transform" the old region, so its equivelent to the newly parsed one.
	 *  
	 */
	private ITextRegion swapNewForOldRegion(IStructuredDocumentRegion oldNode, ITextRegion oldRegion, IStructuredDocumentRegion newNode, ITextRegion newRegion) {
		// makes the old region instance the correct size.
		oldRegion.equatePositions(newRegion);
		// adjusts old node instance appropriately
		oldNode.setLength(newNode.getLength());
		oldNode.setEnded(newNode.isEnded());
		// we do have to set the parent document, since the oldNode's
		// were set to a temporary one, then newNode's have the
		// right one.
		oldNode.setParentDocument(newNode.getParentDocument());
		// if we're transforming a container region, we need to be sure to
		// transfer the new embedded regions, to the old parent
		// Note: if oldRegion hasEmbeddedRegions, then we know the
		// newRegion does too, since we got here because they were the
		// same type.
		if (isCollectionRegion(oldRegion)) { // ||
			// hasContainerRegions(oldRegion))
			// {
			transferEmbeddedRegions(oldNode, (ITextRegionContainer) oldRegion, (ITextRegionContainer) newRegion);
		}
		return oldRegion;
	}

	private IStructuredDocumentRegion switchNodeLists(CoreNodeList oldNodes, CoreNodeList newNodes) {
		IStructuredDocumentRegion result = splice(oldNodes, newNodes);
		// ensure that the old nodes hold no references to the existing model
		if (oldNodes.getLength() > 0) {
			IStructuredDocumentRegion firstItem = oldNodes.item(0);
			firstItem.setPrevious(null);
			IStructuredDocumentRegion lastItem = oldNodes.item(oldNodes.getLength() - 1);
			lastItem.setNext(null);
		}
		return result;
	}

	/**
	 * The purpose of this method is to "reuse" the old container region, when
	 * found to be same (so same instance doesn't change). The goal is to
	 * "transform" the old region, so its equivelent to the newly parsed one.
	 *  
	 */
	private void transferEmbeddedRegions(IStructuredDocumentRegion oldNode, ITextRegionContainer oldRegion, ITextRegionContainer newRegion) {
		// the oldRegion should already have the right parent, since
		// we got here because all's equivelent except the region
		// postions have changed.
		//oldRegion.setParent(newRegion.getParent());
		// but we should check if there's "nested" embedded regions, and if
		// so, we can just move them over. setting their parent as this old
		// region.
		ITextRegionList newRegionsToTransfer = newRegion.getRegions();
		oldRegion.setRegions(newRegionsToTransfer);
		Iterator newRegionsInOldOne = newRegionsToTransfer.iterator();
		while (newRegionsInOldOne.hasNext()) {
			ITextRegion newOne = (ITextRegion) newRegionsInOldOne.next();
			if (isCollectionRegion(newOne)) { // ||
				// hasContainerRegions(newOne)) {
				//((ITextRegionContainer) newOne).setParent(oldRegion);
				oldRegion.setRegions(newRegion.getRegions());
			}
		}
	}

	private void updateDownStreamRegions(IStructuredDocumentRegion flatNode, ITextRegion lastKnownRegion) {
		// so all regions after the last known region (last known to be ok)
		// have to have their start and end values adjusted.
		ITextRegionList regions = flatNode.getRegions();
		int listLength = regions.size();
		int startIndex = 0;
		// first, loop through to find where to start
		for (int i = 0; i < listLength; i++) {
			ITextRegion region = regions.get(i);
			if (region == lastKnownRegion) {
				startIndex = i;
				break;
			}
		}
		// now, beginning one past the last known one, loop
		// through to end of list, adjusting the start and end postions.
		startIndex++;
		for (int j = startIndex; j < listLength; j++) {
			ITextRegion region = regions.get(j);
			region.adjustStart(fLengthDifference);
		}
	}
}
