/*******************************************************************************
 * Copyright (c) 2000, 2016 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
 *     Florian Albrecht <florian.albrecht@clintworld.de> - make NLSKeyHyperlink work with non-text editors - https://bugs.eclipse.org/bugs/show_bug.cgi?id=97228
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.javaeditor;

import org.eclipse.swt.widgets.Display;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IStorage;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.hyperlink.IHyperlink;

import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PartInitException;

import org.eclipse.ui.texteditor.IEditorStatusLine;
import org.eclipse.ui.texteditor.ITextEditor;

import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ITypeBinding;

import org.eclipse.jdt.internal.corext.refactoring.nls.AccessorClassReference;
import org.eclipse.jdt.internal.corext.refactoring.nls.NLSHintHelper;
import org.eclipse.jdt.internal.corext.refactoring.nls.NLSRefactoring;
import org.eclipse.jdt.internal.corext.refactoring.nls.PropertyFileDocumentModel;
import org.eclipse.jdt.internal.corext.util.Messages;

import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.propertiesfileeditor.IPropertiesFilePartitions;
import org.eclipse.jdt.internal.ui.propertiesfileeditor.PropertyKeyHyperlinkDetector;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;


/**
 * NLS key hyperlink.
 *
 * @since 3.1
 */
public class NLSKeyHyperlink implements IHyperlink {

	private IRegion fRegion;
	private AccessorClassReference fAccessorClassReference;
	private IEditorPart fEditor;
	private final String fKeyName;


