/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Genady Beryozkin, me@genady.org - initial API and implementation
 *     Fabio Zadrozny <fabiofz at gmail dot com> - [typing] HippieCompleteAction is slow  ( Alt+/ ) - https://bugs.eclipse.org/bugs/show_bug.cgi?id=270385
 *******************************************************************************/
package org.eclipse.ui.internal.texteditor;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;

import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IWorkbenchWindow;

import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;


/**
 * This class contains the hippie completion engine methods that actually
 * compute the possible completions.
 * <p>
 * This engine is used by the <code>org.eclipse.ui.texteditor.HippieCompleteAction</code>.
 * </p>
 *
 * TODO: Sort by editor type
 * TODO: Provide history option
 *
 * @since 3.1
 * @author Genady Beryozkin, me@genady.org
 */
public final class HippieCompletionEngine {

	/**
	 * Regular expression that is used to find words.
	 */
	// unicode identifier part
//	private static final String COMPLETION_WORD_REGEX= "[\\p{L}[\\p{Mn}[\\p{Pc}[\\p{Nd}[\\p{Nl}]]]]]+"; //$NON-NLS-1$
	// java identifier part (unicode id part + currency symbols)
	private static final String COMPLETION_WORD_REGEX= "[\\p{L}[\\p{Mn}[\\p{Pc}[\\p{Nd}[\\p{Nl}[\\p{Sc}]]]]]]+"; //$NON-NLS-1$
	/**
	 * The pre-compiled word pattern.
	 *
	 * @since 3.2
	 */
	private static final Pattern COMPLETION_WORD_PATTERN= Pattern.compile(COMPLETION_WORD_REGEX);

	/**
	 * Word boundary pattern that does not allow searching at the beginning of the document.
	 *
	 * @since 3.2
	 */
	private static final String NON_EMPTY_COMPLETION_BOUNDARY= "[\\s\\p{Z}[\\p{P}&&[\\P{Pc}]][\\p{S}&&[\\P{Sc}]]]+"; //$NON-NLS-1$

	/**
	 * The word boundary pattern string.
	 *
	 * @since 3.2
	 */
	private static final String COMPLETION_BOUNDARY= "(^|" + NON_EMPTY_COMPLETION_BOUNDARY + ")";  //$NON-NLS-1$ //$NON-NLS-2$
	// with a 1.5 JRE, you can do this:
//	private static final String COMPLETION_WORD_REGEX= "\\p{javaUnicodeIdentifierPart}+"; //$NON-NLS-1$
//	private static final String COMPLETION_WORD_REGEX= "\\p{javaJavaIdentifierPart}+"; //$NON-NLS-1$

	/**
	 * Is completion case sensitive? Even if set to <code>false</code>, the
	 * case of the prefix won't be changed.
	 */
	private static final boolean CASE_SENSITIVE= true;

	/**
	 * Creates a new engine.
	 */
	public HippieCompletionEngine() {
	}

	/*
	 * Copied from {@link FindReplaceDocumentAdapter#asRegPattern(java.lang.String)}.
	 */
	/**
	 * Converts a non-regex string to a pattern that can be used with the regex
	 * search engine.
	 *
	 * @param string the non-regex pattern
	 * @return the string converted to a regex pattern
	 */
	private String asRegPattern(CharSequence string) {
		StringBuilder out= new StringBuilder(string.length());
		boolean quoting= false;

		for (int i= 0, length= string.length(); i < length; i++) {
			char ch= string.charAt(i);
			if (ch == '\\') {
				if (quoting) {
					out.append("\\E"); //$NON-NLS-1$
					quoting= false;
				}
				out.append("\\\\"); //$NON-NLS-1$
				continue;
			}
			if (!quoting) {
				out.append("\\Q"); //$NON-NLS-1$
				quoting= true;
			}
			out.append(ch);
		}
		if (quoting)
			out.append("\\E"); //$NON-NLS-1$

		return out.toString();
	}

