/*******************************************************************************
 * Copyright (c) 2000, 2014 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.part;

import java.util.HashMap;
import java.util.Iterator;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
import org.eclipse.ui.internal.WorkbenchMessages;

/**
 * Handles the redirection of the global actions Cut, Copy, Paste, Delete,
 * Select All, Find, Undo and Redo to either the current inline cell editor or
 * the part's supplied action handler.
 * <p>
 * This class may be instantiated; it is not intended to be subclassed.
 * </p>
 * <p>
 * Example usage:
 * </p>
 *
 * <pre>
 * actionHandler = new CellEditorActionHandler(this.getViewSite().getActionBars());
 * actionHandler.addCellEditor(textCellEditor1);
 * actionHandler.addCellEditor(textCellEditor2);
 * actionHandler.setSelectAllAction(selectAllAction);
 * </pre>
 *
 * @noextend This class is not intended to be subclassed by clients.
 */
public class CellEditorActionHandler {
	private CutActionHandler cellCutAction = new CutActionHandler();

	private CopyActionHandler cellCopyAction = new CopyActionHandler();

	private PasteActionHandler cellPasteAction = new PasteActionHandler();

	private DeleteActionHandler cellDeleteAction = new DeleteActionHandler();

	private SelectAllActionHandler cellSelectAllAction = new SelectAllActionHandler();

	private FindActionHandler cellFindAction = new FindActionHandler();

	private UndoActionHandler cellUndoAction = new UndoActionHandler();

	private RedoActionHandler cellRedoAction = new RedoActionHandler();

	private IAction cutAction;

	private IAction copyAction;

	private IAction pasteAction;

	private IAction deleteAction;

	private IAction selectAllAction;

	private IAction findAction;

	private IAction undoAction;

	private IAction redoAction;

	private IPropertyChangeListener cutActionListener = new ActionEnabledChangeListener(cellCutAction);

	private IPropertyChangeListener copyActionListener = new ActionEnabledChangeListener(cellCopyAction);

	private IPropertyChangeListener pasteActionListener = new ActionEnabledChangeListener(cellPasteAction);

	private IPropertyChangeListener deleteActionListener = new ActionEnabledChangeListener(cellDeleteAction);

	private IPropertyChangeListener selectAllActionListener = new ActionEnabledChangeListener(cellSelectAllAction);

	private IPropertyChangeListener findActionListener = new ActionEnabledChangeListener(cellFindAction);

	private IPropertyChangeListener undoActionListener = new ActionEnabledChangeListener(cellUndoAction);

	private IPropertyChangeListener redoActionListener = new ActionEnabledChangeListener(cellRedoAction);

	private CellEditor activeEditor;

	private IPropertyChangeListener cellListener = new CellChangeListener();

	private Listener controlListener = new ControlListener();

	private HashMap<Control, CellEditor> controlToEditor = new HashMap<>();

	private class ControlListener implements Listener {
		@Override
		public void handleEvent(Event event) {
			switch (event.type) {
			case SWT.Activate:
				activeEditor = controlToEditor.get(event.widget);
				if (activeEditor != null) {
					activeEditor.addPropertyChangeListener(cellListener);
				}
				updateActionsEnableState();
				break;
			case SWT.Deactivate:
				if (activeEditor != null) {
					activeEditor.removePropertyChangeListener(cellListener);
				}
				activeEditor = null;
				updateActionsEnableState();
				break;
			default:
				break;
			}
		}
	}

	private class ActionEnabledChangeListener implements IPropertyChangeListener {
		private IAction actionHandler;

		protected ActionEnabledChangeListener(IAction actionHandler) {
			super();
			this.actionHandler = actionHandler;
		}

		@Override
		public void propertyChange(PropertyChangeEvent event) {
			if (activeEditor != null) {
				return;
			}
			if (event.getProperty().equals(IAction.ENABLED)) {
				Boolean bool = (Boolean) event.getNewValue();
				actionHandler.setEnabled(bool.booleanValue());
				return;
			}
			// If the underlying action's text has changed, we need to
			// change the text. See
			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=154410
			if (event.getProperty().equals(IAction.TEXT)) {
				String text = (String) event.getNewValue();
				actionHandler.setText(text);
				return;
			}
			if (event.getProperty().equals(IAction.TOOL_TIP_TEXT)) {
				String text = (String) event.getNewValue();
				actionHandler.setToolTipText(text);
				return;
			}
		}
	}

