/*******************************************************************************
 * Copyright (c) 2019 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:
 *     IBM Corporation - initial API and implementation
 *     Sebastian Davids: sdavids@gmx.de - see bug 25376
 *     Lukas Hanke <hanke@yatta.de> - [templates][content assist] Content assist for 'for' loop should suggest member variables - https://bugs.eclipse.org/117215
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 488432
 *     Microsoft Corporation - moved template related code to jdt.core.manipulation - https://bugs.eclipse.org/549989
 *******************************************************************************/
package org.eclipse.jdt.internal.corext.template.java;

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.templates.DocumentTemplateContext;
import org.eclipse.jface.text.templates.GlobalTemplateVariables;
import org.eclipse.jface.text.templates.TemplateContext;
import org.eclipse.jface.text.templates.TemplateVariable;
import org.eclipse.jface.text.templates.TemplateVariableResolver;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;

import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.corext.template.java.CompilationUnitCompletion.Variable;

import org.eclipse.jdt.internal.ui.text.template.contentassist.MultiVariable;


/**
 * An abstract context type for templates inside Java code.
 *
 * @since 3.4
 */
public abstract class AbstractJavaContextTypeCore extends CompilationUnitContextType {


	protected static abstract class AbstractIterable extends TemplateVariableResolver {
		public AbstractIterable(String type, String description) {
			super(type, description);
		}

		@Override
		protected String[] resolveAll(TemplateContext context) {
	        IJavaContext jc= (IJavaContext) context;
	        Variable[] iterables= getVariables(jc);
	        String[] names= new String[iterables.length];
	        for (int i= 0; i < iterables.length; i++)
				names[i]= iterables[i].getName();
	        if (names.length > 0)
	        	jc.markAsUsed(names[0]);
			return names;
	    }

		abstract protected Variable[] getVariables(IJavaContext jc);

		/*
		 * @see org.eclipse.jface.text.templates.TemplateVariableResolver#resolve(org.eclipse.jface.text.templates.TemplateVariable, org.eclipse.jface.text.templates.TemplateContext)
		 */
		@Override
		public void resolve(TemplateVariable variable, TemplateContext context) {
			if (variable instanceof MultiVariable) {
				IJavaContext jc= (IJavaContext) context;
				JavaVariable jv= (JavaVariable) variable;
		        Variable[] iterables= getVariables(jc);
				if (iterables.length > 0) {
					jv.setChoices(iterables);
					jc.markAsUsed(iterables[0].getName());

					if (iterables.length > 1)
						variable.setUnambiguous(false);
					else
						variable.setUnambiguous(isUnambiguous(context));

					return;
				}
			}

			super.resolve(variable, context);
		}
	}

	protected static class Array extends AbstractIterable {
		public Array() {
			super("array", JavaTemplateMessages.JavaContextType_variable_description_array);  //$NON-NLS-1$
		}

		@Override
		protected Variable[] getVariables(IJavaContext jc) {
			return jc.getArrays();
		}
	}

	protected static class Iterable extends AbstractIterable {
		public Iterable() {
		    super("iterable", JavaTemplateMessages.JavaContextType_variable_description_iterable);  //$NON-NLS-1$
		}

		@Override
		protected Variable[] getVariables(IJavaContext jc) {
			return jc.getIterables();
		}
	}

	protected static abstract class AbstractIterableType extends TemplateVariableResolver {
		private String fMasterName;

	    public AbstractIterableType(String type, String desc, String master) {
	     	super(type, desc);
	     	fMasterName= master;
	    }
	    @Override
		protected String[] resolveAll(TemplateContext context) {
	        IJavaContext jc= (IJavaContext) context;
	        Variable[] iterables= getVariablesInContextScope(jc);
	        String[] types= new String[iterables.length];
	        for (int i= 0; i < iterables.length; i++)
	        	types[i]= iterables[i].getMemberTypeNames()[0];
			return types;
	    }

		abstract protected Variable[] getVariablesInContextScope(IJavaContext jc);

