/*******************************************************************************
 * Copyright (c) 2000, 2008 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.ui.texteditor.quickdiff;

import java.util.Iterator;
import java.util.List;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.ISelection;

import org.eclipse.ui.IEditorActionDelegate;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.internal.texteditor.quickdiff.QuickDiffMessages;
import org.eclipse.ui.internal.texteditor.quickdiff.QuickDiffRestoreAction;
import org.eclipse.ui.internal.texteditor.quickdiff.ReferenceSelectionAction;
import org.eclipse.ui.internal.texteditor.quickdiff.RestoreAction;
import org.eclipse.ui.internal.texteditor.quickdiff.RevertBlockAction;
import org.eclipse.ui.internal.texteditor.quickdiff.RevertLineAction;
import org.eclipse.ui.internal.texteditor.quickdiff.RevertSelectionAction;

import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorExtension;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
import org.eclipse.ui.texteditor.IUpdate;

/**
 * Action to toggle the line number bar's quick diff display. When turned on, quick diff shows
 * the changes relative to the saved version of the file.
 *
 * @since 3.0
 */
public class QuickDiffToggleAction implements IEditorActionDelegate, IUpdate {

	/** The editor we are working on. */
	ITextEditor fEditor= null;

	/** Our UI proxy action. */
	IAction fProxy;

	/** The restore actions associated with this toggle action. */
	QuickDiffRestoreAction[] fRestoreActions=
		new QuickDiffRestoreAction[] {
			new RevertSelectionAction(fEditor, true),
			new RevertBlockAction(fEditor, true),
			new RevertLineAction(fEditor, true),
			new RestoreAction(fEditor, true),
		};

	/** The menu listener that adds the ruler context menu. */
	private IMenuListener fListener= new IMenuListener() {
		/** Group name for additions, in CompilationUnitEditor... */
		private static final String GROUP_ADD= "add"; //$NON-NLS-1$
		/** Group name for debug contributions */
		private static final String GROUP_DEBUB= "debug"; //$NON-NLS-1$
		private static final String GROUP_QUICKDIFF= "quickdiff"; //$NON-NLS-1$
		private static final String MENU_ID= "quickdiff.menu"; //$NON-NLS-1$
		private static final String GROUP_RESTORE= "restore"; //$NON-NLS-1$

		@Override
		public void menuAboutToShow(IMenuManager manager) {
			// update the toggle action itself
			update();

			IMenuManager menu= (IMenuManager)manager.find(MENU_ID);
			// only add menu if it isn't there yet
			if (menu == null) {
				/* HACK: pre-install menu groups
				 * This is needed since we get the blank context menu, but want to show up
				 * in the same position as the extension-added QuickDiffToggleAction.
				 * The extension is added at the end (naturally), but other menus (debug, add)
				 * don't add themselves to MB_ADDITIONS or alike, but rather to the end, too. So
				 * we pre-install their respective menu groups here.
				 */
				if (manager.find(GROUP_DEBUB) == null)
					manager.insertBefore(IWorkbenchActionConstants.MB_ADDITIONS, new Separator(GROUP_DEBUB));
				if (manager.find(GROUP_ADD) == null)
					manager.insertAfter(IWorkbenchActionConstants.MB_ADDITIONS, new Separator(GROUP_ADD));
				if (manager.find(GROUP_RESTORE) == null)
					manager.insertAfter(GROUP_ADD, new Separator(GROUP_RESTORE));
				if (manager.find(GROUP_QUICKDIFF) == null)
					manager.insertAfter(GROUP_RESTORE, new Separator(GROUP_QUICKDIFF));

				// create quickdiff menu
				menu= new MenuManager(QuickDiffMessages.quickdiff_menu_label, MENU_ID);
				List<ReferenceProviderDescriptor> descriptors= new QuickDiff().getReferenceProviderDescriptors();
				for (Iterator<ReferenceProviderDescriptor> it= descriptors.iterator(); it.hasNext();) {
					ReferenceProviderDescriptor desc= it.next();
					ReferenceSelectionAction action= new ReferenceSelectionAction(desc, fEditor);
					if (action.isEnabled())
						menu.add(action);
				}
				manager.appendToGroup(GROUP_QUICKDIFF, menu);

				// create restore menu if this action is enabled
				if (isConnected()) {
					for (int i= 0; i < fRestoreActions.length; i++) {
						fRestoreActions[i].update();
					}
					// only add block action if selection action is not enabled
					if (fRestoreActions[0].isEnabled())
						manager.appendToGroup(GROUP_RESTORE, fRestoreActions[0]);
					else if (fRestoreActions[1].isEnabled())
						manager.appendToGroup(GROUP_RESTORE, fRestoreActions[1]);
					if (fRestoreActions[2].isEnabled())
						manager.appendToGroup(GROUP_RESTORE, fRestoreActions[2]);
					if (fRestoreActions[3].isEnabled())
						manager.appendToGroup(GROUP_RESTORE, fRestoreActions[3]);
				}
			}
		}
	};

	@Override
	public void setActiveEditor(IAction action, IEditorPart targetEditor) {
		fProxy= action;
		removePopupMenu();
		if (targetEditor instanceof ITextEditor) {
			fEditor= (ITextEditor)targetEditor;
		} else
			fEditor= null;
		for (int i= 0; i < fRestoreActions.length; i++) {
			fRestoreActions[i].setEditor(fEditor);
		}
		setPopupMenu();
	}

	/**
	 * Removes the ruler context menu listener from the current editor.
	 */
	private void removePopupMenu() {
		if (!(fEditor instanceof ITextEditorExtension))
			return;
		((ITextEditorExtension)fEditor).removeRulerContextMenuListener(fListener);
	}

	/**
	 * Installs a submenu with <code>fEditor</code>'s ruler context menu that contains the choices
	 * for the quick diff reference. This allows the toggle action to lazily install the menu once
	 * quick diff has been enabled.
	 */
	private void setPopupMenu() {
		if (!(fEditor instanceof ITextEditorExtension))
			return;
		((ITextEditorExtension)fEditor).addRulerContextMenuListener(fListener);
	}

	/**
	 * States whether this toggle action has been installed and a incremental differ has been
	 * installed with the line number bar.
	 *
	 * @return <code>true</code> if a differ has been installed on <code>fEditor</code>.
	 */
	boolean isConnected() {
		if (!(fEditor instanceof ITextEditorExtension3))
			return false;
		return ((ITextEditorExtension3)fEditor).isChangeInformationShowing();
	}

	@Override
	public void run(IAction action) {
		fProxy= action;
		if (fEditor == null)
			return;

		if (fEditor instanceof ITextEditorExtension3) {
			ITextEditorExtension3 extension= (ITextEditorExtension3)fEditor;
			extension.showChangeInformation(!extension.isChangeInformationShowing());
		}
	}

	@Override
	public void selectionChanged(IAction action, ISelection selection) {
		fProxy= action;
	}

	@Override
	public void update() {
		if (fProxy == null)
			return;
		if (isConnected())
			fProxy.setText(QuickDiffMessages.quickdiff_toggle_disable);
		else
			fProxy.setText(QuickDiffMessages.quickdiff_toggle_enable);
	}

}
