/*******************************************************************************
 * Copyright (c) 2015-2015 Veselin Markov.
 *
 *
 * 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:
 * Veselin Markov <veselin_m84@yahoo.com> - initial API and implementation
 * Simon Scholz <simon.scholz@vogella.com> - Bug 475365
 ******************************************************************************/
package org.eclipse.e4.tools.emf.ui.internal.common.component.dialogs;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.e4.core.services.nls.Translation;
import org.eclipse.e4.tools.emf.ui.common.IModelResource;
import org.eclipse.e4.tools.emf.ui.internal.Messages;
import org.eclipse.e4.tools.emf.ui.internal.common.ComponentLabelProvider;
import org.eclipse.e4.tools.emf.ui.internal.common.ModelEditor;
import org.eclipse.e4.ui.model.application.commands.MCommand;
import org.eclipse.e4.ui.model.application.commands.MCommandParameter;
import org.eclipse.e4.ui.model.application.commands.MParameter;
import org.eclipse.e4.ui.model.application.commands.impl.CommandsPackageImpl;
import org.eclipse.e4.ui.model.application.ui.menu.impl.MenuPackageImpl;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;

import jakarta.inject.Inject;

/**
 * This dialog presents to the user in a table the available {@link MCommandParameter}s that
 * may be referenced by the specified {@link MParameter}.
 *
 * @author Markov
 * @noinstantiate this dialog uses DI an should not be instantiated by the user.
 */
public class ParameterIdSelectionDialog extends AbstractIdDialog<MParameter, MCommandParameter> {

	@Inject
	protected ModelEditor editor;

	/** The parameter, which {@link MParameter#getName() name} feature has to be modified. */
	protected MParameter parameter;
	protected Set<EStructuralFeature> parametersFeatures = new HashSet<>();
	protected Set<EStructuralFeature> commandsFeatures = new HashSet<>();

	@Inject
	public ParameterIdSelectionDialog(Shell parentShell, IModelResource resource, MParameter parameter,
		EditingDomain domain, EModelService modelService, @Translation Messages messages) {
		super(parentShell, resource, parameter, domain, modelService, messages);

		this.parameter = parameter;
		parametersFeatures.add(MenuPackageImpl.Literals.HANDLED_ITEM__PARAMETERS);
		commandsFeatures.add(MenuPackageImpl.Literals.HANDLED_ITEM__COMMAND);
	}

	@Override
	protected IBaseLabelProvider getLabelProvider() {
		final FontDescriptor italicFontDescriptor = FontDescriptor.createFrom(viewer.getControl().getFont())
				.setStyle(SWT.ITALIC);
		return new DelegatingStyledCellLabelProvider(
				new ComponentLabelProvider(editor, messages, italicFontDescriptor));
	}

	@Override
	protected String getShellTitle() {
		return messages.ParameterIdSelectionDialog_ShellTitle;
	}

	@Override
	protected String getDialogTitle() {
		return messages.ParameterIdSelectionDialog_DialogTitle;
	}

	@Override
	protected String getDialogMessage() {
		return messages.ParameterIdSelectionDialog_DialogMessage;
	}

	@Override
	protected String getLabelText() {
		return messages.ParameterIdSelectionDialog_LabelText;
	}

	@Override
	protected List<MCommandParameter> getViewerInput() {
		return getParametersOfParentNodesCommand();
	}

	@Override
	protected EAttribute getFeatureLiteral() {
		return CommandsPackageImpl.Literals.PARAMETER__NAME;
	}

	@Override
	protected String getListItemInformation(MCommandParameter listItem) {
		return null;
	}

	/**
	 * Reads the parameters of the MComand in the {@link EObject} referencing the given {@link #parameter}.
	 *
	 * Searches for the parent element of {@linkplain #parameter}. Once found reads the {@link MCommandParameter}s of
	 * the referenced {@link MCommand}, given the found parent references a MCommand.
	 *
	 * @return all found MCommandParameters or an empty {@link List}, never a {@code null} value.
	 */
	protected List<MCommandParameter> getParametersOfParentNodesCommand() {
		TreeIterator<EObject> it = EcoreUtil.getAllContents(resource.getRoot().get(0), true);
		while (it.hasNext()) {
			EObject containerObjectWithCommand = it.next();
			if (containerObjectWithCommand != null && canSupplyParameters(containerObjectWithCommand)) {
				List<MCommandParameter> commandParameters = getCommandParameters(containerObjectWithCommand);
				return commandParameters;
			}
		}
		return Collections.emptyList();
	}

	/**
	 * Checks if the given {@code object} references a {@link MCommand} and the {@link #parameter}.
	 *
	 * @param object the object to be checked. May not be {@code null}.
	 * @return {@code true} if {@code object} is a model-element that references a command and a list of parameters with
	 *         {@link #parameter}, {@code false} otherwise.
	 */
	protected boolean canSupplyParameters(EObject object) {
		return referencesCommand(object) && referencesParameters(object)
			&& containsSearchedParameter(object);
	}

	/**
	 * Checks whether the given {@code object} has a {@link EReference} to a {@link MCommand} feature.
	 *
	 * @param object that will be checked. May not be {@code null}.
	 * @return {@code true} if the given {@code object} can reference a MCommand, {@code false} otherwise.
	 */
	protected boolean referencesCommand(EObject object) {
		EList<EReference> eAllReferences = object.eClass().getEAllReferences();
		for (EReference r : eAllReferences) {
			if (commandsFeatures.contains(r)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Checks whether the given {@code object} has a {@link EReference} to a list of {@link MParameter}s feature.
	 *
	 * @param object that will be checked. May not be {@code null}.
	 * @return {@code true} if the given {@code object} can reference a MParameters, {@code false} otherwise.
	 */
	protected boolean referencesParameters(EObject object) {
		EList<EReference> eAllReferences = object.eClass().getEAllReferences();
		for (EReference r : eAllReferences) {
			if (parametersFeatures.contains(r)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Checks if the {@link #parameter} is contained in the {@link MParameter}s feature of the given {@code object}.
	 *
	 * @param object that may contain the {@code parameter} we look for.
	 * @return true if {@code object} contains the {@code parameter}, false otherwise.
	 */
	protected boolean containsSearchedParameter(EObject object) {
		for (EStructuralFeature parametersFeature : parametersFeatures) {
			Object parameters = object.eGet(parametersFeature);
			if (parameters != null && parameters instanceof Collection<?>) {
				return ((Collection<?>) parameters).contains(parameter);
			}
		}
		return false;
	}

	/**
	 * Reads the {@link MCommandParameter}s from the {@link MCommand} from the given {@code containerObjectWithCommand}.
	 *
	 * @param containerObjectWithCommand an object containing a {@link EReference} to an {@link MCommand}
	 * @return a {@link List} with the {@link MCommandParameter}s or an empty list but never {@code null} value.
	 *
	 * @throws IllegalArgumentException if {@code containerObjectWithCommand} contains no {@link MCommand}. See
	 *             {@link #referencesCommand(EObject)}.
	 */
	protected List<MCommandParameter> getCommandParameters(EObject containerObjectWithCommand) {
		Object command = containerObjectWithCommand.eGet(MenuPackageImpl.Literals.HANDLED_ITEM__COMMAND);

		if (command != null && command instanceof MCommand) {
			List<MCommandParameter> parameters = ((MCommand) command).getParameters();
			if (parameters != null)
				return parameters;
			return Collections.emptyList();
		}
		throw new IllegalArgumentException("The parameter contains no MCommand"); //$NON-NLS-1$
	}
}
