/*******************************************************************************
 * Copyright (c) 2007, 2019 Dakshinamurthy Karra, 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:
 *     Dakshinamurthy Karra (Jalian Systems) - Templates View - https://bugs.eclipse.org/bugs/show_bug.cgi?id=69581
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.javaeditor;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.window.Window;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.templates.ContextTypeRegistry;
import org.eclipse.jface.text.templates.DocumentTemplateContext;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.persistence.TemplateStore;

import org.eclipse.ui.texteditor.templates.AbstractTemplatesPage;

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

import org.eclipse.jdt.internal.corext.template.java.CompilationUnitContextType;
import org.eclipse.jdt.internal.corext.template.java.JavaContext;
import org.eclipse.jdt.internal.corext.template.java.JavaContextType;
import org.eclipse.jdt.internal.corext.template.java.JavaDocContext;
import org.eclipse.jdt.internal.corext.template.java.JavaDocContextType;
import org.eclipse.jdt.internal.corext.template.java.SWTContextType;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;

import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jdt.ui.text.JavaTextTools;

import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.preferences.EditTemplateDialog;
import org.eclipse.jdt.internal.ui.preferences.JavaSourcePreviewerUpdater;
import org.eclipse.jdt.internal.ui.text.SimpleJavaSourceViewerConfiguration;
import org.eclipse.jdt.internal.ui.text.template.contentassist.TemplateProposal;
import org.eclipse.jdt.internal.ui.text.template.preferences.TemplateVariableProcessor;


/**
 * The templates page for the Java editor.
 *
 * @since 3.4
 */
public class JavaTemplatesPage extends AbstractTemplatesPage {

	private static final String PREFERENCE_PAGE_ID= "org.eclipse.jdt.ui.preferences.JavaTemplatePreferencePage"; //$NON-NLS-1$
	private static final TemplateStore TEMPLATE_STORE= JavaPlugin.getDefault().getTemplateStore();
	private static final IPreferenceStore PREFERENCE_STORE= JavaPlugin.getDefault().getPreferenceStore();
	private static final ContextTypeRegistry TEMPLATE_CONTEXT_REGISTRY= JavaPlugin.getDefault().getTemplateContextRegistry();

	private TemplateVariableProcessor fTemplateProcessor;
	private JavaEditor fJavaEditor;

	/**
	 * Create a new AbstractTemplatesPage for the JavaEditor
	 *
	 * @param javaEditor the java editor
	 */
	public JavaTemplatesPage(JavaEditor javaEditor) {
		super(javaEditor, javaEditor.getViewer());
		fJavaEditor= javaEditor;
		fTemplateProcessor= new TemplateVariableProcessor();
	}

	/*
	 * @see org.eclipse.ui.texteditor.templates.AbstractTemplatesPage#insertTemplate(org.eclipse.jface.text.templates.Template, org.eclipse.jface.text.IDocument)
	 */
	@Override
	protected void insertTemplate(Template template, IDocument document) {
		if (!fJavaEditor.validateEditorInputState())
			return;

		ISourceViewer contextViewer= fJavaEditor.getViewer();
		ITextSelection textSelection= (ITextSelection) contextViewer.getSelectionProvider().getSelection();
		if (!isValidTemplate(document, template, textSelection.getOffset(), textSelection.getLength()))
			return;
		beginCompoundChange(contextViewer);
		/*
		 * The Editor checks whether a completion for a word exists before it allows for the template to be
		 * applied. We pickup the current text at the selection position and replace it with the first char
		 * of the template name for this to succeed.
		 * Another advantage by this method is that the template replaces the selected text provided the
		 * selection by itself is not used in the template pattern.
		 */
		String savedText;
		try {
			savedText= document.get(textSelection.getOffset(), textSelection.getLength());
			if (savedText.length() == 0) {
				String prefix= getIdentifierPart(document, template, textSelection.getOffset(), textSelection.getLength());
				if (prefix.length() > 0 && !template.getName().startsWith(prefix.toString())) {
					return;
				}
				if (prefix.length() > 0) {
					contextViewer.setSelectedRange(textSelection.getOffset() - prefix.length(), prefix.length());
					textSelection= (ITextSelection) contextViewer.getSelectionProvider().getSelection();
				}
			}
			document.replace(textSelection.getOffset(), textSelection.getLength(), template.getName().substring(0, 1));
		} catch (BadLocationException e) {
			endCompoundChange(contextViewer);
			return;
		}
		Position position= new Position(textSelection.getOffset() + 1, 0);
		Region region= new Region(textSelection.getOffset() + 1, 0);
		contextViewer.getSelectionProvider().setSelection(new TextSelection(textSelection.getOffset(), 1));
		ICompilationUnit compilationUnit= (ICompilationUnit) EditorUtility.getEditorInputJavaElement(fJavaEditor, true);

		TemplateContextType type= getContextTypeRegistry().getContextType(template.getContextTypeId());
		DocumentTemplateContext context= ((CompilationUnitContextType) type).createContext(document, position, compilationUnit);
		context.setVariable("selection", savedText); //$NON-NLS-1$
		if (context.getKey().length() == 0) {
			try {
				document.replace(textSelection.getOffset(), 1, savedText);
			} catch (BadLocationException e) {
				endCompoundChange(contextViewer);
				return;
			}
		}
		TemplateProposal proposal= new TemplateProposal(template, context, region, null);
		fJavaEditor.getSite().getPage().activate(fJavaEditor);
		proposal.apply(fJavaEditor.getViewer(), ' ', 0, region.getOffset());
		endCompoundChange(contextViewer);
	}

