/*******************************************************************************
 * Copyright (c) 2019 Remain BV, Netherlands
 *
 * 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:
 *     Remain BV - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui;

import static org.eclipse.ui.SelectionListenerFactory.Predicates.alreadyDelivered;
import static org.eclipse.ui.SelectionListenerFactory.Predicates.alreadyDeliveredAnyPart;
import static org.eclipse.ui.SelectionListenerFactory.Predicates.selfMute;
import static org.eclipse.ui.SelectionListenerFactory.Predicates.targetPartVisible;

import java.util.Objects;
import java.util.function.Predicate;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.internal.PartSelectionListener;

/**
 * Selection listeners are notified of all selections in the workbench. This
 * means that the listener is always required to filter unwanted selections. In
 * addition, the listener has to make sure not to waste cycles in the UI thread,
 * for instance, not update the UI while it is invisible, but make sure to
 * repaint if becoming visible.
 * <p>
 * This filtering generally requires <u>a lot</u> of boilerplate code while,
 * ideally, you only want to receive selections that are of interest.
 * <p>
 * This factory takes care of many practical filtering scenarios by allowing the
 * creation of an intermediate selection service that only calls you back with
 * selections you can work with.
 * <p>
 * <b>Usage:</b> (assumes the part implements ISelectionListener)
 * <p>
 * <u> Only visit the listener if our part is visible: </u>
 *
 * <pre>
 * getSite().getPage().addSelectionListener(SelectionListenerFactory.createVisibleListener(this, this));
 * </pre>
 *
 * <p>
 * <u> Only visit the listener if our part is visible and the selection did not
 * come from us: </u>
 *
 * <pre>
 * getSite().getPage().addSelectionListener(SelectionListenerFactory.createVisibleSelfMutedListener(this, this));
 * </pre>
 *
 * <p>
 * <u> Chaining predicates: </u>
 *
 * <pre>
 * import static org.eclipse.ui.SelectionListenerFactory.Predicates.adaptsTo;
 * import static org.eclipse.ui.SelectionListenerFactory.Predicates.selectionPartVisible;
 * import static org.eclipse.ui.SelectionListenerFactory.Predicates.selectionSize;
 * import static org.eclipse.ui.SelectionListenerFactory.Predicates.selfMute;
 * import static org.eclipse.ui.SelectionListenerFactory.Predicates.targetPartVisible;
 *
 * Predicate&lt;ISelectionModel&gt; predicate = adaptsTo(PlatformObject.class))
 *		.and(selectionSize(1))
 *		.and(selfMute)
 *		.and(selectionPartVisible)
 *		.and(targetPartVisible));
 *
 * getSite().getPage().addSelectionListener(SelectionListenerFactory.createListener(this, predicate));
 * </pre>
 *
 * <p>
 * <u> Creating your own predicate in combination with the visible part
 * predicate: </u>
 *
 * <pre>
 * Predicate&lt;ISelectionModel&gt; predicate = new Predicate&lt;&gt;() {
 *
 * 	public boolean test(ISelectionModel model) {
 * 		if (model.getCurrentSelectionPart() == SampleView4.this) {
 * 			return false;
 * 		}
 * 		if (!(model.getCurrentSelectionPart() instanceof SampleView)) {
 * 			return false;
 * 		}
 * 		return true;
 * 	}
 * };
 *
 * GetSite().getPage().addSelectionListener(SelectionListenerFactory.createVisibleListener(this, this, predicate));
 * </pre>
 *
 * @since 3.117
 *
 */
public class SelectionListenerFactory {

	/**
	 * A model containing selection values. A predicate can use this model to
	 * determine if a selection needs to be delivered.
	 *
	 * Clients do not have to implement this model. For usage information see the
	 * javadoc of {@link SelectionListenerFactory}.
	 *
	 */
	public interface ISelectionModel {

		/**
		 * @return our part, never null.
		 */
		IWorkbenchPart getTargetPart();

		/**
		 * @return the current selection, could be null if your listener implements
		 *         {@link INullSelectionListener}.;
		 */
		ISelection getCurrentSelection();

		/**
		 * @return the part from which the selection originated, could be null if your
		 *         listener implements {@link INullSelectionListener}.
		 */
		IWorkbenchPart getCurrentSelectionPart();

		/**
		 * @return the selection that was lastly delivered to us, could be null.
		 */
		ISelection getLastDeliveredSelection();

