/*******************************************************************************
 * Copyright (c) 2005, 2012 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
 *     Anton Leherbauer (Wind River Systems)
 *     Bryan Wilkinson (QNX)
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.text.contentassist;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.ui.IEditorPart;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IASTCompletionNode;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexManager;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.PreferenceConstants;
import org.eclipse.cdt.ui.text.contentassist.ContentAssistInvocationContext;
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;

import org.eclipse.cdt.internal.ui.editor.CEditor;
import org.eclipse.cdt.internal.ui.text.CHeuristicScanner;
import org.eclipse.cdt.internal.ui.text.Symbols;


/**
 * Describes the context of a content assist invocation in a C/C++ editor.
 * <p>
 * Clients may use but not subclass this class.
 * </p>
 * 
 * @since 4.0
 */
public class CContentAssistInvocationContext extends ContentAssistInvocationContext implements ICEditorContentAssistInvocationContext {
	
	private final IEditorPart fEditor;
	private final boolean fIsCompletion;
	private final boolean fIsAutoActivated;
	
	private ITranslationUnit fTU= null;
	private boolean fTUComputed= false;
	private int fParseOffset= -1;
	private boolean fParseOffsetComputed= false;
	private IASTCompletionNode fCN= null;
	private boolean fCNComputed= false;
	private IIndex fIndex = null;
	private int fContextInfoPosition;
	
	/**
	 * Creates a new context.
	 * 
	 * @param viewer the viewer used by the editor
	 * @param offset the invocation offset
	 * @param editor the editor that content assist is invoked in
	 * @param isAutoActivated indicates whether content assist was auto-activated
	 */
	public CContentAssistInvocationContext(ITextViewer viewer, int offset, IEditorPart editor, boolean isCompletion, boolean isAutoActivated) {
		super(viewer, offset);
		Assert.isNotNull(editor);
		fEditor= editor;
		fIsCompletion= isCompletion;
		fIsAutoActivated= isAutoActivated;
	}
	
	/**
	 * Creates a new context.
	 * 
	 * @param unit the translation unit in <code>document</code>
	 */
	public CContentAssistInvocationContext(ITranslationUnit unit, boolean isCompletion) {
		super();
		fTU= unit;
		fTUComputed= true;
		fEditor= null;
		fIsCompletion= isCompletion;
		fIsAutoActivated= false;
	}
	
	/**
	 * Returns the translation unit that content assist is invoked in, <code>null</code> if there
	 * is none.
	 * 
	 * @return the translation unit that content assist is invoked in, possibly <code>null</code>
	 */
	@Override
	public ITranslationUnit getTranslationUnit() {
		if (!fTUComputed) {
			fTUComputed= true;
			fTU= CUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(fEditor.getEditorInput());
		}
		return fTU;
	}
	
	/**
	 * Returns the project of the translation unit that content assist is invoked in,
	 * <code>null</code> if none.
	 * 
	 * @return the current C project, possibly <code>null</code>
	 */
	@Override
	public ICProject getProject() {
		ITranslationUnit unit= getTranslationUnit();
		return unit == null ? null : unit.getCProject();
	}
		
	@Override
	public IASTCompletionNode getCompletionNode() {
		
		//for scalability
		if (fEditor != null && fEditor instanceof CEditor) {
			CEditor editor = (CEditor)fEditor;
			
			// check to make sure we should attempt local parsing completions... for remote projects
			// we should not do this
			if(!editor.shouldProcessLocalParsingCompletions()) {
				return null;
			}
			if (editor.isEnableScalablilityMode()) {
				if (editor.isParserBasedContentAssistDisabled()) {
					return null;
				}
				if (isAutoActivated() && editor.isContentAssistAutoActivartionDisabled()) {
					return null;
				}
			}
		}
		
		if (fCNComputed) return fCN;
		
		fCNComputed = true;

		int offset = getParseOffset();
		if (offset < 0) return null;
		
		ICProject proj= getProject();
		if (proj == null) return null;
		
		try {
			IIndexManager manager= CCorePlugin.getIndexManager();
			fIndex = manager.getIndex(proj, IIndexManager.ADD_DEPENDENCIES | IIndexManager.ADD_EXTENSION_FRAGMENTS_CONTENT_ASSIST);

			try {
				fIndex.acquireReadLock();
			} catch (InterruptedException e) {
				fIndex = null;
			}

			boolean parseNonIndexed= CUIPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.PREF_USE_STRUCTURAL_PARSE_MODE);
			int flags = parseNonIndexed ? ITranslationUnit.AST_SKIP_INDEXED_HEADERS : ITranslationUnit.AST_SKIP_ALL_HEADERS;
			flags |= ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT;
			
			fCN = fTU.getCompletionNode(fIndex, flags, offset);
		} catch (CoreException e) {
			CUIPlugin.log(e);
		}
		