	@Override
	protected ContextTypeRegistry getContextTypeRegistry() {
		return TEMPLATE_CONTEXT_REGISTRY;
	}

	@Override
	protected IPreferenceStore getTemplatePreferenceStore() {
		return PREFERENCE_STORE;
	}

	@Override
	public TemplateStore getTemplateStore() {
		return TEMPLATE_STORE;
	}

	/*
	 * @see org.eclipse.ui.texteditor.templates.TextEditorTemplatesPage#isValidTemplate(org.eclipse.jface.text.templates.Template, int, int)
	 */
	@Override
	protected boolean isValidTemplate(IDocument document, Template template, int offset, int length) {
		for (String contextId : getContextTypeIds(document, offset)) {
			if (contextId.equals(template.getContextTypeId())) {
				DocumentTemplateContext context= getContext(document, template, offset, length);
				return context.canEvaluate(template) || isTemplateAllowed(context, template);
			}
		}
		return false;
	}

	@Override
	protected SourceViewer createPatternViewer(Composite parent) {
		IDocument document= new Document();
		JavaTextTools tools= JavaPlugin.getDefault().getJavaTextTools();
		tools.setupJavaDocumentPartitioner(document, IJavaPartitions.JAVA_PARTITIONING);
		IPreferenceStore store= JavaPlugin.getDefault().getCombinedPreferenceStore();
		JavaSourceViewer viewer= new JavaSourceViewer(parent, null, null, false, SWT.V_SCROLL | SWT.H_SCROLL, store);
		SimpleJavaSourceViewerConfiguration configuration= new SimpleJavaSourceViewerConfiguration(tools.getColorManager(), store, null, IJavaPartitions.JAVA_PARTITIONING, false);
		viewer.configure(configuration);
		viewer.setEditable(false);
		viewer.setDocument(document);

		Font font= JFaceResources.getFont(PreferenceConstants.EDITOR_TEXT_FONT);
		viewer.getTextWidget().setFont(font);
		new JavaSourcePreviewerUpdater(viewer, configuration, store);

		Control control= viewer.getControl();
		GridData data= new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.FILL_VERTICAL);
		control.setLayoutData(data);

