/*******************************************************************************
 * Copyright (c) 2006, 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.commands;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.commands.Category;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.IExecutionListener;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.commands.ParameterType;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.SerializationException;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.internal.workbench.renderers.swt.IUpdateService;
import org.eclipse.e4.ui.model.application.ui.menu.MItem;
import org.eclipse.e4.ui.workbench.IPresentationEngine;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.commands.IElementReference;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.menus.MenuHelper;
import org.eclipse.ui.menus.UIElement;
import org.eclipse.ui.services.IServiceLocator;

/**
 * A command service which delegates almost all responsibility to the parent
 * service.
 * <p>
 * This class is not intended for use outside of the
 * <code>org.eclipse.ui.workbench</code> plug-in.
 * </p>
 *
 * @since 3.2
 */
public class SlaveCommandService implements ICommandService, IUpdateService {

	private Collection fExecutionListeners = new ArrayList();

	/**
	 * The collection of ICallbackReferences added through this service.
	 *
	 * @since 3.3
	 */
	private Set fCallbackCache = new HashSet();

	private ICommandService fParentService;

	/**
	 * The scoping constant added to callback registrations submitted through this
	 * service.
	 *
	 * @since 3.3
	 */
	private String fScopingName;

	/**
	 * The object to scope. In theory, the service locator that would find this
	 * service.
	 *
	 * @since 3.3
	 */
	private IServiceLocator fScopingValue;

	private IEclipseContext fContext;

	/**
	 * Build the slave service.
	 *
	 * @param parent the parent service. This must not be <code>null</code>.
	 */
	public SlaveCommandService(ICommandService parent, String scopeName, IServiceLocator scopeValue) {
		this(parent, scopeName, scopeValue, null);
	}

	public SlaveCommandService(ICommandService parent, String scopeName, IServiceLocator scopeValue,
			IEclipseContext context) {
		if (parent == null) {
			throw new NullPointerException("The parent command service must not be null"); //$NON-NLS-1$
		}
		fParentService = parent;
		fScopingName = scopeName;
		fScopingValue = scopeValue;
		fContext = context;
	}

	@Override
	public void addExecutionListener(IExecutionListener listener) {
		if (!fExecutionListeners.contains(listener)) {
			fExecutionListeners.add(listener);
		}
		fParentService.addExecutionListener(listener);
	}

	@Override
	public void defineUncategorizedCategory(String name, String description) {
		fParentService.defineUncategorizedCategory(name, description);
	}

	@Override
	public ParameterizedCommand deserialize(String serializedParameterizedCommand)
			throws NotDefinedException, SerializationException {
		return fParentService.deserialize(serializedParameterizedCommand);
	}

	@Override
	public void dispose() {
		if (!fExecutionListeners.isEmpty()) {
			Object[] array = fExecutionListeners.toArray();
			for (Object element : array) {
				removeExecutionListener((IExecutionListener) element);
			}
			fExecutionListeners.clear();
		}
		if (!fCallbackCache.isEmpty()) {
			Object[] array = fCallbackCache.toArray();
			for (Object element : array) {
				unregisterElement((IElementReference) element);
			}
		}
		fScopingValue = null;
		fContext = null;
		fParentService = null;
	}

	@Override
	public Category getCategory(String categoryId) {
		return fParentService.getCategory(categoryId);
	}

	@Override
	public Command getCommand(String commandId) {
		return fParentService.getCommand(commandId);
	}

	@Override
	public Category[] getDefinedCategories() {
		return fParentService.getDefinedCategories();
	}

	@Override
	public Collection getDefinedCategoryIds() {
		return fParentService.getDefinedCategoryIds();
	}

	@Override
	public Collection getDefinedCommandIds() {
		return fParentService.getDefinedCommandIds();
	}

	@Override
	public Command[] getDefinedCommands() {
		return fParentService.getDefinedCommands();
	}

	@Override
	public Collection getDefinedParameterTypeIds() {
		return fParentService.getDefinedParameterTypeIds();
	}

	@Override
	public ParameterType[] getDefinedParameterTypes() {
		return fParentService.getDefinedParameterTypes();
	}

	@Override
	public final String getHelpContextId(final Command command) throws NotDefinedException {
		return fParentService.getHelpContextId(command);
	}

	@Override
	public final String getHelpContextId(final String commandId) throws NotDefinedException {
		return fParentService.getHelpContextId(commandId);
	}

	@Override
	public ParameterType getParameterType(String parameterTypeId) {
		return fParentService.getParameterType(parameterTypeId);
	}

	@Override
	public void readRegistry() {
		fParentService.readRegistry();
	}

	@Override
	public void removeExecutionListener(IExecutionListener listener) {
		fExecutionListeners.remove(listener);
		fParentService.removeExecutionListener(listener);
	}

	@Override
	public final void setHelpContextId(final IHandler handler, final String helpContextId) {
		fParentService.setHelpContextId(handler, helpContextId);
	}

	@Override
	public void refreshElements(String commandId, Map filter) {
		fParentService.refreshElements(commandId, filter);
	}

	@Override
	public IElementReference registerElementForCommand(ParameterizedCommand command, UIElement element)
			throws NotDefinedException {
		if (!command.getCommand().isDefined()) {
			throw new NotDefinedException("Cannot define a callback for undefined command " //$NON-NLS-1$
					+ command.getCommand().getId());
		}
		if (element == null) {
			throw new NotDefinedException("No callback defined for command " //$NON-NLS-1$
					+ command.getCommand().getId());
		}

		ElementReference ref = new ElementReference(command.getId(), element, command.getParameterMap());
		registerElement(ref);
		return ref;
	}

	@Override
	public void registerElement(IElementReference elementReference) {
		fCallbackCache.add(elementReference);
		elementReference.getParameters().put(fScopingName, fScopingValue);
		fParentService.registerElement(elementReference);
	}

	@Override
	public void unregisterElement(IElementReference elementReference) {
		fCallbackCache.remove(elementReference);
		fParentService.unregisterElement(elementReference);
	}

	@Override
	public Runnable registerElementForUpdate(ParameterizedCommand parameterizedCommand, final MItem item) {
		UIElement element = new UIElement(fScopingValue) {

			@Override
			public void setText(String text) {
				item.setLabel(text);
			}

			@Override
			public void setTooltip(String text) {
				item.setTooltip(text);
			}

			@Override
			public void setIcon(ImageDescriptor desc) {
				item.setIconURI(MenuHelper.getIconURI(desc, fContext));
			}

			@Override
			public void setDisabledIcon(ImageDescriptor desc) {
				item.getTransientData().put(IPresentationEngine.DISABLED_ICON_IMAGE_KEY,
						MenuHelper.getIconURI(desc, fContext));
			}

			@Override
			public void setHoverIcon(ImageDescriptor desc) {
				// ignored
			}

			@Override
			public void setChecked(boolean checked) {
				item.setSelected(checked);
			}
		};

		try {
			final IElementReference reference = registerElementForCommand(parameterizedCommand, element);
			return () -> unregisterElement(reference);
		} catch (NotDefinedException e) {
			WorkbenchPlugin.log(e);
		}
		return null;
	}
}