	private class CellChangeListener implements IPropertyChangeListener {
		@Override
		public void propertyChange(PropertyChangeEvent event) {
			if (activeEditor == null) {
				return;
			}
			if (event.getProperty().equals(CellEditor.CUT)) {
				cellCutAction.setEnabled(activeEditor.isCutEnabled());
				return;
			}
			if (event.getProperty().equals(CellEditor.COPY)) {
				cellCopyAction.setEnabled(activeEditor.isCopyEnabled());
				return;
			}
			if (event.getProperty().equals(CellEditor.PASTE)) {
				cellPasteAction.setEnabled(activeEditor.isPasteEnabled());
				return;
			}
			if (event.getProperty().equals(CellEditor.DELETE)) {
				cellDeleteAction.setEnabled(activeEditor.isDeleteEnabled());
				return;
			}
			if (event.getProperty().equals(CellEditor.SELECT_ALL)) {
				cellSelectAllAction.setEnabled(activeEditor.isSelectAllEnabled());
				return;
			}
			if (event.getProperty().equals(CellEditor.FIND)) {
				cellFindAction.setEnabled(activeEditor.isFindEnabled());
				return;
			}
			if (event.getProperty().equals(CellEditor.UNDO)) {
				cellUndoAction.setEnabled(activeEditor.isUndoEnabled());
				return;
			}
			if (event.getProperty().equals(CellEditor.REDO)) {
				cellRedoAction.setEnabled(activeEditor.isRedoEnabled());
				return;
			}
		}
	}

	private class CutActionHandler extends Action {
		protected CutActionHandler() {
			setId("CellEditorCutActionHandler");//$NON-NLS-1$
			setEnabled(false);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_CUT_ACTION);
		}

		@Override
		public void runWithEvent(Event event) {
			if (activeEditor != null) {
				activeEditor.performCut();
				return;
			}
			if (cutAction != null) {
				cutAction.runWithEvent(event);
				return;
			}
		}

