/*******************************************************************************
 * Copyright (c) 2010, 2016 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
 *     Marco Descher <marco@descher.at> - Bug 403081, 403083
 *     Bruce Skingle <Bruce.Skingle@immutify.com> - Bug 442570
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654
 *******************************************************************************/
package org.eclipse.e4.ui.workbench.renderers.swt;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.di.AboutToHide;
import org.eclipse.e4.ui.model.application.ui.menu.MDynamicMenuContribution;
import org.eclipse.e4.ui.model.application.ui.menu.MMenu;
import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement;
import org.eclipse.e4.ui.model.application.ui.menu.MPopupMenu;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.jface.action.IMenuListener2;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.swt.widgets.Menu;

/**
 * <code>MenuManagerHideProcessor</code> provides hooks for renderer processing
 * before and after the <code>MenuManager</code> calls out to its
 * <code>IMenuManagerListener2</code> for the <code>menuAboutToHide</code>
 * events.
 */
public class MenuManagerHideProcessor implements IMenuListener2 {

	@Inject
	private MenuManagerRenderer renderer;

	@Inject
	private EModelService modelService;

	@Override
	public void menuAboutToShow(IMenuManager manager) {
		if (!(manager instanceof MenuManager)) {
			return;
		}
		MenuManager menuManager = (MenuManager) manager;
		final MMenu menuModel = renderer.getMenuModel(menuManager);
		final Menu menu = menuManager.getMenu();
		if (menuModel instanceof MPopupMenu) {
			hidePopup(menu, (MPopupMenu) menuModel);
		}
		if (menuModel != null && menu != null)
			processDynamicElements((MenuManager) manager, menu, menuModel);
	}

	/**
	 * Process dynamic menu contributions provided by
	 * {@link MDynamicMenuContribution} application model elements
	 *
	 * @param menu
	 * @param menuModel
	 *
	 */
	private void processDynamicElements(final MenuManager menuManager, Menu menu, final MMenu menuModel) {
		// We need to make a copy of the dynamic items which need to be removed
		// because the actual remove happens asynchronously.
		final Map<MDynamicMenuContribution, ArrayList<MMenuElement>> toBeHidden = new HashMap<>();

		for (MMenuElement currentMenuElement : menuModel.getChildren()) {
			if (currentMenuElement instanceof MDynamicMenuContribution) {

				final Map<String, Object> storageMap = currentMenuElement.getTransientData();
				@SuppressWarnings("unchecked")
				ArrayList<MMenuElement> mel = (ArrayList<MMenuElement>) storageMap
						.get(MenuManagerShowProcessor.DYNAMIC_ELEMENT_STORAGE_KEY);

				toBeHidden.put((MDynamicMenuContribution) currentMenuElement, mel);
			}
		}

		if (!menu.isDisposed()) {
			menu.getDisplay().asyncExec(() -> {
				if (menu.isDisposed()) {
					return; // may be meanwhile disposed
				}
				for (Entry<MDynamicMenuContribution, ArrayList<MMenuElement>> entry : toBeHidden.entrySet()) {
					MDynamicMenuContribution currentMenuElement = entry.getKey();
					Object contribution = currentMenuElement.getObject();
					if (contribution == null) {
						continue; // avoid NPE (Bug 578964)
					}
					IEclipseContext dynamicMenuContext = EclipseContextFactory.create();

					ArrayList<MMenuElement> mel = entry.getValue();

					dynamicMenuContext.set(List.class, mel);
					dynamicMenuContext.set(MDynamicMenuContribution.class, currentMenuElement);
					IEclipseContext parentContext = modelService.getContainingContext(currentMenuElement);
					ContextInjectionFactory.invoke(contribution, AboutToHide.class, parentContext,
							dynamicMenuContext, null); // contribution==null => NPE (Bug 578964)
					dynamicMenuContext.dispose();
					// remove existing entries for this dynamic
					// contribution item if there are any
					if (mel != null && mel.size() > 0) {
						renderer.removeDynamicMenuContributions(menuManager, menuModel, mel);
					}

					// make existing entries for this dynamic contribution
					// item invisible if there are any
					if (mel != null && mel.size() > 0) {
						for (MMenuElement item : mel) {
							item.setVisible(false);
						}
					}
				}
			});
		}
	}

	@Override
	public void menuAboutToHide(IMenuManager manager) {
	}

	private void hidePopup(Menu menu, MPopupMenu menuModel) {
		final IEclipseContext popupContext = menuModel.getContext();
		final IEclipseContext originalChild = (IEclipseContext) popupContext
				.get(MenuManagerRendererFilter.TMP_ORIGINAL_CONTEXT);
		popupContext.remove(MenuManagerRendererFilter.TMP_ORIGINAL_CONTEXT);
		if (!menu.isDisposed()) {
			menu.getDisplay().asyncExec(() -> {
				if (originalChild == null) {
					popupContext.deactivate();
				} else {
					originalChild.activate();
				}
			});
		}
	}
}
