/*******************************************************************************
 * Copyright (c) 2000, 2014 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
 *     Lars Vogel  <lars.vogel@gmail.com> - [templates][content assist] Ctrl+Space without any starting letter shows to no templates - https://bugs.eclipse.org/406463
 *     Lukas Hanke <hanke@yatta.de> - [templates][content assist] Content assist for 'for' loop should suggest member variables - https://bugs.eclipse.org/117215
 *******************************************************************************/
package _org.eclipse.jdt.internal.corext.template.java;

import melnorme.lang.ide.core.ISourceFile;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContextType;


/**
 * A context for Java source.
 */
public class JavaContext extends CompilationUnitContext {

//	/** A code completion requester for guessing local variable names. */
//	private CompilationUnitCompletion fCompletion;
	/**
	 * The list of used local names.
	 * @since 3.3
	 */
//	private Set<String> fUsedNames= new HashSet<String>();
//	private Map<String, MultiVariable> fVariables= new HashMap<String, MultiVariable>();
//	private ImportRewrite fImportRewrite;

//	private Set<String> fCompatibleContextTypeIds;

	/**
	 * Creates a java template context.
	 *
	 * @param type   the context type.
	 * @param document the document.
	 * @param completionOffset the completion offset within the document.
	 * @param completionLength the completion length.
	 * @param compilationUnit the compilation unit (may be <code>null</code>).
	 */
	public JavaContext(TemplateContextType type, IDocument document, int completionOffset, int completionLength, ISourceFile compilationUnit) {
		super(type, document, completionOffset, completionLength, compilationUnit);
	}

	/**
	 * Creates a java template context.
	 *
	 * @param type   the context type.
	 * @param document the document.
	 * @param completionPosition the position defining the completion offset and length
	 * @param compilationUnit the compilation unit (may be <code>null</code>).
	 * @since 3.2
	 */
	public JavaContext(TemplateContextType type, IDocument document, Position completionPosition, ISourceFile compilationUnit) {
		super(type, document, completionPosition, compilationUnit);
	}

//	/**
//	 * Adds a context type that is also compatible. That means the context can also process templates of that context type.
//	 *
//	 * @param contextTypeId the context type to accept
//	 */
//	public void addCompatibleContextType(String contextTypeId) {
//		if (fCompatibleContextTypeIds == null)
//			fCompatibleContextTypeIds= new HashSet<String>();
//		fCompatibleContextTypeIds.add(contextTypeId);
//	}


//	/**
//	 * Returns the indentation level at the position of code completion.
//	 *
//	 * @return the indentation level at the position of the code completion
//	 */
//	private int getIndentation() {
//		int start= getStart();
//		IDocument document= getDocument();
//		try {
//			IRegion region= document.getLineInformationOfOffset(start);
//			String lineContent= document.get(region.getOffset(), region.getLength());
//			IJavaProject project= getJavaProject();
//			return Strings.computeIndentUnits(lineContent, project);
//		} catch (BadLocationException e) {
//			return 0;
//		}
//	}
//
//	/*
//	 * @see TemplateContext#evaluate(Template template)
//	 */
//	@Override
//	public TemplateBuffer evaluate(Template template) throws BadLocationException, TemplateException {
//		clear();
//
//		if (!canEvaluate(template))
//			throw new TemplateException(JavaTemplateMessages.Context_error_cannot_evaluate);
//
//		TemplateTranslator translator= new TemplateTranslator() {
//			@Override
//			protected TemplateVariable createVariable(TemplateVariableType type, String name, int[] offsets) {
////				TemplateVariableResolver resolver= getContextType().getResolver(type.getName());
////				return resolver.createVariable();
//
//				MultiVariable variable= new JavaVariable(type, name, offsets);
//				fVariables.put(name, variable);
//				return variable;
//			}
//		};
//		TemplateBuffer buffer= translator.translate(template);
//
//		getContextType().resolve(buffer, this);
//
//		rewriteImports();
//
//		IPreferenceStore prefs= JavaPlugin.getDefault().getPreferenceStore();
//		boolean useCodeFormatter= prefs.getBoolean(PreferenceConstants.TEMPLATES_USE_CODEFORMATTER);
//
//		IJavaProject project= getJavaProject();
//		JavaFormatter formatter= new JavaFormatter(TextUtilities.getDefaultLineDelimiter(getDocument()), getIndentation(), useCodeFormatter, project);
//		formatter.format(buffer, this);
//
//		clear();
//
//		return buffer;
//	}

//	private void clear() {
//		fUsedNames.clear();
//		fImportRewrite= null;
//	}