		return fCN;
	}
	
	@Override
	public int getParseOffset() {
		if (!fParseOffsetComputed) {
			fParseOffsetComputed= true;
			fContextInfoPosition= guessContextInformationPosition();
			if (fIsCompletion) {
				fParseOffset = guessCompletionPosition(getInvocationOffset());
			} else if (fContextInfoPosition > 0) {
				fParseOffset = guessCompletionPosition(fContextInfoPosition);
			} else {
				fParseOffset = -1;
			}
		}
		
		return fParseOffset;
	}

	/**
	 * @return the offset where context information (parameter hints) starts.
	 */
	@Override
	public int getContextInformationOffset() {
		getParseOffset();
		return fContextInfoPosition;
	}
	
	/**
	 * Try to find a sensible completion position backwards in case the given offset
	 * is inside a function call argument list.
	 * 
	 * @param contextPosition  the starting position
	 * @return a sensible completion offset
	 */
	protected int guessCompletionPosition(int contextPosition) {
		CHeuristicScanner scanner= new CHeuristicScanner(getDocument());
		int bound= Math.max(-1, contextPosition - 200);
		
		int pos= scanner.findNonWhitespaceBackward(contextPosition - 1, bound);
		if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition;
		
		int token= scanner.previousToken(pos, bound);
		
		if (token == Symbols.TokenCOMMA) {
			pos= scanner.findOpeningPeer(pos, bound, '(', ')');
			if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition;
			
			token = scanner.previousToken(pos, bound);
		}
		
		if (token == Symbols.TokenLPAREN) {
			pos= scanner.findNonWhitespaceBackward(pos - 1, bound);
			if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition;
			
			token= scanner.previousToken(pos, bound);
			
			if (token == Symbols.TokenGREATERTHAN) {
				// skip template arguments
				pos= scanner.findOpeningPeer(pos - 1, '<', '>');
				if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition;
				pos= scanner.findNonWhitespaceBackward(pos - 1, bound);
				if (pos == CHeuristicScanner.NOT_FOUND) return contextPosition;
				token= scanner.previousToken(pos, bound);
			}
			
			if (token == Symbols.TokenIDENT) {
				return pos + 1;
			}
		}
		
		return contextPosition;
	}
	
	/**
	 * Try to find the smallest offset inside the opening parenthesis of a function call
	 * argument list.
	 * 
	 * @return the offset of the function call parenthesis plus 1 or -1 if the invocation
	 *     offset is not inside a function call (or similar)
	 */
	protected int guessContextInformationPosition() {
		final int contextPosition= getInvocationOffset();
		
		CHeuristicScanner scanner= new CHeuristicScanner(getDocument());
		int bound= Math.max(-1, contextPosition - 200);
		
		// try the innermost scope of parentheses that looks like a method call
		int pos= contextPosition - 1;
		do {
			int paren= scanner.findOpeningPeer(pos, bound, '(', ')');
			if (paren == CHeuristicScanner.NOT_FOUND)
				break;
			int token= scanner.previousToken(paren - 1, bound);
			// next token must be a method name (identifier) or the closing angle of a
			// constructor call of a template type.
			if (token == Symbols.TokenIDENT || token == Symbols.TokenGREATERTHAN) {
				return paren + 1;
			}
			pos= paren - 1;
		} while (true);
		
		return -1;
	}
	
	/**
	 * Get the editor content assist is invoked in.
	 * 
	 * @return the editor, may be <code>null</code>
	 */
	@Override
	public IEditorPart getEditor() {
		return fEditor;
	}

	@Override
	public boolean isContextInformationStyle() {
		return !fIsCompletion || (getParseOffset() != getInvocationOffset());
	}
	
	public boolean isAutoActivated() {
		return fIsAutoActivated;
	}

	@Override
	public void dispose() {
		if (fIndex != null) {
			fIndex.releaseReadLock();
		}
		super.dispose();
	}
}