		/*
		 * @see org.eclipse.jface.text.templates.TemplateVariableResolver#resolve(org.eclipse.jface.text.templates.TemplateVariable, org.eclipse.jface.text.templates.TemplateContext)
		 */
		@Override
		public void resolve(TemplateVariable variable, TemplateContext context) {
			if (variable instanceof MultiVariable) {
				IJavaContext jc= (IJavaContext) context;
				MultiVariable mv= (MultiVariable) variable;

				Variable[] iterables= getVariablesInContextScope(jc);
				if (iterables.length > 0) {

					for (Variable iterable : iterables)
						mv.setChoices(iterable, iterable.getMemberTypeNames());

					TemplateVariable master= jc.getTemplateVariable(fMasterName);
					if (master instanceof MultiVariable) {
						final MultiVariable masterMv= (MultiVariable) master;
						jc.addDependency(masterMv, mv);
						mv.setKey(masterMv.getCurrentChoice());
					}

					if (iterables.length > 1 || iterables.length == 1 && mv.getChoices().length > 1)
						variable.setUnambiguous(false);
					else
						variable.setUnambiguous(isUnambiguous(context));

					return;
				}

			}

			super.resolve(variable, context);
		}
	}

	protected static class ArrayType extends AbstractIterableType {
		public ArrayType() {
			super("array_type", JavaTemplateMessages.JavaContextType_variable_description_array_type, "array");  //$NON-NLS-1$ //$NON-NLS-2$
		}
		@Override
		protected Variable[] getVariablesInContextScope(IJavaContext jc) {
			return jc.getArrays();
		}
	}

	protected static class IterableType extends AbstractIterableType {
		public IterableType() {
	     	super("iterable_type", JavaTemplateMessages.JavaContextType_variable_description_iterable_type, "iterable");  //$NON-NLS-1$ //$NON-NLS-2$
		}
		@Override
		protected Variable[] getVariablesInContextScope(IJavaContext jc) {
			return jc.getIterables();
		}
	}

	protected static abstract class AbstractIterableElement extends TemplateVariableResolver {
		private String fMasterName;

	    public AbstractIterableElement(String type, String desc, String master) {
	     	super(type, desc);
	     	fMasterName= master;
	    }

	    @Override
		protected String[] resolveAll(TemplateContext context) {
	        IJavaContext jc= (IJavaContext) context;
	        Variable[] iterables= getLocalVariables(jc);
	        String[] elements= new String[iterables.length];
	        for (int i= 0; i < iterables.length; i++) {
	        	elements[i]= jc.suggestVariableNames(iterables[i].getMemberTypeNames()[0])[0];
	        	if (i == 0)
	        		jc.markAsUsed(elements[0]);
	        }

	        return elements;
	    }

		abstract protected Variable[] getLocalVariables(IJavaContext jc);

		/*
		 * @see org.eclipse.jface.text.templates.TemplateVariableResolver#resolve(org.eclipse.jface.text.templates.TemplateVariable, org.eclipse.jface.text.templates.TemplateContext)
		 */
		@Override
		public void resolve(TemplateVariable variable, TemplateContext context) {
			if (variable instanceof MultiVariable) {
				IJavaContext jc= (IJavaContext) context;
				MultiVariable mv= (MultiVariable) variable;

		        Variable[] iterables= getLocalVariables(jc);
				if (iterables.length > 0) {
					for (Variable iterable : iterables) {
						String[] elements= jc.suggestVariableNames(iterable.getMemberTypeNames()[0]);
						mv.setChoices(iterable, elements);
					}


					TemplateVariable master= jc.getTemplateVariable(fMasterName);
					if (master instanceof MultiVariable) {
						final MultiVariable masterMv= (MultiVariable) master;
						jc.addDependency(masterMv, mv);
						mv.setKey(masterMv.getCurrentChoice());
					}
					jc.markAsUsed(mv.getDefaultValue());

					if (iterables.length > 1 || iterables.length == 1 && mv.getChoices().length > 1)
						variable.setUnambiguous(false);
					else
						variable.setUnambiguous(isUnambiguous(context));

					return;
				}

			}
			super.resolve(variable, context);
		}
	}

	protected static class ArrayElement extends AbstractIterableElement {
		public ArrayElement() {
			super("array_element", JavaTemplateMessages.JavaContextType_variable_description_array_element, "array");	 //$NON-NLS-1$ //$NON-NLS-2$
		}
		@Override
		protected Variable[] getLocalVariables(IJavaContext jc) {
			return jc.getArrays();
		}
	}

	protected static class IterableElement extends AbstractIterableElement {
		public IterableElement() {
	     	super("iterable_element", JavaTemplateMessages.JavaContextType_variable_description_iterable_element, "iterable");	 //$NON-NLS-1$ //$NON-NLS-2$
		}
		@Override
		protected Variable[] getLocalVariables(IJavaContext jc) {
			return jc.getIterables();
		}
	}