	/**
	 * Return the list of completion suggestions that correspond to the
	 * provided prefix.
	 *
	 * @param document the document to be scanned
	 * @param prefix the prefix to search for
	 * @param firstPosition the initial position in the document that
	 *        the search will start from. In order to search from the
	 *        beginning of the document use <code>firstPosition=0</code>.
	 * @param currentWordLast if <code>true</code> the word at caret position
	 * 		  should be that last completion. <code>true</code> is good
	 * 		  for searching in the currently open document and <code>false</code>
	 * 		  is good for searching in other documents.
	 * @return a {@link List} of possible completions (as {@link String}s),
	 *         excluding the common prefix
	 * @throws BadLocationException if there is some error scanning the
	 *         document.
	 */
	public List<String> getCompletionsForward(IDocument document, CharSequence prefix,
			int firstPosition, boolean currentWordLast) throws BadLocationException {
		ArrayList<String> res= new ArrayList<>();
		for (Iterator<String> it= getForwardIterator(document, prefix, firstPosition, currentWordLast); it.hasNext();) {
			res.add(it.next());
		}
		return res;
	}

	/**
	 * Search for possible completions in the backward direction. If there
	 * is a possible completion that begins before <code>firstPosition</code>
	 * but ends after that position, it will not be included in the results.
	 *
	 * @param document the document to be scanned
	 * @param prefix the completion prefix
	 * @param firstPosition the caret position
	 * @return a {@link List} of possible completions ({@link String}s)
	 *         from the caret position to the beginning of the document.
	 *         The empty suggestion is not included in the results.
	 * @throws BadLocationException if any error occurs
	 */
	public List<String> getCompletionsBackwards(IDocument document, CharSequence prefix, int firstPosition) throws BadLocationException {
		ArrayList<String> res= new ArrayList<>();
		for (Iterator<String> it= getBackwardIterator(document, prefix, firstPosition); it.hasNext();) {
			res.add(it.next());
		}
		return res;
	}

	/**
	 * Returns the text between the provided position and the preceding word boundary.
	 *
	 * @param doc the document that will be scanned.
	 * @param pos the caret position.
	 * @return the text if found, or null.
	 * @throws BadLocationException if an error occurs.
	 * @since 3.2
	 */
	public String getPrefixString(IDocument doc, int pos) throws BadLocationException {
		Matcher m= COMPLETION_WORD_PATTERN.matcher(""); //$NON-NLS-1$
		int prevNonAlpha= pos;
		while (prevNonAlpha > 0) {
			m.reset(doc.get(prevNonAlpha-1, pos - prevNonAlpha + 1));
			if (!m.matches()) {
				break;
			}
			prevNonAlpha--;
		}
		if (prevNonAlpha != pos) {
			return doc.get(prevNonAlpha, pos - prevNonAlpha);
		}
		return null;
	}

	/**
	 * Remove duplicate suggestions (excluding the prefix), leaving the closest
	 * to list head.
	 *
	 * @param suggestions a list of suggestions ({@link String}).
	 * @return a list of unique completion suggestions.
	 */
	public List<String> makeUnique(List<String> suggestions) {
		HashSet<String> seenAlready= new HashSet<>();
		ArrayList<String> uniqueSuggestions= new ArrayList<>();

		for (Iterator<String> i= suggestions.iterator(); i.hasNext();) {
			String suggestion= i.next();
			if (!seenAlready.contains(suggestion)) {
				seenAlready.add(suggestion);
				uniqueSuggestions.add(suggestion);
			}
		}
		return uniqueSuggestions;
	}



	/**
	 * Calculates the documents to be searched. Note that the first returned document is always from
	 * the current editor and if we have no current editor, an empty list is returned even if there
	 * are other documents available.
	 *
	 * @param currentTextEditor this is the currently opened text editor.
	 * @return A List of IDocument with the opened documents so that the first document in that list
	 *         is always the current document.
	 * @since 3.6
	 */
	public static List<IDocument> computeDocuments(ITextEditor currentTextEditor) {
		ArrayList<IDocument> documentsForSearch= new ArrayList<>();
		if (currentTextEditor == null) {
			return documentsForSearch;
		}

		IDocumentProvider provider= currentTextEditor.getDocumentProvider();
		if (provider == null) {
			return documentsForSearch;
		}

		IDocument currentDocument= provider.getDocument(currentTextEditor.getEditorInput());
		if(currentDocument == null){
			return documentsForSearch;
		}

		List<IDocument> computedDocuments= new ArrayList<>();
		IWorkbenchWindow window= currentTextEditor.getSite().getWorkbenchWindow();
		IEditorReference editorsArray[]= window.getActivePage().getEditorReferences();

		for (int i= 0; i < editorsArray.length; i++) {
			IEditorPart realEditor= editorsArray[i].getEditor(false);
			if (realEditor instanceof ITextEditor && !realEditor.equals(currentTextEditor)) {
				ITextEditor textEditor= (ITextEditor)realEditor;
				provider= textEditor.getDocumentProvider();
				if (provider == null) {
					continue;
				}
				IDocument doc= provider.getDocument(textEditor.getEditorInput());
				if (doc == null) {
					continue;
				}
				computedDocuments.add(doc);
			}
		}

		//The first is always the one related to the passed currentTextEditor.
		computedDocuments.add(0, currentDocument);
		return computedDocuments;
	}