	/*
	 * @see TemplateContext#canEvaluate(Template templates)
	 */
	@Override
	public boolean canEvaluate(Template template) {
		if (!hasCompatibleContextType(template))
			return false;

		if (fForceEvaluation)
			return true;

		String key= getKey();
		return (key.length() > 0 || !isAfterDot()) && template.getName().toLowerCase().startsWith(key.toLowerCase());
	}

	private boolean isAfterDot() {
		try {
			IDocument document= getDocument();
			int offset= getCompletionOffset();
			return document.get(offset - 1, 1).charAt(0) == '.';
		} catch (BadLocationException e) {
			return false;
		}
	}

	private boolean hasCompatibleContextType(Template template) {
		String key= getKey();
		if (template.matches(key, getContextType().getId()))
			return true;

//		if (fCompatibleContextTypeIds == null)
//			return false;
//
//		Iterator<String> iter= fCompatibleContextTypeIds.iterator();
//		while (iter.hasNext()) {
//			if (template.matches(key, iter.next()))
//				return true;
//		}
//
		return false;
	}

	/*
	 * @see DocumentTemplateContext#getCompletionPosition();
	 */
	@Override
	public int getStart() {

		if (fIsManaged && getCompletionLength() > 0)
			return super.getStart();

		try {
			IDocument document = getDocument();

			int start = getCompletionOffset();
			int end = getCompletionOffset() + getCompletionLength();

			while (start != 0 && Character.isUnicodeIdentifierPart(document.getChar(start - 1)))
				start--;

			while (start != end && Character.isWhitespace(document.getChar(start)))
				start++;

			if (start == end)
				start = getCompletionOffset();

			return start;

		} catch (BadLocationException e) {
			return super.getStart();
		}
	}

	/*
	 * @see org.eclipse.jdt.internal.corext.template.DocumentTemplateContext#getEnd()
	 */
	@Override
	public int getEnd() {

		if (fIsManaged || getCompletionLength() == 0)
			return super.getEnd();

		try {
			IDocument document = getDocument();

			int start = getCompletionOffset();
			int end = getCompletionOffset() + getCompletionLength();

			while (start != end && Character.isWhitespace(document.getChar(end - 1)))
				end--;

			return end;

		} catch (BadLocationException e) {
			return super.getEnd();
		}
	}