		viewer.setEditable(false);
		return viewer;
	}

	@Override
	protected Image getImage(Template template) {
		String contextId= template.getContextTypeId();
		if (SWTContextType.ID_ALL.equals(contextId) || SWTContextType.ID_STATEMENTS.equals(contextId) || SWTContextType.ID_MEMBERS.equals(contextId))
			return JavaPluginImages.get(JavaPluginImages.IMG_OBJS_SWT_TEMPLATE);
		return JavaPluginImages.get(JavaPluginImages.IMG_OBJS_TEMPLATE);
	}

	/*
	 * @see org.eclipse.ui.texteditor.templates.TextEditorTemplatesPage#editTemplate(org.eclipse.jface.text.templates.Template, boolean, boolean)
	 */
	@Override
	protected Template editTemplate(Template template, boolean edit, boolean isNameModifiable) {
		EditTemplateDialog dialog= new EditTemplateDialog(getSite().getShell(), template, edit, isNameModifiable, getContextTypeRegistry());
		if (dialog.open() == Window.OK)
			return dialog.getTemplate();
		return null;
	}

	/*
	 * @see org.eclipse.ui.texteditor.templates.TextEditorTemplatesPage#updatePatternViewer(org.eclipse.jface.text.templates.Template)
	 */
	@Override
	protected void updatePatternViewer(Template template) {
		if (template == null) {
			getPatternViewer().getDocument().set(""); //$NON-NLS-1$
			return ;
		}
		String contextId= template.getContextTypeId();
		TemplateContextType type= getContextTypeRegistry().getContextType(contextId);
		fTemplateProcessor.setContextType(type);

		IDocument doc= getPatternViewer().getDocument();

		String start= null;
		if ("javadoc".equals(contextId)) { //$NON-NLS-1$
			start= "/**" + doc.getLegalLineDelimiters()[0]; //$NON-NLS-1$
		} else
			start= ""; //$NON-NLS-1$

		doc.set(start + template.getPattern());
		int startLen= start.length();
		getPatternViewer().setDocument(doc, startLen, doc.getLength() - startLen);
	}

	@Override
	protected String getPreferencePageId() {
		return PREFERENCE_PAGE_ID;
	}

	/**
	 * Undomanager - end compound change
	 *
	 * @param viewer the viewer
	 */
	private void endCompoundChange(ISourceViewer viewer) {
		if (viewer instanceof ITextViewerExtension)
			((ITextViewerExtension) viewer).getRewriteTarget().endCompoundChange();
	}

	/**
	 * Undomanager - begin a compound change
	 *
	 * @param viewer the viewer
	 */
	private void beginCompoundChange(ISourceViewer viewer) {
		if (viewer instanceof ITextViewerExtension)
			((ITextViewerExtension) viewer).getRewriteTarget().beginCompoundChange();
	}

	/**
	 * Check whether the template is allowed eventhough the context can't evaluate it. This is
	 * needed because the Dropping of a template is more lenient than ctl-space invoked code assist.
	 *
	 * @param context the template context
	 * @param template the template
	 * @return true if the template is allowed
	 */
	private boolean isTemplateAllowed(DocumentTemplateContext context, Template template) {
		int offset;
		try {
			if (JavaDocContextType.ID.equals(template.getContextTypeId())) {
				return (offset= context.getCompletionOffset()) > 0 && Character.isWhitespace(context.getDocument().getChar(offset - 1));
			} else {
				return ((offset= context.getCompletionOffset()) > 0 && !isTemplateNamePart(context.getDocument().getChar(offset - 1)));
			}
		} catch (BadLocationException e) {
		}
		return false;
	}

	/**
	 * Checks whether the character is a valid character in Java template names
	 *
	 * @param ch the character
	 * @return <code>true</code> if the character is part of a template name
	 */
	private boolean isTemplateNamePart(char ch) {
		return !Character.isWhitespace(ch) && ch != '(' && ch != ')' && ch != '{' && ch != '}' && ch != ';';
	}

	/**
	 * Get context
	 *
	 * @param document the document
	 * @param template the template
	 * @param offset the offset
	 * @param length the length
	 * @return the context
	 */
	private DocumentTemplateContext getContext(IDocument document, Template template, final int offset, int length) {
		DocumentTemplateContext context;
		if (JavaDocContextType.ID.equals(template.getContextTypeId())) {
			context= new JavaDocContext(getContextTypeRegistry().getContextType(template.getContextTypeId()), document, new Position(offset, length), (ICompilationUnit) EditorUtility
					.getEditorInputJavaElement(fJavaEditor, true));
		} else {
			context= new JavaContext(getContextTypeRegistry().getContextType(template.getContextTypeId()), document, new Position(offset, length), (ICompilationUnit) EditorUtility.getEditorInputJavaElement(
					fJavaEditor, true));
		}
		return context;
	}

	/**
	 * Get the active contexts for the given position in the document.
	 * <p>
	 * FIXME: should trigger code assist to get the context.
	 * </p>
	 *
	 * @param document the document
	 * @param offset the offset
	 * @return an array of valid context id
	 */
	@Override
	protected String[] getContextTypeIds(IDocument document, int offset) {
		try {
			String partition= TextUtilities.getContentType(document, IJavaPartitions.JAVA_PARTITIONING, offset, true);
			String[] ids= new String[] { JavaContextType.ID_ALL, JavaContextType.ID_MEMBERS, JavaContextType.ID_STATEMENTS, SWTContextType.ID_ALL, SWTContextType.ID_STATEMENTS, SWTContextType.ID_MEMBERS};
			if (IJavaPartitions.JAVA_DOC.equals(partition))
				ids= new String[] { JavaDocContextType.ID };
			IJavaElement elem= EditorUtility.getEditorInputJavaElement(fJavaEditor, true);
			if (elem != null && JavaModelUtil.MODULE_INFO_JAVA.equals(elem.getElementName())) {
				ids= new String[] { JavaContextType.ID_MODULE };
			}
			if (document.get().trim().length() == 0) {
				ids= new String[] { JavaContextType.ID_EMPTY };
			}
			return ids;
		} catch (BadLocationException e) {
			return new String[0];
		}
	}

	/**
	 * Get the Java identifier terminated at the given offset
	 *
	 * @param document the document
	 * @param template the template
	 * @param offset the offset
	 * @param length the length
	 * @return the identifier part the Java identifier
	 */
	private String getIdentifierPart(IDocument document, Template template, int offset, int length) {
		return getContext(document, template, offset, length).getKey();
	}
}
