/*******************************************************************************
 *  Copyright (c) 2000, 2009 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
 * 	   Sergey Prigogin (Google)
 *     Andrew Ferguson (Symbian)
 *******************************************************************************/

package org.eclipse.cdt.internal.ui.text.spelling;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.TypedRegion;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.texteditor.spelling.ISpellingProblemCollector;

import org.eclipse.cdt.core.dom.ast.gnu.cpp.GPPLanguage;
import org.eclipse.cdt.ui.text.ICPartitions;
import org.eclipse.cdt.ui.text.ITokenStore;
import org.eclipse.cdt.ui.text.ITokenStoreFactory;
import org.eclipse.cdt.ui.text.doctools.IDocCommentDictionary;
import org.eclipse.cdt.ui.text.doctools.IDocCommentOwner;
import org.eclipse.cdt.ui.text.doctools.IDocCommentSimpleDictionary;

import org.eclipse.cdt.internal.ui.text.CPreprocessorScanner;
import org.eclipse.cdt.internal.ui.text.FastCPartitioner;
import org.eclipse.cdt.internal.ui.text.doctools.DocCommentSpellDictionary;
import org.eclipse.cdt.internal.ui.text.spelling.engine.ISpellChecker;
import org.eclipse.cdt.internal.ui.text.spelling.engine.ISpellDictionary;
import org.eclipse.cdt.internal.ui.text.spelling.engine.ISpellEventListener;

/**
 * C/C++ spelling engine
 */
public class CSpellingEngine extends SpellingEngine {

	/**
	 * A dummy token store for use with a token scanner.
	 */
	private static class SimpleTokenStore implements ITokenStore {
		public void ensureTokensInitialised() {
		}
		public IPreferenceStore getPreferenceStore() {
			return null;
		}
		public IToken getToken(String property) {
			return new Token(property);
		}
		public void adaptToPreferenceChange(PropertyChangeEvent event) {
		}
		public boolean affectsBehavior(PropertyChangeEvent event) {
			return false;
		}
	}

	/*
	 * @see org.eclipse.cdt.internal.ui.text.spelling.SpellingEngine#check(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IRegion[], org.eclipse.cdt.internal.ui.text.spelling.engine.ISpellChecker, org.eclipse.ui.texteditor.spelling.ISpellingProblemCollector, org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	protected void check(IDocument document, IRegion[] regions, ISpellChecker checker, ISpellingProblemCollector collector, IProgressMonitor monitor) {
		ISpellEventListener listener= new SpellEventListener(collector, document);
		boolean isIgnoringStringLiterals= SpellingPreferences.isIgnoreStringLiterals();
		
		ISpellDictionary toRemove= null;
		try {
			checker.addListener(listener);
			
			IDocCommentOwner owner= null;
			if (document instanceof IDocumentExtension3) {
				IDocumentPartitioner partitioner= ((IDocumentExtension3)document).getDocumentPartitioner(ICPartitions.C_PARTITIONING);
					if (partitioner instanceof FastCPartitioner) {
						owner= ((FastCPartitioner)partitioner).getDocCommentOwner();
					}
			}
			
			try {
				for (int i= 0; i < regions.length; i++) {
					IRegion region= regions[i];
					ITypedRegion[] partitions= TextUtilities.computePartitioning(document,
							ICPartitions.C_PARTITIONING, region.getOffset(), region.getLength(), false);
					for (int index= 0; index < partitions.length; index++) {
						if (monitor != null && monitor.isCanceled())
							return;

						ITypedRegion partition= partitions[index];
						final String type= partition.getType();
						
						if (isIgnoringStringLiterals && type.equals(ICPartitions.C_STRING))
							continue;

						if (owner!=null) {
							IDocCommentDictionary dict= null;
							
							if (type.equals(ICPartitions.C_MULTI_LINE_DOC_COMMENT)) {
								dict= owner.getMultilineConfiguration().getSpellingDictionary();
							} else if (type.equals(ICPartitions.C_SINGLE_LINE_DOC_COMMENT)) {
								dict= owner.getSinglelineConfiguration().getSpellingDictionary();
							}
							
							if (dict instanceof IDocCommentSimpleDictionary) {
								ISpellDictionary sd= new DocCommentSpellDictionary((IDocCommentSimpleDictionary)dict);
								checker.addDictionary(sd);
								toRemove= sd;
							}
						}
						
						if (type.equals(ICPartitions.C_PREPROCESSOR)) {
							RuleBasedScanner scanner = new CPreprocessorScanner(new ITokenStoreFactory() {
								public ITokenStore createTokenStore(String[] propertyColorNames) {
									return new SimpleTokenStore();
								}}, GPPLanguage.getDefault());
							scanner.setRange(document, partition.getOffset(), partition.getLength());
							int firstTokenOffset = -1;
							int firstTokenLength = -1;
							while (true) {
								IToken token = scanner.nextToken();
								if (token.isEOF()) {
									break;
								}
								if (token.isOther()) {
									int offset = scanner.getTokenOffset();
									int length = scanner.getTokenLength();
									if (firstTokenOffset < 0) {
										firstTokenOffset = offset;
										firstTokenLength = length;
									}
									String subregionType = null;
									char c = document.getChar(offset);
									if (c == '"') {
										if (!isIgnoringStringLiterals &&
												!isIncludeDirective(document, firstTokenOffset, firstTokenLength)) {
											subregionType = ICPartitions.C_STRING;
										}
									} else if (c == '/' && length >= 2) {
										c = document.getChar(offset + 1);
										if (c == '/') {
											subregionType = ICPartitions.C_SINGLE_LINE_COMMENT;
										} else if (c == '*') {
											subregionType = ICPartitions.C_MULTI_LINE_COMMENT;
										}
									}
									if (subregionType != null) {
										TypedRegion subregion = new TypedRegion(offset, length, subregionType);
										checker.execute(new SpellCheckIterator(document, subregion,
												checker.getLocale()));
									}
								}
							}
						} else if (!type.equals(IDocument.DEFAULT_CONTENT_TYPE) &&
								!type.equals(ICPartitions.C_CHARACTER)) {
							checker.execute(new SpellCheckIterator(document, partition, checker.getLocale()));
						}
						
						if (toRemove != null) {
							checker.removeDictionary(toRemove);
							toRemove= null;
						}
					}
				}
			} catch (BadLocationException x) {
				// Ignore BadLocationException since although it does happen from time to time,
				// there seems to be not much harm from it.
//				CUIPlugin.log(x);
			}
		} finally {
			if (toRemove != null)
				checker.removeDictionary(toRemove);
			checker.removeListener(listener);
		}
	}

	/**
	 * Returns <code>true</code> if the token at the given offset and length is an include directive. 
	 * @param document
	 * @param offset
	 * @param length
	 * @return
	 * @throws BadLocationException
	 */
	private boolean isIncludeDirective(IDocument document, int offset, int length) throws BadLocationException {
		while (length > 0) {
			char c = document.getChar(offset);
			if (c == '#' || Character.isWhitespace(c)) {
				offset++;
				length--;
			} else if (c == 'i') {
				return document.get(offset, length).startsWith("include"); //$NON-NLS-1$
			} else {
				break;
			}
		}
		return false;
	}
}