	/**
	 * Provides an iterator that will get the completions that start with the passed prefix after
	 * the passed position (forward until the end of the document).
	 *
	 * @param document the document to be scanned
	 * @param prefix the prefix to search for
	 * @param firstPosition the initial position in the document that the search will start from. In
	 *            order to search from the beginning of the document use
	 *            <code>firstPosition=0</code>.
	 * @param currentWordLast if <code>true</code> the word at caret position should be that last
	 *            completion. <code>true</code> is good for searching in the currently open document
	 *            and <code>false</code> is good for searching in other documents.
	 * @return Iterator (for Strings) that will get the completions forward from the passed
	 *         position.
	 *
	 * @since 3.6
	 */
	public Iterator<String> getForwardIterator(IDocument document, CharSequence prefix, int firstPosition, boolean currentWordLast) {
		return new HippieCompletionForwardIterator(document, prefix, firstPosition, currentWordLast);
	}

	/**
	 * Provides an iterator that will get the completions that start with the passed prefix before
	 * the passed position (backwards until the start of the document).
	 *
	 * @param document the document to be scanned
	 * @param prefix the prefix to search for
	 * @param firstPosition the initial position in the document that the search will start from. In
	 *            order to search from the end of the document use
	 *            <code>firstPosition=document.getLength()</code>.
	 * @return Iterator that will get the completions backward from the passed position.
	 *
	 * @since 3.6
	 */
	public Iterator<String> getBackwardIterator(IDocument document, CharSequence prefix, int firstPosition) {
		return new HippieCompletionBackwardIterator(document, prefix, firstPosition);
	}

	/**
	 * Provides an iterator that will get the completions for all the documents received, starting
	 * at the "document" passed (first going backward and then forward from the position passed) and
	 * later going forward through each of the "otherDocuments".
	 *
	 * @param document the document to be scanned
	 * @param otherDocuments the additional documents to be scanned
	 * @param prefix the prefix to search for
	 * @param firstPosition the initial position in the document that the search will start from.
	 * @return Iterator that will first get the completions backward from the document passed, then
	 *         forward in that same document and when that is finished it will get it forward for
	 *         the other documents (in the same sequence the documents are available).
	 *
	 * @since 3.6
	 */
	public Iterator<String> getMultipleDocumentsIterator(IDocument document, List<IDocument> otherDocuments, CharSequence prefix, int firstPosition) {
		return new MultipleDocumentsIterator(document, otherDocuments, prefix, firstPosition);
	}



	/**
	 * Class that keeps the state while iterating the suggestions
	 *
	 * @since 3.6
	 */
	private final class MultipleDocumentsIterator implements Iterator<String> {

		/**
		 * This is the next token to be returned (when null, no more tokens should be returned)
		 */
		private String fNext;

		/**
		 * -1 means that we still haven't checked the current do completions Any other number means
		 * that we'll get the completions for some other editor.
		 */
		private int fCurrLocation= -1;

		/** These are the suggestions which we already loaded. */
		private final List<String> fSuggestions;

		/** This marks the current suggestion to be returned */
		private int fCurrSuggestion= 0;

		/** This is the prefix that should be searched */
		private final CharSequence fPrefix;

		/** The list of IDocuments that we should search */
		private final List<IDocument> fOtherDocuments;

		/**
		 * The document that's currently opened (that's the 1st we should look and we should 1st
		 * search backwards from the current offset and later forwards)
		 */
		private final IDocument fOpenDocument;