	protected static class Index extends NameResolver {
	    public Index() {
	    	super("int"); //$NON-NLS-1$
	     	setType("index"); //$NON-NLS-1$
	     	setDescription(JavaTemplateMessages.JavaContextType_variable_description_index);
	    }
	}

	protected static class Collection extends VarResolver {
	    public Collection() {
	    	super("java.util.Collection"); //$NON-NLS-1$
	    	setType("collection"); //$NON-NLS-1$
	    	setDescription(JavaTemplateMessages.JavaContextType_variable_description_collection);
		}
	}

	protected static class Iterator extends NameResolver {
	    public Iterator() {
	    	super("java.util.Iterator"); //$NON-NLS-1$
	     	setType("iterator"); //$NON-NLS-1$
	     	setDescription(JavaTemplateMessages.JavaContextType_variable_description_iterator);
		}
	}

	protected static class Todo extends TemplateVariableResolver {

		public Todo() {
			super("todo", JavaTemplateMessages.JavaContextType_variable_description_todo);  //$NON-NLS-1$
		}
		@Override
		protected String resolve(TemplateContext context) {
			IJavaContext javaContext= (IJavaContext) context;
			ICompilationUnit compilationUnit= javaContext.getCompilationUnit();
			if (compilationUnit == null)
				return "XXX"; //$NON-NLS-1$

			IJavaProject javaProject= compilationUnit.getJavaProject();
			String todoTaskTag= StubUtility.getTodoTaskTag(javaProject);
			if (todoTaskTag == null)
				return "XXX"; //$NON-NLS-1$

			return todoTaskTag;
		}
	}
/*
	protected static class Arguments extends SimpleVariableResolver {
	    public Arguments() {
	     	super("arguments", TemplateMessages.getString("Javavariable.description.arguments"), "");
	    }
	}
*/

	/**
	 * Initializes the context type resolvers.
	 * <p>
	 * <strong>Note:</strong> Only call this method if this
	 * context type doesn't inherit from another context type
	 * which already has these resolvers.</p>
	 *
	 * @since 3.4
	 */
	public void initializeContextTypeResolvers() {

		// global
		addResolver(new GlobalTemplateVariables.Cursor());
		addResolver(new GlobalTemplateVariables.WordSelection());
		addResolver(new GlobalTemplateVariables.Selection(GlobalTemplateVariables.LineSelection.NAME, JavaTemplateMessages.CompilationUnitContextType_variable_description_line_selection));
		addResolver(new GlobalTemplateVariables.Dollar());
		addResolver(new GlobalTemplateVariables.Date());
		addResolver(new GlobalTemplateVariables.Year());
		addResolver(new GlobalTemplateVariables.Time());
		addResolver(new GlobalTemplateVariables.User());

		// compilation unit
		addResolver(new File());
		addResolver(new PrimaryTypeName());
		addResolver(new ReturnType());
		addResolver(new Method());
		addResolver(new Type());
		addResolver(new Package());
		addResolver(new Project());
		addResolver(new Arguments());

		// java
		addResolver(new Array());
		addResolver(new ArrayType());
		addResolver(new ArrayElement());
		addResolver(new Index());
		addResolver(new Iterator());
		addResolver(new Collection());
		addResolver(new Iterable());
		addResolver(new IterableType());
		addResolver(new IterableElement());
		addResolver(new Todo());
	}


	/*
	 * @see org.eclipse.jdt.internal.corext.template.java.CompilationUnitContextType#createContext(org.eclipse.jface.text.IDocument, int, int, org.eclipse.jdt.core.ICompilationUnit)
	 */
	@Override
	public DocumentTemplateContext createContext(IDocument document, int offset, int length, ICompilationUnit compilationUnit) {
		JavaContextCore javaContext= new JavaContextCore(this, document, offset, length, compilationUnit);
		initializeContext(javaContext);
		return javaContext;
	}

	/*
	 * @see org.eclipse.jdt.internal.corext.template.java.CompilationUnitContextType#createContext(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.Position, org.eclipse.jdt.core.ICompilationUnit)
	 */
	@Override
	public DocumentTemplateContext createContext(IDocument document, Position completionPosition, ICompilationUnit compilationUnit) {
		JavaContextCore javaContext= new JavaContextCore(this, document, completionPosition, compilationUnit);
		initializeContext(javaContext);
		return javaContext;
	}

	/**
	 * Hook to initialize the context
	 *
	 * @param context the context
	 */
	protected void initializeContext(IJavaContext context) {
	}

}