		public void updateEnabledState() {
			if (activeEditor != null) {
				setEnabled(activeEditor.isCutEnabled());
				return;
			}
			if (cutAction != null) {
				setEnabled(cutAction.isEnabled());
				return;
			}
			setEnabled(false);
		}
	}

	private class CopyActionHandler extends Action {
		protected CopyActionHandler() {
			setId("CellEditorCopyActionHandler");//$NON-NLS-1$
			setEnabled(false);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_COPY_ACTION);
		}

		@Override
		public void runWithEvent(Event event) {
			if (activeEditor != null) {
				activeEditor.performCopy();
				return;
			}
			if (copyAction != null) {
				copyAction.runWithEvent(event);
				return;
			}
		}

		public void updateEnabledState() {
			if (activeEditor != null) {
				setEnabled(activeEditor.isCopyEnabled());
				return;
			}
			if (copyAction != null) {
				setEnabled(copyAction.isEnabled());
				return;
			}
			setEnabled(false);
		}
	}

	private class PasteActionHandler extends Action {
		protected PasteActionHandler() {
			setId("CellEditorPasteActionHandler");//$NON-NLS-1$
			setEnabled(false);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_PASTE_ACTION);
		}

		@Override
		public void runWithEvent(Event event) {
			if (activeEditor != null) {
				activeEditor.performPaste();
				return;
			}
			if (pasteAction != null) {
				pasteAction.runWithEvent(event);
				return;
			}
		}

		public void updateEnabledState() {
			if (activeEditor != null) {
				setEnabled(activeEditor.isPasteEnabled());
				return;
			}
			if (pasteAction != null) {
				setEnabled(pasteAction.isEnabled());
				return;
			}
			setEnabled(false);
		}
	}

	private class DeleteActionHandler extends Action {
		protected DeleteActionHandler() {
			setId("CellEditorDeleteActionHandler");//$NON-NLS-1$
			setEnabled(false);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_DELETE_ACTION);
		}

		@Override
		public void runWithEvent(Event event) {
			if (activeEditor != null) {
				activeEditor.performDelete();
				return;
			}
			if (deleteAction != null) {
				deleteAction.runWithEvent(event);
				return;
			}
		}

		public void updateEnabledState() {
			if (activeEditor != null) {
				setEnabled(activeEditor.isDeleteEnabled());
				return;
			}
			if (deleteAction != null) {
				setEnabled(deleteAction.isEnabled());
				return;
			}
			setEnabled(false);
		}
	}

	private class SelectAllActionHandler extends Action {
		protected SelectAllActionHandler() {
			setId("CellEditorSelectAllActionHandler");//$NON-NLS-1$
			setEnabled(false);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_SELECT_ALL_ACTION);
		}

		@Override
		public void runWithEvent(Event event) {
			if (activeEditor != null) {
				activeEditor.performSelectAll();
				return;
			}
			if (selectAllAction != null) {
				selectAllAction.runWithEvent(event);
				return;
			}
		}

		public void updateEnabledState() {
			if (activeEditor != null) {
				setEnabled(activeEditor.isSelectAllEnabled());
				return;
			}
			if (selectAllAction != null) {
				setEnabled(selectAllAction.isEnabled());
				return;
			}
			setEnabled(false);
		}
	}

	private class FindActionHandler extends Action {
		protected FindActionHandler() {
			setId("CellEditorFindActionHandler");//$NON-NLS-1$
			setEnabled(false);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_FIND_ACTION);
		}

		@Override
		public void runWithEvent(Event event) {
			if (activeEditor != null) {
				activeEditor.performFind();
				return;
			}
			if (findAction != null) {
				findAction.runWithEvent(event);
				return;
			}
		}

		public void updateEnabledState() {
			if (activeEditor != null) {
				setEnabled(activeEditor.isFindEnabled());
				return;
			}
			if (findAction != null) {
				setEnabled(findAction.isEnabled());
				return;
			}
			setEnabled(false);
		}
	}

	private class UndoActionHandler extends Action {
		protected UndoActionHandler() {
			setId("CellEditorUndoActionHandler");//$NON-NLS-1$
			setEnabled(false);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_UNDO_ACTION);
		}

		@Override
		public void runWithEvent(Event event) {
			if (activeEditor != null) {
				activeEditor.performUndo();
				return;
			}
			if (undoAction != null) {
				undoAction.runWithEvent(event);
				return;
			}
		}

		public void updateEnabledState() {
			if (activeEditor != null) {
				setEnabled(activeEditor.isUndoEnabled());
				setText(WorkbenchMessages.Workbench_undo);
				setToolTipText(WorkbenchMessages.Workbench_undoToolTip);
				return;
			}
			if (undoAction != null) {
				setEnabled(undoAction.isEnabled());
				setText(undoAction.getText());
				setToolTipText(undoAction.getToolTipText());
				return;
			}
			setEnabled(false);
		}
	}

	private class RedoActionHandler extends Action {
		protected RedoActionHandler() {
			setId("CellEditorRedoActionHandler");//$NON-NLS-1$
			setEnabled(false);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IWorkbenchHelpContextIds.CELL_REDO_ACTION);
		}

		@Override
		public void runWithEvent(Event event) {
			if (activeEditor != null) {
				activeEditor.performRedo();
				return;
			}
			if (redoAction != null) {
				redoAction.runWithEvent(event);
				return;
			}
		}

		public void updateEnabledState() {
			if (activeEditor != null) {
				setEnabled(activeEditor.isRedoEnabled());
				setText(WorkbenchMessages.Workbench_redo);
				setToolTipText(WorkbenchMessages.Workbench_redoToolTip);
				return;
			}
			if (redoAction != null) {
				setEnabled(redoAction.isEnabled());
				setText(redoAction.getText());
				setToolTipText(redoAction.getToolTipText());
				return;
			}
			setEnabled(false);
		}
	}

	/**
	 * Creates a <code>CellEditor</code> action handler for the global Cut, Copy,
	 * Paste, Delete, Select All, Find, Undo, and Redo of the action bar.
	 *
	 * @param actionBar the action bar to register global action handlers.
	 */
	public CellEditorActionHandler(IActionBars actionBar) {
		super();
		actionBar.setGlobalActionHandler(ActionFactory.CUT.getId(), cellCutAction);
		actionBar.setGlobalActionHandler(ActionFactory.COPY.getId(), cellCopyAction);
		actionBar.setGlobalActionHandler(ActionFactory.PASTE.getId(), cellPasteAction);
		actionBar.setGlobalActionHandler(ActionFactory.DELETE.getId(), cellDeleteAction);
		actionBar.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), cellSelectAllAction);
		actionBar.setGlobalActionHandler(ActionFactory.FIND.getId(), cellFindAction);
		actionBar.setGlobalActionHandler(ActionFactory.UNDO.getId(), cellUndoAction);
		actionBar.setGlobalActionHandler(ActionFactory.REDO.getId(), cellRedoAction);
	}

	/**
	 * Adds a <code>CellEditor</code> to the handler so that the Cut, Copy, Paste,
	 * Delete, Select All, Find, Undo, and Redo actions are redirected to it when
	 * active.
	 *
	 * @param editor the <code>CellEditor</code>
	 */
	public void addCellEditor(CellEditor editor) {
		if (editor == null) {
			return;
		}

		Control control = editor.getControl();
		Assert.isNotNull(control);
		controlToEditor.put(control, editor);
		control.addListener(SWT.Activate, controlListener);
		control.addListener(SWT.Deactivate, controlListener);

		if (control.isFocusControl()) {
			activeEditor = editor;
			editor.addPropertyChangeListener(cellListener);
			updateActionsEnableState();
		}
	}

	/**
	 * Disposes of this action handler
	 */
	public void dispose() {
		setCutAction(null);
		setCopyAction(null);
		setPasteAction(null);
		setDeleteAction(null);
		setSelectAllAction(null);
		setFindAction(null);
		setUndoAction(null);
		setRedoAction(null);

		Iterator<Control> itr = controlToEditor.keySet().iterator();
		while (itr.hasNext()) {
			Control control = itr.next();
			if (!control.isDisposed()) {
				control.removeListener(SWT.Activate, controlListener);
				control.removeListener(SWT.Deactivate, controlListener);
			}
		}
		controlToEditor.clear();

		if (activeEditor != null) {
			activeEditor.removePropertyChangeListener(cellListener);
		}
		activeEditor = null;

	}

	/**
	 * Removes a <code>CellEditor</code> from the handler so that the Cut, Copy,
	 * Paste, Delete, Select All, Find Undo, and Redo actions are no longer
	 * redirected to it.
	 *
	 * @param editor the <code>CellEditor</code>
	 */
	public void removeCellEditor(CellEditor editor) {
		if (editor == null) {
			return;
		}

		if (activeEditor == editor) {
			activeEditor.removePropertyChangeListener(cellListener);
			activeEditor = null;
		}

		Control control = editor.getControl();
		if (control != null) {
			controlToEditor.remove(control);
			if (!control.isDisposed()) {
				control.removeListener(SWT.Activate, controlListener);
				control.removeListener(SWT.Deactivate, controlListener);
			}
		}
	}

	/**
	 * Sets the default <code>IAction</code> handler for the Copy action. This
	 * <code>IAction</code> is run only if no active cell editor control.
	 *
	 * @param action the <code>IAction</code> to run for the Copy action, or
	 *               <code>null</code> if not interested.
	 */
	public void setCopyAction(IAction action) {
		if (copyAction == action) {
			return;
		}

		if (copyAction != null) {
			copyAction.removePropertyChangeListener(copyActionListener);
		}

		copyAction = action;

		if (copyAction != null) {
			copyAction.addPropertyChangeListener(copyActionListener);
		}

		cellCopyAction.updateEnabledState();
	}

	/**
	 * Sets the default <code>IAction</code> handler for the Cut action. This
	 * <code>IAction</code> is run only if no active cell editor control.
	 *
	 * @param action the <code>IAction</code> to run for the Cut action, or
	 *               <code>null</code> if not interested.
	 */
	public void setCutAction(IAction action) {
		if (cutAction == action) {
			return;
		}

		if (cutAction != null) {
			cutAction.removePropertyChangeListener(cutActionListener);
		}

		cutAction = action;

		if (cutAction != null) {
			cutAction.addPropertyChangeListener(cutActionListener);
		}

		cellCutAction.updateEnabledState();
	}

	/**
	 * Sets the default <code>IAction</code> handler for the Delete action. This
	 * <code>IAction</code> is run only if no active cell editor control.
	 *
	 * @param action the <code>IAction</code> to run for the Delete action, or
	 *               <code>null</code> if not interested.
	 */
	public void setDeleteAction(IAction action) {
		if (deleteAction == action) {
			return;
		}

		if (deleteAction != null) {
			deleteAction.removePropertyChangeListener(deleteActionListener);
		}

		deleteAction = action;

		if (deleteAction != null) {
			deleteAction.addPropertyChangeListener(deleteActionListener);
		}

		cellDeleteAction.updateEnabledState();
	}

	/**
	 * Sets the default <code>IAction</code> handler for the Find action. This
	 * <code>IAction</code> is run only if no active cell editor control.
	 *
	 * @param action the <code>IAction</code> to run for the Find action, or
	 *               <code>null</code> if not interested.
	 */
	public void setFindAction(IAction action) {
		if (findAction == action) {
			return;
		}

		if (findAction != null) {
			findAction.removePropertyChangeListener(findActionListener);
		}

		findAction = action;

		if (findAction != null) {
			findAction.addPropertyChangeListener(findActionListener);
		}

		cellFindAction.updateEnabledState();
	}

	/**
	 * Sets the default <code>IAction</code> handler for the Paste action. This
	 * <code>IAction</code> is run only if no active cell editor control.
	 *
	 * @param action the <code>IAction</code> to run for the Paste action, or
	 *               <code>null</code> if not interested.
	 */
	public void setPasteAction(IAction action) {
		if (pasteAction == action) {
			return;
		}

		if (pasteAction != null) {
			pasteAction.removePropertyChangeListener(pasteActionListener);
		}

		pasteAction = action;

		if (pasteAction != null) {
			pasteAction.addPropertyChangeListener(pasteActionListener);
		}

		cellPasteAction.updateEnabledState();
	}

	/**
	 * Sets the default <code>IAction</code> handler for the Redo action. This
	 * <code>IAction</code> is run only if no active cell editor control.
	 *
	 * @param action the <code>IAction</code> to run for the Redo action, or
	 *               <code>null</code> if not interested.
	 */
	public void setRedoAction(IAction action) {
		if (redoAction == action) {
			return;
		}

		if (redoAction != null) {
			redoAction.removePropertyChangeListener(redoActionListener);
		}

		redoAction = action;

		if (redoAction != null) {
			redoAction.addPropertyChangeListener(redoActionListener);
		}

		cellRedoAction.updateEnabledState();
	}

	/**
	 * Sets the default <code>IAction</code> handler for the Select All action. This
	 * <code>IAction</code> is run only if no active cell editor control.
	 *
	 * @param action the <code>IAction</code> to run for the Select All action, or
	 *               <code>null</code> if not interested.
	 */
	public void setSelectAllAction(IAction action) {
		if (selectAllAction == action) {
			return;
		}

		if (selectAllAction != null) {
			selectAllAction.removePropertyChangeListener(selectAllActionListener);
		}

		selectAllAction = action;

		if (selectAllAction != null) {
			selectAllAction.addPropertyChangeListener(selectAllActionListener);
		}

		cellSelectAllAction.updateEnabledState();
	}

	/**
	 * Sets the default <code>IAction</code> handler for the Undo action. This
	 * <code>IAction</code> is run only if no active cell editor control.
	 *
	 * @param action the <code>IAction</code> to run for the Undo action, or
	 *               <code>null</code> if not interested.
	 */
	public void setUndoAction(IAction action) {
		if (undoAction == action) {
			return;
		}

		if (undoAction != null) {
			undoAction.removePropertyChangeListener(undoActionListener);
		}

		undoAction = action;

		if (undoAction != null) {
			undoAction.addPropertyChangeListener(undoActionListener);
		}

		cellUndoAction.updateEnabledState();
	}

	/**
	 * Updates the enable state of the Cut, Copy, Paste, Delete, Select All, Find,
	 * Undo, and Redo action handlers
	 */
	private void updateActionsEnableState() {
		cellCutAction.updateEnabledState();
		cellCopyAction.updateEnabledState();
		cellPasteAction.updateEnabledState();
		cellDeleteAction.updateEnabledState();
		cellSelectAllAction.updateEnabledState();
		cellFindAction.updateEnabledState();
		cellUndoAction.updateEnabledState();
		cellRedoAction.updateEnabledState();
	}
}