		/**
		 * @return the part from which the last delivered selection originated, could be
		 *         null.
		 */
		IWorkbenchPart getLastDeliveredSelectionPart();

		/**
		 * @return true if the target part is visible
		 */
		boolean isTargetPartVisible();

		/**
		 * @return true if the selection part is visible
		 */
		boolean isSelectionPartVisible();

	}

	/**
	 * Static class to hold the predicates for this factory.
	 *
	 */
	public static class Predicates {

		/**
		 * Creates a predicate that tests true if the selection is an instance of the
		 * passed selection type.
		 *
		 * <p>
		 * Use this predicate if the listener may only see specific selections like
		 * {@link IStructuredSelection}. This will avoid the listener to be visited on
		 * text selections.
		 * </p>
		 *
		 *
		 * @param selectionType A subclass of {@link ISelection} which may not be null.
		 * @return the {@link Predicate} that filters based on the the selection.
		 */
		public static Predicate<ISelectionModel> selectionType(Class<? extends ISelection> selectionType) {
			return model -> (model.getCurrentSelection() != null
					&& selectionType.isAssignableFrom(model.getCurrentSelection().getClass()));
		}

		/**
		 * Creates a predicate that will test true if the selection is an
		 * {@link IStructuredSelection} and number of elements matches the passed size.
		 *
		 * <p>
		 * A listener might not be able to handle multiple selections. It is bad
		 * practice to only react on the first element in the list. In this case, use
		 * this predicate to tell the selection framework that the listener only wants
		 * to be visited if one element is selected.
		 * </p>
		 *
		 * <p>
		 * A listener might need two or more selected elements (e.g. in a compare). Use
		 * this predicate to tell the selection framework that the listener only wants
		 * to be visited if there are enough elements in the selection.
		 * </p>
		 *
		 *
		 * @param size The number of required elements.
		 *
		 * @return the {@link Predicate} that filters based on the selection size.
		 *
		 * @see #minimalSelectionSize(int)
		 */
		public static Predicate<ISelectionModel> selectionSize(int size) {
			return model -> model.getCurrentSelection() instanceof IStructuredSelection
					&& ((IStructuredSelection) model.getCurrentSelection()).size() == size;
		}

		/**
		 * Creates a predicate that will test true if the selection is an
		 * {@link IStructuredSelection} and number of elements is at least the passed
		 * size.
		 *
		 * <p>
		 * A listener might need two or more selected elements (e.g. in a compare). Use
		 * this predicate to tell the selection framework that the listener only wants
		 * to be visited if there are enough elements in the selection.
		 * </p>
		 *
		 * @param size The number of elements at least in the list.
		 *
		 * @return the {@link Predicate} that filters based on the selection size.
		 */
		public static Predicate<ISelectionModel> minimalSelectionSize(int size) {
			return model -> model.getCurrentSelection() instanceof IStructuredSelection
					&& ((IStructuredSelection) model.getCurrentSelection()).size() >= size;
		}

		/**
		 * A predicate that will test true if the selection is not empty.
		 *
		 * <p>
		 * Empty selections can be used to clear the UI but often the UI is left as it
		 * is. Use with care.
		 * </p>
		 *
		 */
		public static Predicate<ISelectionModel> emptySelection = model -> model.getCurrentSelection() != null
				&& !model.getCurrentSelection().isEmpty();

		/**
		 * Creates a predicate that returns true when all the objects contained in an
		 * {@link IStructuredSelection} are adaptable to the passed adapter type.
		 *
		 * <p>
		 * Listeners often want to react only on specific objects. Use this predicate to
		 * only receive selections that contain objects the listener can work with.
		 * </p>
		 *
		 * @param adapterType The class that all elements of the selection must extend
		 *                    or implement. It may not be null.
		 * @return the {@link Predicate} that filters the selection.
		 */
		public static Predicate<ISelectionModel> adaptsTo(Class<?> adapterType) {
			return model -> {
				if (!(model.getCurrentSelection() instanceof IStructuredSelection)) {
					return false;
				}
				IStructuredSelection sel = (IStructuredSelection) model.getCurrentSelection();
				for (Object object : sel) {
					if (Adapters.adapt(object, adapterType) == null) {
						return false;
					}
				}
				return true;
			};
		}