	/**
	 * Creates a new NLS key hyperlink.
	 *
	 * @param region the region of the link
	 * @param keyName the name of the key
	 * @param ref the accessor class reference
	 * @param editor the editor which contains the hyperlink
	 */
	public NLSKeyHyperlink(IRegion region, String keyName, AccessorClassReference ref, IEditorPart editor) {
		Assert.isNotNull(region);
		Assert.isNotNull(keyName);
		Assert.isNotNull(ref);
		Assert.isNotNull(editor);

		fRegion= region;
		fKeyName= keyName;
		fAccessorClassReference= ref;
		fEditor= editor;
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getHyperlinkRegion()
	 */
	@Override
	public IRegion getHyperlinkRegion() {
		return fRegion;
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#open()
	 */
	@Override
	public void open() {
		IStorage propertiesFile= null;
		try {
			ITypeBinding typeBinding= fAccessorClassReference.getBinding();
			propertiesFile= NLSHintHelper.getResourceBundle(typeBinding.getJavaElement().getJavaProject(), fAccessorClassReference);
		} catch (JavaModelException e) {
			// Don't open the file
		}
		openKeyInPropertiesFile(fKeyName, propertiesFile, fEditor);
	}

	/**
	 * Calculates the region of the NLS key in the properties file and reveals it in editor.
	 *
	 * @param keyName the NLS key
	 * @param propertiesFile the properties file, or <code>null</code>
	 * @param activeEditor the active editor part
	 */
	public static void openKeyInPropertiesFile(String keyName, IStorage propertiesFile, IEditorPart activeEditor) {
		if (propertiesFile == null) {
			showErrorInStatusLine(activeEditor, JavaEditorMessages.Editor_OpenPropertiesFile_error_fileNotFound_dialogMessage);
			return;
		}

		IEditorPart editor;
		try {
			editor= EditorUtility.openInEditor(propertiesFile, true);
		} catch (PartInitException e) {
			handleOpenPropertiesFileFailed(propertiesFile, activeEditor);
			return;
		}

		// Reveal the key in the editor
		IEditorInput editorInput = editor.getEditorInput();
		IDocument document= null;
		if (editor instanceof ITextEditor)
			document= ((ITextEditor)editor).getDocumentProvider().getDocument(editorInput);
		else {
			IFile file= editorInput.getAdapter(IFile.class);
			if (file != null) {
				IPath path= file.getFullPath();
				ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
				try {
					manager.connect(path, LocationKind.IFILE, null);
					try {
						ITextFileBuffer buffer= manager.getTextFileBuffer(path, LocationKind.IFILE);
						if (buffer != null)
							document= buffer.getDocument();
					} finally {
						manager.disconnect(path, LocationKind.IFILE, null);
					}
				} catch (CoreException ex) {
					JavaPlugin.log(ex);
				}
			}
		}

		// Find key in document
		boolean found= false;
		IRegion region= null;
		if (document != null) {
			FindReplaceDocumentAdapter finder= new FindReplaceDocumentAdapter(document);
			PropertyKeyHyperlinkDetector detector= new PropertyKeyHyperlinkDetector();
			detector.setContext(editor);
			String key= PropertyFileDocumentModel.escape(keyName, false);
			int offset= document.getLength() - 1;
			try {
				while (!found && offset >= 0) {
					region= finder.find(offset, key, false, true, false, false);
					if (region == null)
						offset= -1;
					else {
						// test whether it's the key
						IHyperlink[] hyperlinks= detector.detectHyperlinks(null, region, false);
						if (hyperlinks != null) {
							for (IHyperlink hyperlink : hyperlinks) {
								IRegion hyperlinkRegion= hyperlink.getHyperlinkRegion();
								found= key.equals(document.get(hyperlinkRegion.getOffset(), hyperlinkRegion.getLength()));
							}
						} else if (document instanceof IDocumentExtension3) {
							// Fall back: test using properties file partitioning
							ITypedRegion partition= ((IDocumentExtension3)document).getPartition(IPropertiesFilePartitions.PROPERTIES_FILE_PARTITIONING, region.getOffset(), false);
							found= IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())
							&& key.equals(document.get(partition.getOffset(), partition.getLength()).trim());
						}
						// Prevent endless loop (panic code, shouldn't be needed)
						if (offset == region.getOffset())
							offset= -1;
						else
							offset= region.getOffset();
					}
				}
			} catch (BadLocationException | BadPartitioningException e1) {
				found= false;
			}
		}
		if (found)
			EditorUtility.revealInEditor(editor, region);
		else {
			EditorUtility.revealInEditor(editor, 0, 0);
			showErrorInStatusLine(editor, Messages.format(JavaEditorMessages.Editor_OpenPropertiesFile_error_keyNotFound, keyName));
		}
	}

	/**
	 * Shows the given message as error on the status line.
	 *
	 * @param editor the editor part
	 * @param message message to be displayed
	 */
	private static void showErrorInStatusLine(IEditorPart editor, final String message) {
		final Display display= editor.getSite().getShell().getDisplay();
		display.beep();
		final IEditorStatusLine statusLine= editor.getAdapter(IEditorStatusLine.class);
		if (statusLine != null) {
			display.asyncExec(() -> statusLine.setMessage(true, message, null));
		}
	}

	/**
	 * Shows error message in status line if opening the properties file in editor fails.
	 *
	 * @param propertiesFile the propertiesFile
	 * @param editor the editor part
	 */
	private static void handleOpenPropertiesFileFailed(IStorage propertiesFile, IEditorPart editor) {
		showErrorInStatusLine(editor, Messages.format(JavaEditorMessages.Editor_OpenPropertiesFile_error_openEditor_dialogMessage, BasicElementLabels.getPathLabel(propertiesFile.getFullPath(), true)));
	}

/*
 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getTypeLabel()
 */
	@Override
	public String getTypeLabel() {
		return null;
	}

/*
 * @see org.eclipse.jdt.internal.ui.javaeditor.IHyperlink#getHyperlinkText()
 */
	@Override
	public String getHyperlinkText() {
		String bundleName= fAccessorClassReference.getResourceBundleName();
		String propertyFileName= BasicElementLabels.getResourceName((bundleName.substring(bundleName.lastIndexOf('.') + 1, bundleName.length()) + NLSRefactoring.PROPERTY_FILE_EXT));
		return Messages.format(JavaEditorMessages.Editor_OpenPropertiesFile_hyperlinkText, new Object[] { propertyFileName });
	}
}
