/*******************************************************************************
 * 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.javaeditor;


import java.lang.reflect.InvocationTargetException;
import java.util.Comparator;

import java.text.Collator;

import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.ISchedulingRule;

import org.eclipse.core.resources.ResourcesPlugin;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.window.Window;

import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IEditingSupport;
import org.eclipse.jface.text.IEditingSupportRegistry;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.source.ISourceViewer;

import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import org.eclipse.ui.dialogs.FilteredList;
import org.eclipse.ui.progress.IProgressService;

import org.eclipse.ui.texteditor.IUpdate;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.search.TypeNameMatch;

import org.eclipse.jdt.internal.corext.codemanipulation.AddImportsOperation;
import org.eclipse.jdt.internal.corext.codemanipulation.AddImportsOperation.IChooseImportQuery;
import org.eclipse.jdt.internal.corext.util.History;
import org.eclipse.jdt.internal.corext.util.QualifiedTypeNameHistory;

import org.eclipse.jdt.ui.IWorkingCopyManager;

import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.actions.ActionUtil;
import org.eclipse.jdt.internal.ui.actions.WorkbenchRunnableAdapter;
import org.eclipse.jdt.internal.ui.util.ElementValidator;
import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
import org.eclipse.jdt.internal.ui.util.TypeNameMatchLabelProvider;


public class AddImportOnSelectionAction extends Action implements IUpdate {

	private static final AddImportComparator ADD_IMPORT_COMPARATOR= new AddImportComparator();

	private static final class AddImportComparator implements Comparator<String> {

		@Override
		public int compare(String o1, String o2) {
			if (o1.equals(o2))
				return 0;

			History<String, String> history= QualifiedTypeNameHistory.getDefault();

			int pos1= history.getPosition(o1);
			int pos2= history.getPosition(o2);

			if (pos1 == pos2)
				return Collator.getInstance().compare(o1, o2);

			if (pos1 > pos2) {
				return -1;
			} else {
				return 1;
			}
		}

	}

	private CompilationUnitEditor fEditor;

	public AddImportOnSelectionAction(CompilationUnitEditor editor) {
		super(JavaEditorMessages.AddImportOnSelection_label);
		setToolTipText(JavaEditorMessages.AddImportOnSelection_tooltip);
		setDescription(JavaEditorMessages.AddImportOnSelection_description);
		fEditor= editor;
		PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.ADD_IMPORT_ON_SELECTION_ACTION);
		setEnabled(getCompilationUnit() != null);
	}

	@Override
	public void update() {
		setEnabled(fEditor != null && getCompilationUnit() != null);
	}

	private ICompilationUnit getCompilationUnit () {
		if (fEditor == null) {
			return null;
		}
		IWorkingCopyManager manager= JavaPlugin.getDefault().getWorkingCopyManager();
		return manager.getWorkingCopy(fEditor.getEditorInput());
	}

	/*
	 * @see org.eclipse.jface.action.IAction#run()
	 */
	@Override
	public void run() {
		final ICompilationUnit cu= getCompilationUnit();
		if (cu == null || fEditor == null)
			return;
		if (!ElementValidator.checkValidateEdit(cu, getShell(), JavaEditorMessages.AddImportOnSelection_error_title))
			return;
		if (!ActionUtil.isEditable(fEditor))
			return;

		ISelection selection= fEditor.getSelectionProvider().getSelection();
		if (selection instanceof ITextSelection) {
			final ITextSelection textSelection= (ITextSelection) selection;
			AddImportOnSelectionAction.SelectTypeQuery query= new SelectTypeQuery(getShell());
			AddImportsOperation op= new AddImportsOperation(cu, textSelection.getOffset(), textSelection.getLength(), query, false);
			IEditingSupport helper= createViewerHelper(textSelection, query);
			try {
				registerHelper(helper);
				IProgressService progressService= PlatformUI.getWorkbench().getProgressService();
				progressService.runInUI(fEditor.getSite().getWorkbenchWindow(), new WorkbenchRunnableAdapter(op, op.getScheduleRule()), op.getScheduleRule());
				IStatus status= op.getStatus();
				if (!status.isOK()) {
					IStatusLineManager manager= getStatusLineManager();
					if (manager != null) {
						manager.setMessage(status.getMessage());
					}
				}
			} catch (InvocationTargetException e) {
				ExceptionHandler.handle(e, getShell(), JavaEditorMessages.AddImportOnSelection_error_title, null);
			} catch (InterruptedException e) {
				// Do nothing. Operation has been canceled.
			} finally {
				deregisterHelper(helper);
			}
		}
	}

	private IEditingSupport createViewerHelper(final ITextSelection selection, final SelectTypeQuery query) {
		return new IEditingSupport() {

			@Override
			public boolean isOriginator(DocumentEvent event, IRegion subjectRegion) {
				return subjectRegion.getOffset() <= selection.getOffset() + selection.getLength() &&  selection.getOffset() <= subjectRegion.getOffset() + subjectRegion.getLength();
			}

			@Override
			public boolean ownsFocusShell() {
				return query.isShowing();
			}

		};
	}

	private void registerHelper(IEditingSupport helper) {
		ISourceViewer viewer= fEditor.getViewer();
		if (viewer instanceof IEditingSupportRegistry) {
			IEditingSupportRegistry registry= (IEditingSupportRegistry) viewer;
			registry.register(helper);
		}
	}

	private void deregisterHelper(IEditingSupport helper) {
		ISourceViewer viewer= fEditor.getViewer();
		if (viewer instanceof IEditingSupportRegistry) {
			IEditingSupportRegistry registry= (IEditingSupportRegistry) viewer;
			registry.unregister(helper);
		}
	}

	private Shell getShell() {
		return fEditor.getSite().getShell();
	}

	/**
	 * Creates a import query that shows a dialog for conflics.
	 *
	 * @param shell the parent shell
	 * @return the created mport query
	 */
	public static IChooseImportQuery newDialogQuery(Shell shell) {
		return new SelectTypeQuery(shell);
	}

	private static class SelectTypeQuery implements IChooseImportQuery {

		private final Shell fShell;
		private boolean fIsShowing;

		public SelectTypeQuery(Shell shell) {
			fShell= shell;
		}

		@Override
		public TypeNameMatch chooseImport(TypeNameMatch[] results, String containerName) {
			int nResults= results.length;

			if (nResults == 0) {
				return null;
			} else if (nResults == 1) {
				return results[0];
			}

			if (containerName.length() != 0) {
				for (int i= 0; i < nResults; i++) {
					TypeNameMatch curr= results[i];
					if (containerName.equals(curr.getTypeContainerName())) {
						return curr;
					}
				}
			}
			fIsShowing= true;
			ElementListSelectionDialog dialog= new ElementListSelectionDialog(fShell, new TypeNameMatchLabelProvider(TypeNameMatchLabelProvider.SHOW_FULLYQUALIFIED)) {
				@Override
				protected FilteredList createFilteredList(Composite parent) {
					FilteredList filteredList= super.createFilteredList(parent);
					filteredList.setComparator(ADD_IMPORT_COMPARATOR);
					return filteredList;
				}
			};
			dialog.setTitle(JavaEditorMessages.AddImportOnSelection_dialog_title);
			dialog.setMessage(JavaEditorMessages.AddImportOnSelection_dialog_message);
			dialog.setElements(results);
			if (dialog.open() == Window.OK) {
				fIsShowing= false;
				TypeNameMatch result= (TypeNameMatch) dialog.getFirstResult();
				QualifiedTypeNameHistory.remember(result.getFullyQualifiedName());
				return result;
			}
			fIsShowing= false;
			return null;
		}

		boolean isShowing() {
			return fIsShowing;
		}
	}

	private IStatusLineManager getStatusLineManager() {
		return fEditor.getEditorSite().getActionBars().getStatusLineManager();
	}

	/**
	 * @return Returns the scheduling rule for this operation
	 */
	public ISchedulingRule getScheduleRule() {
		return ResourcesPlugin.getWorkspace().getRoot();
	}

}