		/**
		 * A predicate that tests true when the part that provides the selection is
		 * visible.
		 * <p>
		 * When a part becomes visible it will receive the current selection. However,
		 * this selection may come from a part that now has become invisible. This may
		 * be unwanted because of the missing visual link.
		 * </p>
		 */
		public static Predicate<ISelectionModel> selectionPartVisible = ISelectionModel::isSelectionPartVisible;

		/**
		 * A predicate that tests true if the selection and the part it came from are
		 * the same as the selection listener currently has.
		 * <p>
		 * A part will broadcast its selection again when it gets focus. This can cause
		 * unneeded reactions on selections that the listener already has.
		 * </p>
		 *
		 */
		public static Predicate<ISelectionModel> alreadyDelivered = model -> !(Objects
				.equals(model.getCurrentSelectionPart(), model.getLastDeliveredSelectionPart())
				&& Objects.equals(model.getCurrentSelection(), model.getLastDeliveredSelection()));

		/**
		 * A predicate that tests true if the selection is the same as the selection we
		 * currently have. No matter which part delivered it.
		 * <p>
		 * Multiple parts can broadcast the same selection. This can cause unneeded
		 * reactions on selections that the listener already has.
		 * </p>
		 */
		public static Predicate<ISelectionModel> alreadyDeliveredAnyPart = model -> !Objects
				.equals(model.getCurrentSelection(), model.getLastDeliveredSelection());

		/**
		 * A predicate that tests true if the selection originates from its own part.
		 * <p>
		 * A selection is also delivered to the part that created it. In most cases this
		 * must be ignored.
		 * </p>
		 */
		public static Predicate<ISelectionModel> selfMute = model -> model.getCurrentSelectionPart() != model
				.getTargetPart();

		/**
		 * A predicate that tests true if the part that receives the selection is
		 * visible.
		 * <p>
		 * A selection is also delivered to parts that are not visible to the user (e.g.
		 * in a different perspective or obscured by other parts). In most cases this
		 * must be ignored.
		 * </p>
		 */
		public static Predicate<ISelectionModel> targetPartVisible = ISelectionModel::isTargetPartVisible;

	}

	/**
	 * Create a listener for a part that also acts as the selection listener.
	 * <p>
	 * The listener will be automatically removed when the part is closed.
	 * </p>
	 *
	 * @param part      the part which must implements the
	 *                  {@link ISelectionListener} to be notified. May not be null.
	 * @param predicate the predicate must test true before the selection is
	 *                  delivered. May not be null.
	 * @return the listener. Never null.
	 */
	public static ISelectionListener createListener(IWorkbenchPart part, Predicate<ISelectionModel> predicate) {
		Assert.isNotNull(part);
		Assert.isNotNull(predicate);
		return new PartSelectionListener(part, (ISelectionListener) part, predicate);
	}

	/**
	 * Create a listener for a part with a separate selection listener.
	 * <p>
	 * The listener will be automatically removed when the part is closed.
	 * </p>
	 *
	 * @param part      the part. May not be null.
	 * @param listener  the selection listener to be notified. May not be null. It
	 *                  can be the part itself if it implements
	 *                  {@link ISelectionChangedListener}.
	 * @param predicate the predicate that must test true before the selection is
	 *                  delivered. May not be null.
	 * @return the listener
	 */
	public static ISelectionListener createListener(IWorkbenchPart part, ISelectionListener listener,
			Predicate<ISelectionModel> predicate) {
		Assert.isNotNull(part);
		Assert.isNotNull(listener);
		Assert.isNotNull(predicate);
		return new PartSelectionListener(part, listener, predicate);
	}

	/**
	 * Convenience method to create a listener that only gets notified when:
	 * <ul>
	 * <li>the selection has changed;</li>
	 * <li>the part is visible.</li>
	 * </ul>
	 * <p>
	 * The listener will be automatically removed when the part is closed.
	 * </p>
	 *
	 * @param part     the part. May not be null.
	 * @param listener the selection listener to be notified. May not be null. It
	 *                 can be the part itself if it implements
	 *                 {@link ISelectionChangedListener}.
	 * @return the listener. Never null.
	 *
	 * @see Predicates#alreadyDelivered
	 * @see Predicates#targetPartVisible
	 */
	public static ISelectionListener createVisibleListener(IWorkbenchPart part, ISelectionListener listener) {
		Assert.isNotNull(part);
		Assert.isNotNull(listener);
		return new PartSelectionListener(part, listener, alreadyDelivered.and(targetPartVisible));
	}

