/*******************************************************************************
 * Copyright (c) 2000, 2015 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
 *******************************************************************************/

package org.eclipse.jdt.internal.ui.propertiesfileeditor;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.StringCharacterIterator;
import java.util.Properties;

import org.eclipse.swt.widgets.Display;

import org.eclipse.core.runtime.CoreException;

import org.eclipse.core.resources.IStorage;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;

import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IStorageEditorInput;

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

import org.eclipse.jdt.core.IJarEntryResource;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaModelException;


/**
 * Properties key hyperlink detector.
 *
 * @since 3.1
 */
public class PropertyKeyHyperlinkDetector extends AbstractHyperlinkDetector {

	/*
	 * @see org.eclipse.jface.text.hyperlink.IHyperlinkDetector#detectHyperlinks(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion, boolean)
	 */
	@Override
	public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region, boolean canShowMultipleHyperlinks) {
		ITextEditor textEditor= getAdapter(ITextEditor.class);
		if (region == null || textEditor == null)
			return null;

		IEditorSite site= textEditor.getEditorSite();
		if (site == null)
			return null;

		int offset= region.getOffset();
		if (!checkEnabled(textEditor, offset))
			return null;

		ITypedRegion partition= null;
		try {
			IStorageEditorInput storageEditorInput= (IStorageEditorInput)textEditor.getEditorInput();
			IDocument document= textEditor.getDocumentProvider().getDocument(storageEditorInput);
			if (document instanceof IDocumentExtension3)
				partition= ((IDocumentExtension3)document).getPartition(IPropertiesFilePartitions.PROPERTIES_FILE_PARTITIONING, offset, false);

			// Check whether it is the correct partition
			if (partition == null || !IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) {
				return null;
			}

			// Check whether the partition covers the selection
			if (offset + region.getLength() > partition.getOffset() + partition.getLength()) {
				return null;
			}

			// Extract the key from the partition (which contains key and assignment
			String key= document.get(partition.getOffset(), partition.getLength());

			String realKey= key.trim();
			int delta= key.indexOf(realKey);

			String unicodeKey= getUnicodeString(realKey);
			// Check whether the key is valid
			Properties properties= new Properties();
			properties.load(new ByteArrayInputStream(document.get().getBytes()));
			if (properties.getProperty(unicodeKey) == null) {
				return null;
			}

			return new PropertyKeyHyperlink[] { new PropertyKeyHyperlink(new Region(partition.getOffset() + delta, realKey.length()), realKey, textEditor) };

		} catch (BadLocationException | BadPartitioningException | IOException ex) {
			return null;
		} catch (IllegalArgumentException ex) {
			showErrorInStatusLine(ex.getLocalizedMessage(), textEditor);
			return null;
		}
	}

	private String getUnicodeString(String key) {
		StringCharacterIterator iter= new StringCharacterIterator(key);
		StringBuilder result= new StringBuilder();
		while (iter.getIndex() < iter.getEndIndex()) {
			char c= iter.current();
			if (c == '\\') {
				iter.next();
				c= iter.current();
				if (c == 'u') {
					StringBuilder unicode= new StringBuilder();
					unicode.append(iter.next());
					unicode.append(iter.next());
					unicode.append(iter.next());
					unicode.append(iter.next());
					c= (char)Integer.parseInt(unicode.toString(), 16);
				}
			}
			result.append(c);
			iter.next();
		}
		return result.toString();
	}

	static boolean checkEnabled(ITextEditor textEditor, int offset) {
		if (offset < 0)
			return false;

		IEditorInput editorInput= textEditor.getEditorInput();
		return editorInput instanceof IFileEditorInput || (editorInput instanceof IStorageEditorInput && isEclipseNLSAvailable((IStorageEditorInput) editorInput));
	}

	private static boolean isEclipseNLSAvailable(IStorageEditorInput editorInput) {
		IStorage storage;
		try {
			storage= editorInput.getStorage();
		} catch (CoreException ex) {
			return false;
		}
		if (!(storage instanceof IJarEntryResource))
			return false;

		IJavaProject javaProject= ((IJarEntryResource) storage).getPackageFragmentRoot().getJavaProject();

		if (javaProject == null || !javaProject.exists())
			return false;

		try {
			return javaProject.findType("org.eclipse.osgi.util.NLS") != null; //$NON-NLS-1$
		} catch (JavaModelException e) {
			return false;
		}
	}

	private void showErrorInStatusLine(final String message, ITextEditor textEditor) {
		Display display= textEditor.getEditorSite().getShell().getDisplay();
		display.beep();
		final IEditorStatusLine statusLine= textEditor.getAdapter(IEditorStatusLine.class);
		if (statusLine != null) {
			display.asyncExec(() -> statusLine.setMessage(true, message, null));
		}
	}
}