		/** The current offset in the opened document */
		private final int fSelectionOffset;

		/** Indicates whether we already added the empty completion. */
		private boolean fAddedEmpty= false;

		/** The 'current' forward iterator. */
		private Iterator<String> fCompletionsForwardIterator;

		/** The 'current' backward iterator. */
		private Iterator<String> fCompletionsBackwardIterator;

		private MultipleDocumentsIterator(IDocument openDocument, List<IDocument> otherDocuments,
				CharSequence prefix, int selectionOffset) {
			this.fPrefix= prefix;
			this.fSuggestions= new ArrayList<>();
			this.fOtherDocuments= otherDocuments;
			this.fSelectionOffset= selectionOffset;
			this.fOpenDocument= openDocument;
			calculateNext();
		}


		/**
		 * This method calculates the next token to be returned (so, after creating the class or
		 * after calling next(), this function must be called).
		 *
		 * It'll check which document should be used and will get the completions on that document
		 * until some completion is found.
		 *
		 * An empty completion is always added at the end.
		 *
		 * After the empty completion, the next is set to null.
		 */
		private void calculateNext() {
			if (fCurrLocation == -1) {
				fCompletionsBackwardIterator= getBackwardIterator(
						fOpenDocument, fPrefix, fSelectionOffset);

				fCompletionsForwardIterator= getForwardIterator(
						fOpenDocument, fPrefix, (fSelectionOffset - fPrefix.length()), true);
				fCurrLocation++;
			}
			if (checkNext()) {
				return;
			}


			while (fCurrLocation < this.fOtherDocuments.size()) {
				fCompletionsForwardIterator= getForwardIterator(
						(this.fOtherDocuments.get(fCurrLocation)), fPrefix, 0, false);
				fCurrLocation++;
				if (checkNext()) {
					return;
				}
			}

			// add the empty suggestion (last one)
			if (!fAddedEmpty) {
				fSuggestions.add(""); //$NON-NLS-1$
				fAddedEmpty= true;
			}
			checkNext();
		}

		/**
		 * @return true if a completion was found and false if it couldn't be found -- in which case
		 *         the next is set to null.
		 */
		private boolean checkNext() {
			if (fCompletionsBackwardIterator != null) {
				if (fCompletionsBackwardIterator.hasNext()) {
					fSuggestions.add(fCompletionsBackwardIterator.next());
				} else {
					fCompletionsBackwardIterator= null;
				}
			}
			if (fCompletionsBackwardIterator == null) {
				//only get if backward completions are consumed
				if (fCompletionsForwardIterator != null && fCompletionsForwardIterator.hasNext()) {
					fSuggestions.add(fCompletionsForwardIterator.next());
				}
			}

			if (fSuggestions.size() > fCurrSuggestion) {
				fNext= fSuggestions.get(fCurrSuggestion);
				fCurrSuggestion++;
				return true;
			}
			fNext= null;
			return false;
		}

		/**
		 * We always calculate the next to see if it's available.
		 *
		 * @return <code>true</code> if the next token to be returned is not null (we always
		 *         pre-calculate things)
		 */
		@Override
		public boolean hasNext() {
			return fNext != null;
		}


		/**
		 * @return the next suggestion
		 */
		@Override
		public String next() {
			if (fNext == null) {
				throw new NoSuchElementException("No more elements to iterate"); //$NON-NLS-1$
			}
			String ret= fNext;
			calculateNext();
			return ret;
		}

		/**
		 * Not supported!
		 *
		 * @throws UnsupportedOperationException always.
		 */
		@Override
		public void remove() {
			throw new UnsupportedOperationException("Not supported"); //$NON-NLS-1$

		}

	}

	/**
	 * Base class for Iterator that gets the word completions in a document, and returns them one by
	 * one (lazily gotten).
	 *
	 * @since 3.6
	 */
	private abstract class HippieCompletionIterator implements Iterator<String> {

		/** The document to be scanned */
		protected IDocument fDocument;

		/** The prefix to search for */
		protected CharSequence fPrefix;

		/**
		 * The initial position in the document that the search will start from. In order to search
		 * from the beginning of the document use <code>firstPosition=0</code>.
		 */
		protected int fFirstPosition;

		/** Determines if we have a next element to be returned. */
		protected boolean fHasNext;

