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

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuCreator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.IActionDelegate;
import org.eclipse.ui.IActionDelegate2;
import org.eclipse.ui.IActionDelegateWithEvent;
import org.eclipse.ui.INullSelectionListener;
import org.eclipse.ui.IPluginContribution;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.ui.SelectionEnabler;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
import org.eclipse.ui.internal.util.BundleUtility;
import org.eclipse.ui.internal.util.Util;

/**
 * A PluginAction is a proxy for an action extension.
 *
 * At startup we read the registry and create a PluginAction for each action
 * extension. This plugin action looks like the real action ( label, icon, etc )
 * and acts as a proxy for the action until invoked. At that point the proxy
 * will instantiate the real action and delegate the run method to the real
 * action. This makes it possible to load the action extension lazily.
 *
 * Occasionally the class will ask if it is OK to load the delegate (on
 * selection changes). If the plugin containing the action extension has been
 * loaded then the action extension itself will be instantiated.
 */

public abstract class PluginAction extends Action
		implements ISelectionListener, ISelectionChangedListener, INullSelectionListener, IPluginContribution {
	private IActionDelegate delegate;

	private SelectionEnabler enabler;

	private ISelection selection;

	private IConfigurationElement configElement;

	private String pluginId;

	private String runAttribute = IWorkbenchRegistryConstants.ATT_CLASS;

	private static int actionCount = 0;

	/**
	 * PluginAction constructor.
	 *
	 * @param actionElement the element
	 * @param id            the identifier
	 * @param style         the style bits
	 */
	public PluginAction(IConfigurationElement actionElement, String id, int style) {
		super(null, style);

		this.configElement = actionElement;

		if (id != null) {
			setId(id);
		} else {
			// Create unique action id.
			setId("PluginAction." + actionCount); //$NON-NLS-1$
			++actionCount;
		}

		String defId = actionElement.getAttribute(IWorkbenchRegistryConstants.ATT_DEFINITION_ID);
		setActionDefinitionId(defId);

		pluginId = configElement.getContributor().getName();

		// read initialEnabled attribute
		String initialEnabledAttr = configElement.getAttribute(IWorkbenchRegistryConstants.ATT_INITIAL_ENABLED);
		if (initialEnabledAttr != null) {
			boolean initialEnabled = Boolean.parseBoolean(initialEnabledAttr);
			setEnabled(initialEnabled);
		}

		// Read enablement declaration.
		if (configElement.getAttribute(IWorkbenchRegistryConstants.ATT_ENABLES_FOR) != null) {
			enabler = new SelectionEnabler(configElement);
		} else {
			IConfigurationElement[] kids = configElement.getChildren(IWorkbenchRegistryConstants.TAG_ENABLEMENT);
			IConfigurationElement[] kids2 = configElement.getChildren(IWorkbenchRegistryConstants.TAG_SELECTION);
			if (kids.length > 0 || kids2.length > 0) {
				enabler = new SelectionEnabler(configElement);
			}
		}

		// Give enabler or delegate a chance to adjust enable state
		selectionChanged(StructuredSelection.EMPTY);
	}

	/**
	 * Creates the delegate and refreshes its enablement.
	 */
	protected final void createDelegate() {
		// The runAttribute is null if delegate creation failed previously...
		if (delegate == null && runAttribute != null) {
			try {
				Object obj = WorkbenchPlugin.createExtension(configElement, runAttribute);
				delegate = validateDelegate(obj);
				initDelegate();
				refreshEnablement();
			} catch (Throwable e) {
				runAttribute = null;
				IStatus status = null;
				if (e instanceof CoreException) {
					status = ((CoreException) e).getStatus();
				} else {
					status = StatusUtil.newStatus(IStatus.ERROR, "Internal plug-in action delegate error on creation.", //$NON-NLS-1$
							e);
				}
				String id = configElement.getAttribute(IWorkbenchRegistryConstants.ATT_ID);
				WorkbenchPlugin.log("Could not create action delegate for id: " + id, status); //$NON-NLS-1$
				return;
			}
		}
	}

	/**
	 * Validates the object is a delegate of the expected type. Subclasses can
	 * override to check for specific delegate types.
	 * <p>
	 * <b>Note:</b> Calls to the object are not allowed during this method.
	 * </p>
	 *
	 * @param obj a possible action delegate implementation
	 * @return the <code>IActionDelegate</code> implementation for the object
	 * @throws WorkbenchException if not of the expected delegate type
	 */
	protected IActionDelegate validateDelegate(Object obj) throws WorkbenchException {
		if (obj instanceof IActionDelegate) {
			return (IActionDelegate) obj;
		}

		throw new WorkbenchException("Action must implement IActionDelegate"); //$NON-NLS-1$
	}

	/**
	 * Initialize the action delegate by calling its lifecycle method. Subclasses
	 * may override but must call this implementation first.
	 */
	protected void initDelegate() {
		if (delegate instanceof IActionDelegate2) {
			((IActionDelegate2) delegate).init(this);
		}
	}

	/**
	 * Returns the action delegate if created. Can be <code>null</code> if the
	 * delegate is not created yet or if previous delegate creation failed.
	 */
	protected IActionDelegate getDelegate() {
		return delegate;
	}

	/**
	 * Returns true if the declaring plugin has been loaded and there is no need to
	 * delay creating the delegate any more.
	 */
	protected boolean isOkToCreateDelegate() {
		// test if the plugin has loaded
		String bundleId = configElement.getContributor().getName();
		return BundleUtility.isActive(bundleId);
	}

	/**
	 * Refresh the action enablement.
	 */
	protected void refreshEnablement() {
		if (enabler != null) {
			setEnabled(enabler.isEnabledForSelection(selection));
		}
		if (delegate != null) {
			delegate.selectionChanged(this, selection);
		}
	}

	@Override
	public void run() {
		runWithEvent(null);
	}

	@Override
	public void runWithEvent(Event event) {
		// this message dialog is problematic.
		if (delegate == null) {
			createDelegate();
			if (delegate == null) {
				MessageDialog.openInformation(Util.getShellToParentOn(), WorkbenchMessages.Information,
						WorkbenchMessages.PluginAction_operationNotAvailableMessage);
				return;
			}
			if (!isEnabled()) {
				MessageDialog.openInformation(Util.getShellToParentOn(), WorkbenchMessages.Information,
						WorkbenchMessages.PluginAction_disabledMessage);
				return;
			}
		}

		if (event != null) {
			if (delegate instanceof IActionDelegate2) {
				((IActionDelegate2) delegate).runWithEvent(this, event);
				return;
			}
			// Keep for backward compatibility with R2.0
			if (delegate instanceof IActionDelegateWithEvent) {
				((IActionDelegateWithEvent) delegate).runWithEvent(this, event);
				return;
			}
		}

		delegate.run(this);
	}

	/**
	 * Handles selection change. If rule-based enabled is defined, it will be first
	 * to call it. If the delegate is loaded, it will also be given a chance.
	 *
	 * @param newSelection the new selection
	 */
	public void selectionChanged(ISelection newSelection) {
		// Update selection.
		selection = newSelection;
		if (selection == null) {
			selection = StructuredSelection.EMPTY;
		}

		// The selection is passed to the delegate as-is without
		// modification. If the selection needs to be modified
		// the action contributors should do so.

		// If the delegate can be loaded, do so.
		// Otherwise, just update the enablement.
		if (delegate == null && isOkToCreateDelegate()) {
			createDelegate();
		} else {
			refreshEnablement();
		}
	}

	/**
	 * The <code>SelectionChangedEventAction</code> implementation of this
	 * <code>ISelectionChangedListener</code> method calls
	 * <code>selectionChanged(IStructuredSelection)</code> when the selection is a
	 * structured one.
	 */
	@Override
	public void selectionChanged(SelectionChangedEvent event) {
		ISelection sel = event.getSelection();
		selectionChanged(sel);
	}

	/**
	 * The <code>SelectionChangedEventAction</code> implementation of this
	 * <code>ISelectionListener</code> method calls
	 * <code>selectionChanged(IStructuredSelection)</code> when the selection is a
	 * structured one. Subclasses may extend this method to react to the change.
	 */
	@Override
	public void selectionChanged(IWorkbenchPart part, ISelection sel) {
		selectionChanged(sel);
	}

	/**
	 * For testing purposes only.
	 *
	 * @return the selection
	 * @since 3.1
	 */
	public ISelection getSelection() {
		return selection;
	}

	/**
	 * Returns the action identifier this action overrides. Default implementation
	 * returns <code>null</code>.
	 *
	 * @return the action identifier to override or <code>null</code>
	 */
	public String getOverrideActionId() {
		return null;
	}

	/**
	 * @return the IConfigurationElement used to create this PluginAction.
	 *
	 * @since 3.0
	 */
	protected IConfigurationElement getConfigElement() {
		return configElement;
	}

	@Override
	public String getLocalId() {
		return getId();
	}

	@Override
	public String getPluginId() {
		return pluginId;
	}

	/**
	 * Disposes the delegate, if created.
	 *
	 * @since 3.1
	 */
	public void disposeDelegate() {
		// avoid calling dispose() twice if the delegate implements
		// both IActionDelegate2 and IWorkbenchWindowActionDelegate
		if (getDelegate() instanceof IActionDelegate2) {
			((IActionDelegate2) getDelegate()).dispose();
		} else if (getDelegate() instanceof IWorkbenchWindowActionDelegate) {
			((IWorkbenchWindowActionDelegate) getDelegate()).dispose();
		}
		delegate = null;
	}

	/**
	 * Disposes this plugin action.
	 *
	 * @since 3.1
	 */
	public void dispose() {
		disposeDelegate();
		selection = null;
	}

	@Override
	public IMenuCreator getMenuCreator() {
		// now that action contribution item defers asking for the menu
		// creator until its ready o show the menu, asking for the menu
		// creator is time to instantiate the delegate
		if (getDelegate() == null) {
			createDelegate();
		}
		return super.getMenuCreator();
	}
}