	/*
	 * @see org.eclipse.jdt.internal.corext.template.DocumentTemplateContext#getKey()
	 */
	@Override
	public String getKey() {

		if (getCompletionLength() == 0)
			return super.getKey();

		try {
			IDocument document = getDocument();

			int start = getStart();
			int end = getCompletionOffset();
			return start <= end ? 
					document.get(start, end - start) : 
					""; //$NON-NLS-1$

		} catch (BadLocationException e) {
			return super.getKey();
		}
	}

//	/**
//	 * Returns the character before the start position of the completion.
//	 *
//	 * @return the character before the start position of the completion
//	 */
//	public char getCharacterBeforeStart() {
//		int start= getStart();
//
//		try {
//			return start == 0
//				? ' '
//				: getDocument().getChar(start - 1);
//
//		} catch (BadLocationException e) {
//			return ' ';
//		}
//	}

//	private static void handleException(Shell shell, Exception e) {
//		String title= JavaTemplateMessages.JavaContext_error_title;
//		if (e instanceof CoreException)
//			ExceptionHandler.handle((CoreException)e, shell, title, null);
//		else if (e instanceof InvocationTargetException)
//			ExceptionHandler.handle((InvocationTargetException)e, shell, title, null);
//		else {
//			JavaPlugin.log(e);
//			String message= e.getMessage();
//			if (message == null) {
//				message= JavaTemplateMessages.JavaContext_unexpected_error_message;
//			}
//			MessageDialog.openError(shell, title, message);
//		}
//	}

//	private CompilationUnitCompletion getCompletion() {
//		ICompilationUnit compilationUnit= getCompilationUnit();
//		if (fCompletion == null) {
//			fCompletion= new CompilationUnitCompletion(compilationUnit);
//
//			if (compilationUnit != null) {
//				try {
//					compilationUnit.codeComplete(getStart(), fCompletion);
//				} catch (JavaModelException e) {
//					// ignore
//				}
//			}
//		}
//
//		return fCompletion;
//	}

//	/**
//	 * Returns the names of arrays available in the current {@link CompilationUnit}'s scope.
//	 *
//	 * @return the names of local arrays available in the current {@link CompilationUnit}'s scope
//	 */
//	public Variable[] getArrays() {
//		Variable[] arrays= getCompletion().findArraysInCurrentScope();
//		arrange(arrays);
//		return arrays;
//	}
//
//	/**
//	 * Sorts already used variables behind any that are not yet used.
//	 *
//	 * @param variables the variables to sort
//	 * @since 3.3
//	 */
//	private void arrange(Variable[] variables) {
//		Arrays.sort(variables, new Comparator<Variable>() {
//			public int compare(Variable o1, Variable o2) {
//				return rank(o1) - rank(o2);
//			}
//
//			private int rank(Variable l) {
//				return fUsedNames.contains(l.getName()) ? 1 : 0;
//			}
//		});
//	}
//
//	/**
//	 * Returns the names of local variables matching <code>type</code>.
//	 *
//	 * @param type the type of the variables
//	 * @return the names of local variables matching <code>type</code>
//	 * @since 3.3
//	 */
//	public Variable[] getLocalVariables(String type) {
//		Variable[] localVariables= getCompletion().findLocalVariables(type);
//		arrange(localVariables);
//		return localVariables;
//	}
//
//	/**
//	 * Returns the names of fields matching <code>type</code>.
//	 *
//	 * @param type the type of the fields
//	 * @return the names of fields matching <code>type</code>
//	 * @since 3.3
//	 */
//	public Variable[] getFields(String type) {
//		Variable[] fields= getCompletion().findFieldVariables(type);
//		arrange(fields);
//		return fields;
//	}
//
//	/**
//	 * Returns the names of iterables or arrays available in the current {@link CompilationUnit}'s scope.
//	 *
//	 * @return the names of iterables or arrays available in the current {@link CompilationUnit}'s scope
//	 */
//	public Variable[] getIterables() {
//		Variable[] iterables= getCompletion().findIterablesInCurrentScope();
//		arrange(iterables);
//		return iterables;
//	}
//
//	public void markAsUsed(String name) {
//		fUsedNames.add(name);
//	}
//
//	public String[] suggestVariableNames(String type) throws IllegalArgumentException {
//		String[] excludes= computeExcludes();
//		// TODO erasure, arrays, etc.
//		String[] result= suggestVariableName(type, excludes);
//		return result;
//	}
//
//	String[] computeExcludes() {
//		String[] excludes= getCompletion().getLocalVariableNames();
//		if (!fUsedNames.isEmpty()) {
//			String[] allExcludes= new String[fUsedNames.size() + excludes.length];
//			System.arraycopy(excludes, 0, allExcludes, 0, excludes.length);
//			System.arraycopy(fUsedNames.toArray(), 0, allExcludes, 0, fUsedNames.size());
//			excludes= allExcludes;
//		}
//		return excludes;
//	}
//
//	private String[] suggestVariableName(String type, String[] excludes) throws IllegalArgumentException {
//		int dim=0;
//		while (type.endsWith("[]")) {//$NON-NLS-1$
//			dim++;
//			type= type.substring(0, type.length() - 2);
//		}
//
//		IJavaProject project= getJavaProject();
//		if (project != null)
//			return StubUtility.getVariableNameSuggestions(NamingConventions.VK_LOCAL, project, type, dim, Arrays.asList(excludes), true);
//
//		// fallback if we lack proper context: roll-our own lowercasing
//		return new String[] {Signature.getSimpleName(type).toLowerCase()};
//	}
//
//	/**
//	 * Adds an import for type with type name <code>type</code> if possible.
//	 * Returns a string which can be used to reference the type.
//	 *
//	 * @param type the fully qualified name of the type to import
//	 * @return returns a type to which the type binding can be assigned to.
//	 * 	The returned type contains is unqualified when an import could be added or was already known.
//	 * 	It is fully qualified, if an import conflict prevented the import.
//	 * @since 3.4
//	 */
//	public String addImport(String type) {
//		if (isReadOnly())
//			return type;
//
//		ICompilationUnit cu= getCompilationUnit();
//		if (cu == null)
//			return type;
//
//		try {
//			boolean qualified= type.indexOf('.') != -1;
//			if (!qualified) {
//				IJavaSearchScope searchScope= SearchEngine.createJavaSearchScope(new IJavaElement[] { cu.getJavaProject() });
//				SimpleName nameNode= null;
//				TypeNameMatch[] matches= findAllTypes(type, searchScope, nameNode, null, cu);
//				if (matches.length != 1) // only add import if we have a single match
//					return type;
//				type= matches[0].getFullyQualifiedName();
//			}
//
//			CompilationUnit root= getASTRoot(cu);
//			if (fImportRewrite == null) {
//				if (root == null) {
//					fImportRewrite= StubUtility.createImportRewrite(cu, true);
//				} else {
//					fImportRewrite= StubUtility.createImportRewrite(root, true);
//				}
//			}
//
//			ImportRewriteContext context;
//			if (root == null)
//				context= null;
//			else
//				context= new ContextSensitiveImportRewriteContext(root, getCompletionOffset(), fImportRewrite);
//
//			return fImportRewrite.addImport(type, context);
//		} catch (JavaModelException e) {
//			handleException(null, e);
//			return type;
//		}
//	}
//
//	/**
//	 * Adds a static import for the member with name <code>qualifiedMemberName</code>. The member is
//	 * either a static field or a static method or a '*' to import all static members of a type.
//	 *
//	 * @param qualifiedMemberName the fully qualified name of the member to import or a qualified type
//	 * 			name plus a '.*' suffix.
//	 * @return returns either the simple member name if the import was successful or else the qualified name.
//	 * @since 3.4
//	 */
//	public String addStaticImport(String qualifiedMemberName) {
//		if (isReadOnly())
//			return qualifiedMemberName;
//
//		ICompilationUnit cu= getCompilationUnit();
//		if (cu == null)
//			return qualifiedMemberName;
//
//		int memberOffset= qualifiedMemberName.lastIndexOf('.');
//		if (memberOffset == -1)
//			return qualifiedMemberName;
//
//		String typeName= qualifiedMemberName.substring(0, memberOffset);
//		String memberName= qualifiedMemberName.substring(memberOffset + 1, qualifiedMemberName.length());
//		try {
//			boolean isField;
//			if ("*".equals(memberName)) { //$NON-NLS-1$
//				isField= true;
//			} else {
//				IJavaProject javaProject= cu.getJavaProject();
//
//				IType type= javaProject.findType(typeName);
//				if (type == null)
//					return qualifiedMemberName;
//
//				IField field= type.getField(memberName);
//				if (field.exists()) {
//					isField= true;
//				} else if (hasMethod(type, memberName)) {
//					isField= false;
//				} else {
//					return qualifiedMemberName;
//				}
//			}
//
//			CompilationUnit root= getASTRoot(cu);
//			if (fImportRewrite == null) {
//				if (root == null) {
//					fImportRewrite= StubUtility.createImportRewrite(cu, true);
//				} else {
//					fImportRewrite= StubUtility.createImportRewrite(root, true);
//				}
//			}
//
//			ImportRewriteContext context;
//			if (root == null)
//				context= null;
//			else
//				context= new ContextSensitiveImportRewriteContext(root, getCompletionOffset(), fImportRewrite);
//
//			return fImportRewrite.addStaticImport(typeName, memberName, isField, context);
//		} catch (JavaModelException e) {
//			handleException(null, e);
//			return typeName;
//		}
//	}
//
//	/**
//	 * Does <code>type</code> contain a method with <code>name</code>?
//	 *
//	 * @param type the type to inspect
//	 * @param name the name of the method to search for
//	 * @return true if has such a method
//	 * @throws JavaModelException if methods could not be retrieved
//	 * @since 3.4
//	 */
//	private boolean hasMethod(IType type, String name) throws JavaModelException {
//		IMethod[] methods= type.getMethods();
//		for (int i= 0; i < methods.length; i++) {
//			if (name.equals(methods[i].getElementName()))
//				return true;
//		}
//
//		return false;
//	}
//
//	private void rewriteImports() {
//		if (fImportRewrite == null)
//			return;
//
//		if (isReadOnly())
//			return;
//
//		ICompilationUnit cu= getCompilationUnit();
//		if (cu == null)
//			return;
//
//		try {
//			Position position= new Position(getCompletionOffset(), 0);
//			IDocument document= getDocument();
//			final String category= "__template_position_importer" + System.currentTimeMillis(); //$NON-NLS-1$
//			IPositionUpdater updater= new DefaultPositionUpdater(category);
//			document.addPositionCategory(category);
//			document.addPositionUpdater(updater);
//			document.addPosition(position);
//
//			try {
//				JavaModelUtil.applyEdit(cu, fImportRewrite.rewriteImports(null), false, null);
//
//				setCompletionOffset(position.getOffset());
//			} catch (CoreException e) {
//				handleException(null, e);
//			} finally {
//				document.removePosition(position);
//				document.removePositionUpdater(updater);
//				document.removePositionCategory(category);
//			}
//		} catch (BadLocationException e) {
//			handleException(null, e);
//		} catch (BadPositionCategoryException e) {
//			handleException(null, e);
//		}
//	}
//
//	private CompilationUnit getASTRoot(ICompilationUnit compilationUnit) {
//		return SharedASTProvider.getAST(compilationUnit, SharedASTProvider.WAIT_NO, new NullProgressMonitor());
//	}
//
//	/*
//	 * Finds a type by the simple name. From AddImportsOperation
//	 */
//	private TypeNameMatch[] findAllTypes(String simpleTypeName, IJavaSearchScope searchScope, SimpleName nameNode, IProgressMonitor monitor, ICompilationUnit cu) throws JavaModelException {
//		boolean is50OrHigher= JavaModelUtil.is50OrHigher(cu.getJavaProject());
//
//		int typeKinds= SimilarElementsRequestor.ALL_TYPES;
//		if (nameNode != null) {
//			typeKinds= ASTResolving.getPossibleTypeKinds(nameNode, is50OrHigher);
//		}
//
//		ArrayList<TypeNameMatch> typeInfos= new ArrayList<TypeNameMatch>();
//		TypeNameMatchCollector requestor= new TypeNameMatchCollector(typeInfos);
//		new SearchEngine().searchAllTypeNames(null, 0, simpleTypeName.toCharArray(), SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, getSearchForConstant(typeKinds), searchScope, requestor, IJavaSearchConstants.FORCE_IMMEDIATE_SEARCH, monitor);
//
//		ArrayList<TypeNameMatch> typeRefsFound= new ArrayList<TypeNameMatch>(typeInfos.size());
//		for (int i= 0, len= typeInfos.size(); i < len; i++) {
//			TypeNameMatch curr= typeInfos.get(i);
//			if (curr.getPackageName().length() > 0) { // do not suggest imports from the default package
//				if (isOfKind(curr, typeKinds, is50OrHigher) && isVisible(curr, cu)) {
//					typeRefsFound.add(curr);
//				}
//			}
//		}
//		return typeRefsFound.toArray(new TypeNameMatch[typeRefsFound.size()]);
//	}
//
//	private int getSearchForConstant(int typeKinds) {
//		final int CLASSES= SimilarElementsRequestor.CLASSES;
//		final int INTERFACES= SimilarElementsRequestor.INTERFACES;
//		final int ENUMS= SimilarElementsRequestor.ENUMS;
//		final int ANNOTATIONS= SimilarElementsRequestor.ANNOTATIONS;
//
//		switch (typeKinds & (CLASSES | INTERFACES | ENUMS | ANNOTATIONS)) {
//			case CLASSES: return IJavaSearchConstants.CLASS;
//			case INTERFACES: return IJavaSearchConstants.INTERFACE;
//			case ENUMS: return IJavaSearchConstants.ENUM;
//			case ANNOTATIONS: return IJavaSearchConstants.ANNOTATION_TYPE;
//			case CLASSES | INTERFACES: return IJavaSearchConstants.CLASS_AND_INTERFACE;
//			case CLASSES | ENUMS: return IJavaSearchConstants.CLASS_AND_ENUM;
//			default: return IJavaSearchConstants.TYPE;
//		}
//	}
//
//	private boolean isOfKind(TypeNameMatch curr, int typeKinds, boolean is50OrHigher) {
//		int flags= curr.getModifiers();
//		if (Flags.isAnnotation(flags)) {
//			return is50OrHigher && ((typeKinds & SimilarElementsRequestor.ANNOTATIONS) != 0);
//		}
//		if (Flags.isEnum(flags)) {
//			return is50OrHigher && ((typeKinds & SimilarElementsRequestor.ENUMS) != 0);
//		}
//		if (Flags.isInterface(flags)) {
//			return (typeKinds & SimilarElementsRequestor.INTERFACES) != 0;
//		}
//		return (typeKinds & SimilarElementsRequestor.CLASSES) != 0;
//	}
//
//
//	private boolean isVisible(TypeNameMatch curr, ICompilationUnit cu) {
//		int flags= curr.getModifiers();
//		if (Flags.isPrivate(flags)) {
//			return false;
//		}
//		if (Flags.isPublic(flags) || Flags.isProtected(flags)) {
//			return true;
//		}
//		return curr.getPackageName().equals(cu.getParent().getElementName());
//	}
//
//	/**
//	 * Evaluates a 'java' template in the context of a compilation unit
//	 *
//	 * @param template the template to be evaluated
//	 * @param compilationUnit the compilation unit in which to evaluate the template
//	 * @param position the position inside the compilation unit for which to evaluate the template
//	 * @return the evaluated template
//	 * @throws CoreException in case the template is of an unknown context type
//	 * @throws BadLocationException in case the position is invalid in the compilation unit
//	 * @throws TemplateException in case the evaluation fails
//	 */
//	public static String evaluateTemplate(Template template, ICompilationUnit compilationUnit, int position) throws CoreException, BadLocationException, TemplateException {
//
//		TemplateContextType contextType= JavaPlugin.getDefault().getTemplateContextRegistry().getContextType(template.getContextTypeId());
//		if (!(contextType instanceof CompilationUnitContextType))
//			throw new CoreException(new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, IStatus.ERROR, JavaTemplateMessages.JavaContext_error_message, null));
//
//		IDocument document= new Document();
//		if (compilationUnit != null && compilationUnit.exists())
//			document.set(compilationUnit.getSource());
//
//		CompilationUnitContext context= ((CompilationUnitContextType) contextType).createContext(document, position, 0, compilationUnit);
//		context.setForceEvaluation(true);
//
//		TemplateBuffer buffer= context.evaluate(template);
//		if (buffer == null)
//			return null;
//		return buffer.getString();
//	}
//
//	TemplateVariable getTemplateVariable(String name) {
//		TemplateVariable variable= fVariables.get(name);
//		if (variable != null && !variable.isResolved())
//			getContextType().resolve(variable, this);
//		return variable;
//	}

//	/**
//	 * Adds a multi-variable guess dependency.
//	 *
//	 * @param master the master variable - <code>slave</code> needs to be updated when
//	 *        <code>master</code> changes
//	 * @param slave the dependent variable
//	 * @since 3.3
//	 */
//	public void addDependency(MultiVariable master, MultiVariable slave) {
//		MultiVariableGuess guess= getMultiVariableGuess();
//		if (guess == null) {
//			guess= new MultiVariableGuess();
//			setMultiVariableGuess(guess);
//		}
//
//		guess.addDependency(master, slave);
//	}

}