		/** The next element to be returned */
		protected String fNext;

		/** The current state for the iterator */
		protected int fCurrentState= 0;

		/** The class that'll do the search */
		protected FindReplaceDocumentAdapter fSearcher;

		/** Pattern to be used -- search only at word boundaries */
		protected String fSearchPattern;

		/** The next place to search for */
		protected int fNextPos;


		/**
		 * Constructor
		 *
		 * @param document the document to be scanned
		 * @param prefix the prefix to search for
		 * @param firstPosition the initial position in the document that the search will start
		 *            from. In order to search from the beginning of the document use
		 *            <code>firstPosition=0</code>.
		 */
		public HippieCompletionIterator(IDocument document, CharSequence prefix, int firstPosition) {
			this.fDocument= document;
			this.fPrefix= prefix;
			this.fFirstPosition= firstPosition;
		}

		/**
		 * Must be called to calculate the first completion (subclasses must explicitly call it when
		 * properly initialized).
		 */
		protected void calculateFirst() {
			try {
				calculateNext();
			} catch (BadLocationException e) {
				log(e);
				fHasNext= false;
				fNext= null;
			}
		}

		@Override
		public boolean hasNext() {
			return fHasNext;
		}

		@Override
		public String next() {
			if (!fHasNext) {
				throw new NoSuchElementException();
			}
			String ret= fNext;
			try {
				calculateNext();
			} catch (BadLocationException e) {
				log(e);
				fHasNext= false;
				fNext= null;
			}
			return ret;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

		/**
		 * Subclasses must override to calculates whether we have a next element to be returned and
		 * which element it is (set fHasNext and fNext).
		 *
		 * @throws BadLocationException if we're at an invalid position in the document.
		 */
		protected abstract void calculateNext() throws BadLocationException;
	}



	/**
	 * Iterator that gets the word completions in a document, and returns them one by one (lazily
	 * gotten) from the current position.
	 *
	 * @since 3.6
	 */
	private class HippieCompletionForwardIterator extends HippieCompletionIterator {

		/**
		 * If <code>true</code> the word at caret position should be that last completion.
		 * <code>true</code> is good for searching in the currently open document and
		 * <code>false</code> is good for searching in other documents.
		 */
		private boolean fCurrentWordLast;


		/** The completion for the current word -- fix bug 132533 */
		private String fCurrentWordCompletion= null;


		private HippieCompletionForwardIterator(IDocument document, CharSequence prefix, int firstPosition, boolean currentWordLast) {
			super(document, prefix, firstPosition);
			this.fCurrentWordLast= currentWordLast;
			calculateFirst();
		}

		@Override
		protected void calculateNext() throws BadLocationException {
			if (fCurrentState == 0) {
				if (fFirstPosition == fDocument.getLength()) {
					this.fHasNext= false;
					return;
				}
				fSearcher= new FindReplaceDocumentAdapter(fDocument);

				// unless we are at the beginning of the document, the completion boundary
				// matches one character. It is enough to move just one character backwards
				// because the boundary pattern has the (....)+ form.
				// see HippieCompletionTest#testForwardSearch().
				if (fFirstPosition > 0) {
					fFirstPosition--;
					// empty spacing is not permitted now.
					fSearchPattern= NON_EMPTY_COMPLETION_BOUNDARY + asRegPattern(fPrefix);
				} else {
					fSearchPattern= COMPLETION_BOUNDARY + asRegPattern(fPrefix);
				}

				fNextPos= fFirstPosition;
				fCurrentState= 1;
			}

			if (fCurrentState == 1) {
				fHasNext= false;
				IRegion reg= fSearcher.find(fNextPos, fSearchPattern, true, CASE_SENSITIVE, false, true);
				while (reg != null) {
					IRegion word= checkRegion(reg);
					fNextPos= word.getOffset() + word.getLength();
					if (fNextPos >= fDocument.getLength()) {
						fCurrentState= 2;
						if (fHasNext) {
							return;
						}
						break;
					} else {
						if (fHasNext) {
							return;
						}
						reg= fSearcher.find(fNextPos, fSearchPattern, true, CASE_SENSITIVE, false, true);
					}
				}
				fCurrentState= 2;
			}

			if (fCurrentState == 2) {
				fCurrentState= 3;
				// the word at caret position goes last (bug 132533).
				if (fCurrentWordCompletion != null) {
					fNext= fCurrentWordCompletion;
					fHasNext= true;
					return;
				}
			}

			fNext= null;
			fHasNext= false;
			return;
		}