	/**
	 * Convenience method to create a listener that only gets notified when:
	 * <ul>
	 * <li>the selection has changed;</li>
	 * <li>the part is visible.</li>
	 * </ul>
	 * <p>
	 * The listener will be automatically removed when the part is closed.
	 * </p>
	 *
	 * @param part                the part. May not be null.
	 * @param listener            the selection listener to be notified. May not be
	 *                            null. It can be the part itself if it implements
	 *                            {@link ISelectionChangedListener}.
	 * @param additionalPredicate the additional predicate which must test true
	 *                            before the selection is delivered. May not be
	 *                            null.
	 * @return the listener, never null.
	 *
	 * @see Predicates#alreadyDelivered
	 * @see Predicates#targetPartVisible
	 *
	 */
	public static ISelectionListener createVisibleListener(IWorkbenchPart part, ISelectionListener listener,
			Predicate<ISelectionModel> additionalPredicate) {
		Assert.isNotNull(part);
		Assert.isNotNull(listener);
		Assert.isNotNull(additionalPredicate);
		return decorate(createVisibleListener(part, listener), additionalPredicate);
	}

	/**
	 * Provides a listener that only gets notified of selection events when:
	 * <ul>
	 * <li>the selection was not already delivered from any part;</li>
	 * <li>the part is visible;</li>
	 * <li>the selection does not originate from the part.</li>
	 * </ul>
	 * <p>
	 * The listener will be automatically removed when the part is closed.
	 * </p>
	 *
	 * @param part     the part. May not be null.
	 * @param listener the selection listener to be notified. It can be the part
	 *                 itself if it implements {@link ISelectionChangedListener}.
	 *                 May not be null.
	 *
	 * @return the listener, never null.
	 *
	 * @see Predicates#alreadyDeliveredAnyPart
	 * @see Predicates#targetPartVisible
	 * @see Predicates#selfMute
	 */
	public static ISelectionListener createVisibleSelfMutedListener(IWorkbenchPart part, ISelectionListener listener) {
		Assert.isNotNull(part);
		Assert.isNotNull(listener);
		return new PartSelectionListener(part, listener, alreadyDeliveredAnyPart.and(targetPartVisible).and(selfMute));
	}

	/**
	 * Provides a listener that only gets notified of selection events when:
	 * <ul>
	 * <li>the selection has changed;</li>
	 * <li>the part is visible;</li>
	 * <li>the selection does not originate from the part.</li>
	 * </ul>
	 * <p>
	 * The listener will be automatically removed if the part is closed.
	 * </p>
	 *
	 * @param part                the part. May not be null.
	 * @param listener            the selection listener to be notified. May not be
	 *                            null. It can be the part itself if it implements
	 *                            {@link ISelectionChangedListener}.
	 * @param additionalPredicate the predicate to and-add on top of the chain that
	 *                            this method already adds. May not be null
	 * @return the listener, never null.
	 * @see #createVisibleListener(IWorkbenchPart, ISelectionListener)
	 *
	 * @see Predicates#alreadyDeliveredAnyPart
	 * @see Predicates#targetPartVisible
	 * @see Predicates#selfMute
	 */
	public static ISelectionListener createVisibleSelfMutedListener(IWorkbenchPart part, ISelectionListener listener,
			Predicate<ISelectionModel> additionalPredicate) {
		Assert.isNotNull(part);
		Assert.isNotNull(listener);
		Assert.isNotNull(additionalPredicate);
		return decorate(createVisibleSelfMutedListener(part, listener), additionalPredicate);
	}

	/**
	 * Decorates the passed listener with the passed predicate. The listener must be
	 * created by this factory otherwise nothing happens.
	 *
	 * @param listener            the listener that was created by this factory.
	 * @param additionalPredicate a predicate to and-add on top of the chain that
	 *                            the listener already has. May not be null.
	 * @return the passed listener
	 */
	public static ISelectionListener decorate(ISelectionListener listener,
			Predicate<ISelectionModel> additionalPredicate) {
		Assert.isNotNull(additionalPredicate);
		if (listener instanceof PartSelectionListener) {
			return ((PartSelectionListener) listener).addPredicate(additionalPredicate);
		}
		return listener;
	}
}
