/*******************************************************************************
 * Copyright (c) 2016 vogella GmbH 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: Simon Scholz <simon.scholz@vogella.com> - initial API and
 * implementation
 ******************************************************************************/

package org.eclipse.e4.ui.internal.workbench;

import java.util.List;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.ui.di.UIEventTopic;
import org.eclipse.e4.ui.model.application.ui.MElementContainer;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.advanced.MPerspectiveStack;
import org.eclipse.e4.ui.model.application.ui.advanced.MPlaceholder;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
import org.eclipse.e4.ui.model.application.ui.basic.MStackElement;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.workbench.IWorkbench;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.e4.ui.workbench.UIEvents.EventTags;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.osgi.service.event.Event;

/**
 * The {@link PartOnTopManager} is used to calculate the parts, which are on
 * top. This means all parts that can be directly seen by a user in the UI.
 */
public class PartOnTopManager {

	@Inject
	private EModelService modelService;

	/**
	 * @param context
	 */
	@PostConstruct
	public void init(IEclipseContext context) {
		context.set(PartOnTopManager.class, this);
	}

	/**
	 * This event listener sets the initial onTop state, when a widget for an
	 * {@link MUIElement} is created.
	 *
	 * @param event
	 *            {@link Event} containing the data of the widgets that have
	 *            been created for a {@link MUIElement}.
	 */
	@Inject
	@Optional
	public void subscribeTopicWidget(@UIEventTopic(UIEvents.UIElement.TOPIC_WIDGET) Event event) {
		Object element = event.getProperty(EventTags.ELEMENT);
		Object newValue = event.getProperty(EventTags.NEW_VALUE);

		if (element instanceof MPart && newValue != null) {
			handlePartSelection(null, element);
		}
	}

	/**
	 * This event listener updates the on top status of {@link MStackElement}
	 * instances by adding or removing the {@link IWorkbench#ON_TOP} tag
	 * according to the state.
	 *
	 * @param event
	 *            {@link Event} containing the data of the selection change
	 *            event on element container, like {@link MPerspectiveStack} and
	 *            {@link MPartStack}.
	 */
	@Inject
	@Optional
	public void subscribeTopicSelectedElement(
			@UIEventTopic(UIEvents.ElementContainer.TOPIC_SELECTEDELEMENT) Event event) {

		Object element = event.getProperty(UIEvents.EventTags.ELEMENT);
		Object oldValue = event.getProperty(UIEvents.EventTags.OLD_VALUE);
		Object newValue = event.getProperty(UIEvents.EventTags.NEW_VALUE);

		if (element instanceof MPerspectiveStack) {
			// stack element's contents can be hidden or shown due to a
			// perspective switch ...
			handlePerspectiveSwitch(oldValue, newValue);
		} else if (element instanceof MPartStack) {
			// ... or by changing the selection in a part stack
			handlePartSelection(oldValue, newValue);
		}

	}

	/**
	 * @param oldValue
	 * @param newValue
	 */
	private void handlePerspectiveSwitch(Object oldValue, Object newValue) {
		handleNewAndOldSelectedElementsOfElementContainer(oldValue, newValue);
	}

	private void handlePartSelection(Object oldValue, Object newValue) {
		Object oldElement = oldValue;

		if (oldElement instanceof MPlaceholder) {
			oldElement = ((MPlaceholder) oldElement).getRef();
		}

		if (oldElement instanceof MPart) {
			MPart contextElement = (MPart) oldElement;
			if (contextElement.getContext() != null) {
				contextElement.getContext().set(IWorkbench.ON_TOP, Boolean.FALSE);
			}
		}

		Object newElement = newValue;

		if (newElement instanceof MPlaceholder) {
			newElement = ((MPlaceholder) newElement).getRef();
		}

		if (newElement instanceof MPart) {
			MPart contextElement = (MPart) newElement;
			if (contextElement.getContext() != null) {
				contextElement.getContext().set(IWorkbench.ON_TOP, Boolean.TRUE);
			}
		}

		handleNewAndOldSelectedElementsOfElementContainer(oldElement, newElement);
	}

	private void handleNewAndOldSelectedElementsOfElementContainer(Object oldValue, Object newValue) {
		if (newValue instanceof MElementContainer) {
			List<MPart> newParts = modelService.findElements((MElementContainer<?>) newValue, null, MPart.class);
			if (oldValue instanceof MElementContainer) {
				List<MPart> oldParts = modelService.findElements((MElementContainer<?>) oldValue, null, MPart.class,
						null);
				oldParts.forEach(part -> {
					if (part.getContext() != null) {
						if (!isPartOnTop(newParts, part)) {
							part.getContext().set(IWorkbench.ON_TOP, Boolean.FALSE);
						}
					}
				});
			}
			handleNewSelectedElementsOfElementContainer(newParts);
		}
	}

	private boolean isPartOnTop(List<MPart> newParts, MPart part) {
		if (part.getCurSharedRef() != null) {
			if (newParts.contains(part)) {
				java.util.Optional<MPartStack> partStackContainer = getPartStackContainer(part.getCurSharedRef());
				if (partStackContainer.isPresent()) {
					return part.getCurSharedRef().equals(partStackContainer.get().getSelectedElement());
				}
			}
		}
		return false;
	}

	private void handleNewSelectedElementsOfElementContainer(List<MPart> parts) {
		parts.stream().filter(part -> {
			if (part.getContext() == null) {
				return false;
			}
			MUIElement partRef = part;
			if (part.getCurSharedRef() != null) {
				partRef = part.getCurSharedRef();
			}
			java.util.Optional<MPartStack> partStackContainer = getPartStackContainer(partRef);
			if (partStackContainer.isPresent()) {
				return partRef.equals(partStackContainer.get().getSelectedElement());
			}

			return true;
		}).forEach(part -> {
			part.getContext().set(IWorkbench.ON_TOP, Boolean.TRUE);
		});
	}

	private java.util.Optional<MPartStack> getPartStackContainer(MUIElement uiElement) {
		MUIElement parent = modelService.getContainer(uiElement);
		if (parent instanceof MPartStack) {
			return java.util.Optional.of((MPartStack) parent);
		} else if (parent instanceof MWindow) {
			// reached window level so there is no MPartStack as parent
			return java.util.Optional.empty();
		}

		return getPartStackContainer(parent);
	}
}