		/**
		 * Checks the given region for a word to be returned in this iterator.
		 *
		 * @param reg the region to check
		 * @return the word region.
		 * @throws BadLocationException if we're at an invalid position in the document.
		 */
		private IRegion checkRegion(IRegion reg) throws BadLocationException {
			// since the boundary may be of nonzero length
			int wordSearchPos= reg.getOffset() + reg.getLength() - fPrefix.length();
			// try to complete to a word. case is irrelevant here.
			IRegion word= fSearcher.find(wordSearchPos, COMPLETION_WORD_REGEX, true, true, false, true);
			if (word.getLength() > fPrefix.length()) { // empty suggestion will be added later
				String wholeWord= fDocument.get(word.getOffset(), word.getLength());
				String completion= wholeWord.substring(fPrefix.length());
				if (fCurrentWordLast && reg.getOffset() == fFirstPosition) { // we got the word at caret as completion
					if (fCurrentWordCompletion == null) {
						fCurrentWordCompletion= completion; // add it as the last word.
					}
				} else {
					fNext= completion;
					fHasNext= true;
				}
			}
			return word;
		}
	}



	/**
	 * Iterator that gets the word completions in a document, and returns them one by one (lazily
	 * gotten) backward from the current position.
	 *
	 * @since 3.6
	 */
	private class HippieCompletionBackwardIterator extends HippieCompletionIterator {

		/** Last position searched **/
		private int fLastSearchPos= -1;

		private HippieCompletionBackwardIterator(IDocument document, CharSequence prefix, int firstPosition) {
			super(document, prefix, firstPosition);
			calculateFirst();
		}

		@Override
		protected void calculateNext() throws BadLocationException {
			if (fCurrentState == 0) {
				fCurrentState= 1;
				// FindReplaceDocumentAdapter expects the start offset to be before the
				// actual caret position, probably for compatibility with forward search.
				if (fFirstPosition <= 1) {
					this.fNext= null;
					this.fHasNext= false;
					return;
				}
				fSearcher= new FindReplaceDocumentAdapter(fDocument);

				// search only at word boundaries
				fSearchPattern= COMPLETION_BOUNDARY + asRegPattern(fPrefix);

				int length= fDocument.getLength();
				fNextPos= fFirstPosition;
				if (fNextPos >= length) {
					fNextPos= length - 1;
				}
			}
			while (true) {
				if (fNextPos <= 0) {
					this.fNext= null;
					this.fHasNext= false;
					return;
				}

				Assert.isTrue(fLastSearchPos != fNextPos, "Position did not change in loop (this would lead to recursion -- and should never happen)."); //$NON-NLS-1$

				fLastSearchPos= fNextPos;
				IRegion reg= fSearcher.find(fNextPos, fSearchPattern, false, CASE_SENSITIVE, false, true);
				if (reg == null) {
					this.fNext= null;
					this.fHasNext= false;
					return;
				}

				// since the boundary may be of nonzero length
				int wordSearchPos= reg.getOffset() + reg.getLength() - fPrefix.length();
				// try to complete to a word. case is of no matter here
				IRegion word= fSearcher.find(wordSearchPos, COMPLETION_WORD_REGEX, true, true, false, true);
				fNextPos= word.getOffset() - 1;
				if (word.getOffset() + word.getLength() > fFirstPosition) {
					continue;
				}
				if (word.getLength() > fPrefix.length()) { // empty suggestion will be added later
					String found= fDocument.get(word.getOffset(), word.getLength());
					this.fHasNext= true;
					this.fNext= found.substring(fPrefix.length());
					return;
				}
			}

			//Note: unreachable section
		}

	}

	/**
	 * Logs the exception.
	 *
	 * @param e the exception
	 *
	 * @since 3.6
	 */
	private void log(BadLocationException e) {
		String msg= e.getLocalizedMessage();
		if (msg == null)
			msg= "unable to access the document"; //$NON-NLS-1$
		TextEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, msg, e));
	}

}
