/*******************************************************************************
 * Copyright (c) 2010, 2019 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
 *     Manumitting Technologies Inc - Bug 380609
 *     Stefan Nöbauer - Bug 547997
 ******************************************************************************/

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

import java.util.List;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.model.application.MApplicationElement;
import org.eclipse.e4.ui.model.application.descriptor.basic.MPartDescriptor;
import org.eclipse.e4.ui.model.application.ui.MElementContainer;
import org.eclipse.e4.ui.model.application.ui.MSnippetContainer;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.SideValue;
import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective;
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.MPartSashContainerElement;
import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar;
import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.workbench.Selector;

/**
 * This service is used to find, create and handle model elements
 *
 * @since 1.0
 * @noimplement This interface is not intended to be implemented by clients.
 */
public interface EModelService {
	// Insertion constants

	/** Insert the new element above the existing one */
	int ABOVE = 0;

	/** Insert the new element below the existing one */
	int BELOW = 1;

	/** Insert the new element to the left of the existing one */
	int LEFT_OF = 2;

	/** Insert the new element to the right of the existing one */
	int RIGHT_OF = 3;

	// Search modifiers / Location Constants

	/** Returned Location if the element's parent chain does not relate to the MApplication's model */
	int NOT_IN_UI = 0x00;

	/** Returned Location if the element is in the UI but not in an MPerspective */
	int OUTSIDE_PERSPECTIVE = 0x01;

	/** Returned Location if the element is in the currently active perspective */
	int IN_ACTIVE_PERSPECTIVE = 0x02;

	/** Returned Location if the element is in a non-active perspective */
	int IN_ANY_PERSPECTIVE = 0x04;

	/** Returned Location if the element is contained in an MArea */
	int IN_SHARED_AREA = 0x08;

	/** Returned Location if the element is in an MTrimBar */
	int IN_TRIM = 0x10;

	/**
	 * Returned Location if the element is in a main menu of an MWindow
	 *
	 * @since 1.1
	 */
	int IN_MAIN_MENU = 0x20;

	/**
	 * Returned Location if the element is in an MPart
	 *
	 * @since 1.1
	 */
	int IN_PART = 0x40;

	/**
	 * Returned Location if the element is in SharedElements
	 *
	 * @since 1.11
	 */
	int IN_SHARED_ELEMENTS = 0x80;

	// 'Standard' searches

	/** Searches for elements in the UI that the user is currently seeing (excluding trim) */
	int PRESENTATION = OUTSIDE_PERSPECTIVE | IN_ACTIVE_PERSPECTIVE
			| IN_SHARED_AREA;

	/** Searches for elements in the UI presentation, including all perspectives */
	int ANYWHERE = OUTSIDE_PERSPECTIVE | IN_ANY_PERSPECTIVE | IN_SHARED_AREA
			| IN_TRIM;

	/**
	 * Searches for elements in the UI that the user is currently seeing that are OUTSIDE the
	 * perspective (i.e. visible regardless of the current perspective)
	 */
	int GLOBAL = OUTSIDE_PERSPECTIVE | IN_SHARED_AREA;

	/**
	 * When invoking the 'cloneElement' method the newly cloned element's 'transientData' map will
	 * contain a reference to the original element using this as a key.
	 *
	 * @since 1.1
	 */
	String CLONED_FROM_KEY = "Cloned From"; //$NON-NLS-1$

	/**
	 * Creates instances of model elements. The method supports any type extending
	 * {@link MApplicationElement}, both in the standard e4 UI model and in an
	 * extension models.
	 *
	 * <p>
	 * <b>Caution:</b> To create model element instances of extension models you
	 * need to register them with the
	 * <code>the org.eclipse.e4.workbench.model.definition.enrichment</code>
	 * ExtensionPoint.
	 * </p>
	 *
	 * @param elementType the class to instantiate. Cannot be <code>null</code>
	 * @return a new instance
	 * @throws NullPointerException     if the passed class is <code>null</code>
	 * @throws IllegalArgumentException if the passed class is not supported.
	 */
	<T extends MApplicationElement> T createModelElement(Class<T> elementType);

	/**
	 * Removes a visual model element from the application model by calling
	 * {@link MUIElement#setToBeRendered(boolean)} with false and by removing the
	 * model element from its parent.
	 *
	 * @param element - model
	 * @since 1.9
	 */
	void deleteModelElement(MUIElement element);



	/**
	 * This is a convenience method that constructs a new Selector based on
	 * {@link ElementMatcher} and forwards the call on to the base API
	 * {@link EModelService#findElements(MApplicationElement, Class, int, Selector)}.
	 *
	 * @param searchRoot  The element at which to start the search. This element
	 *                    must be non-null and is included in the search.
	 * @param id          The id of the element search for, can be null
	 * @param clazz       The type of element to be searched for. If non-null this
	 *                    also defines the return type of the List.
	 * @param tagsToMatch List of tags that needs to match.
	 * @param searchFlags A bitwise combination of the following constants:
	 *                    <ul>
	 *                    <li><b>OUTSIDE_PERSPECTIVE</b> Include the elements in the
	 *                    window's model that are not in a perspective</li>
	 *                    <li><b>IN_ANY_PERSPECTIVE</b> Include the elements in all
	 *                    perspectives</li>
	 *                    <li><b>IN_ACTIVE_PERSPECTIVE</b> Include the elements in
	 *                    the currently active perspective only</li>
	 *                    <li><b>IN_MAIN_MENU</b> Include elements in an MWindow's
	 *                    main menu</li>
	 *                    <li><b>IN_PART</b> Include MMenu and MToolbar elements
	 *                    owned by parts</li>
	 *                    <li><b>IN_ACTIVE_PERSPECTIVE</b> Include the elements in
	 *                    the currently active perspective only</li>
	 *                    <li><b>IN_SHARED_AREA</b> Include the elements in the
	 *                    shared area</li>
	 *                    <li><b>IN_TRIM</b> Include the elements in the window's
	 *                    trim</li>
	 *                    <li><b>IN_SHARED_ELEMENTS</b> Include the elements in the
	 *                    window's Shared Elements</li>
	 *                    </ul>
	 *                    Note that you may omit both perspective flags but still
	 *                    define <b>IN_SHARED_AREA</b>; the flags
	 *                    <b>OUTSIDE_PERSPECTIVE | IN_SHARED_AREA</b> for example
	 *                    will search the presentation <i>excluding</i> the elements
	 *                    in perspective stacks.
	 *
	 * @return The generically typed list of matching elements.
	 *
	 * @see EModelService#findElements(MApplicationElement, Class, int, Selector)
	 */
	<T> List<T> findElements(MUIElement searchRoot, String id, Class<T> clazz,
			List<String> tagsToMatch, int searchFlags);

	/**
	 * This is a convenience method that forwards the parameters on to
	 * {@link EModelService#findElements(MUIElement, String, Class, List, int)},
	 * passing {@link EModelService#ANYWHERE} as the 'searchFlags'.
	 *
	 * @param searchRoot  The element at which to start the search. This element
	 *                    must be non-null and is included in the search.
	 * @param id          The id of the element search for, can be null
	 * @param clazz       The type of element to be searched for. If non-null this
	 *                    also defines the return type of the List.
	 * @param tagsToMatch List of tags that needs to match.
	 *
	 * @return The generically typed list of matching elements.
	 */
	<T> List<T> findElements(MUIElement searchRoot, String id, Class<T> clazz,
			List<String> tagsToMatch);

	/**
	 * This is a convenience method that forwards the parameters on to
	 * {@link EModelService#findElements(MUIElement, String, Class, List)}, passing
	 * null as 'tagsToMatch'.
	 *
	 * @param searchRoot The element at which to start the search. This element must
	 *                   be non-null and is included in the search.
	 * @param id         The id of the element search for, can be null
	 * @param clazz      The type of element to be searched for. If non-null this
	 *                   also defines the return type of the List.
	 * @return The generically typed list of matching elements.
	 *
	 * @since 1.8
	 *
	 */
	<T> List<T> findElements(MUIElement searchRoot, String id, Class<T> clazz);

	/**
	 * Return a list of any elements that match the given search criteria. The
	 * search is recursive and includes the specified search root. Any of the search
	 * parameters may be specified as <code>null</code> in which case that field
	 * will always 'match'.
	 * <p>
	 * NOTE: This is a generically typed method with the List's generic type
	 * expected to be the value of the 'clazz' parameter. If the 'clazz' parameter
	 * is null then the returned list is untyped.
	 * </p>
	 *
	 * @param <T>         The generic type of the returned list
	 * @param searchRoot  The element at which to start the search. This element
	 *                    must be non-null and is included in the search.
	 * @param clazz       The type of element to be searched for. If non-null this
	 *                    also defines the return type of the List.
	 * @param searchFlags A bitwise combination of the following constants:
	 *                    <ul>
	 *                    <li><b>OUTSIDE_PERSPECTIVE</b> Include the elements in the
	 *                    window's model that are not in a perspective</li>
	 *                    <li><b>IN_ANY_PERSPECTIVE</b> Include the elements in all
	 *                    perspectives</li>
	 *                    <li><b>IN_ACTIVE_PERSPECTIVE</b> Include the elements in
	 *                    the currently active perspective only</li>
	 *                    <li><b>IN_MAIN_MENU</b> Include elements in an MWindow's
	 *                    main menu</li>
	 *                    <li><b>IN_PART</b> Include MMenu and MToolbar elements
	 *                    owned by parts</li>
	 *                    <li><b>IN_ACTIVE_PERSPECTIVE</b> Include the elements in
	 *                    the currently active perspective only</li>
	 *                    <li><b>IN_SHARED_AREA</b> Include the elements in the
	 *                    shared area</li>
	 *                    <li><b>IN_TRIM</b> Include the elements in the window's
	 *                    trim</li>
	 *                    <li><b>IN_SHARED_ELEMENTS</b> Include the elements in the
	 *                    window's Shared Elements</li>
	 *                    </ul>
	 *                    Note that you may omit both perspective flags but still
	 *                    define <b>IN_SHARED_AREA</b>; the flags
	 *                    <b>OUTSIDE_PERSPECTIVE | IN_SHARED_AREA</b> for example
	 *                    will search the presentation <i>excluding</i> the elements
	 *                    in perspective stacks.
	 * @param matcher     An implementation of a Selector that will return true for
	 *                    elements that it wants in the returned list.
	 * @return The generically typed list of matching elements.
	 *
	 * @since 1.1
	 */
	<T> List<T> findElements(MApplicationElement searchRoot, Class<T> clazz,
			int searchFlags, Selector matcher);

	/**
	 * Returns the first element, recursively searching under the specified search
	 * root (inclusive)
	 *
	 * @param id         The id to search for, must not be null
	 * @param searchRoot The element at which to start the search, must not be null
	 * @return The first element with a matching id or <code>null</code> if one is
	 *         not found
	 */
	MUIElement find(String id, MUIElement searchRoot);

	/**
	 * Locate the context that is closest to the given element in the parent
	 * hierarchy. It does not include the context of the supplied element (should it
	 * have one).
	 *
	 * @param element the element to locate parent context for
	 * @return the containing context for this element
	 */
	IEclipseContext getContainingContext(MUIElement element);

	/**
	 * Brings the specified element to the top of its containment structure. If the
	 * specified element is a top-level window, then it will be selected as the
	 * application's currently active window. Otherwise, the element may merely be
	 * brought up to be seen by the user but not necessarily have its containing
	 * window become the application's active window.
	 *
	 * @param element The element to bring to the top
	 */
	void bringToTop(MUIElement element);

	/**
	 * Clones the element, creating a deep copy of its structure.
	 *
	 * NOTE: The cloned element gets the original element added into its
	 * 'transientData' map using the CLONED_FROM_KEY key. This is useful in cases
	 * where there may be other information the newly cloned element needs from the
	 * original.
	 *
	 * @param element          The element to clone
	 * @param snippetContainer An optional MUIElement where the cloned snippet is to
	 *                         be saved. null if the clone need not be saved
	 * @return The newly cloned element
	 */
	MUIElement cloneElement(MUIElement element, MSnippetContainer snippetContainer);

	/**
	 * If a snippet with the given id exists a clone is created and returned.
	 * returns <code>null</code> if no snippet can be found.
	 *
	 * @param snippetContainer The container of the snippet to clone used
	 * @param snippetId        The element id of the snippet to clone
	 * @param refWin           The window that Placeholder references should be
	 *                         resolved using
	 *
	 * @return The cloned snippet or <code>null</code> if no snippet with the given
	 *         id can be found
	 */
	MUIElement cloneSnippet(MSnippetContainer snippetContainer, String snippetId,
			MWindow refWin);

	/**
	 * Convenience method to find a snippet by id in a particular container
	 *
	 * @param snippetContainer The container to look in
	 * @param id               The id of the root element of the snippet
	 * @return The root element of the snippet or <code>null</code> if none is found
	 */
	MUIElement findSnippet(MSnippetContainer snippetContainer, String id);

	/**
	 * Return the count of the children whose 'toBeRendered' flag is true
	 *
	 * @param element
	 *            The element to test
	 * @return the number of children with 'toBeRendered' == true
	 */
	int countRenderableChildren(MUIElement element);

	/**
	 * Given a containing MWindow find the MPlaceholder that is currently being used
	 * to host the given element (if any)
	 *
	 * @param window  The containing window
	 * @param element The element to find the MPlaceholder for
	 * @return the MPlaceholder or null if none is found
	 */
	MPlaceholder findPlaceholderFor(MWindow window, MUIElement element);

	/**
	 * Move the element to a new location. The element will be placed at the end of
	 * the new parent's list of children.
	 *
	 * @param element   The element to move
	 * @param newParent The new parent for the element.
	 */
	<T extends MUIElement> void move(T element, MElementContainer<? super T> newParent);

	/**
	 * Move the element to a new location. The element will be placed at the end of
	 * the new parent's list of children. If 'leavePlaceholder is true then an
	 * instance of MPlaceholder will be inserted into the model at the element's
	 * original location.
	 *
	 * @param element          The element to move
	 * @param newParent        The new parent for the element.
	 * @param leavePlaceholder true if a placeholder for the element should be added
	 */
	<T extends MUIElement> void move(T element, MElementContainer<? super T> newParent,
			boolean leavePlaceholder);

	/**
	 * Move the element to a new location. The element will be placed at the
	 * specified index in the new parent's list of children.
	 *
	 * @param element   The element to move
	 * @param newParent The new parent for the element.
	 * @param index     The index to insert the element at; -1 means at the end
	 */
	<T extends MUIElement> void move(T element, MElementContainer<? super T> newParent, int index);

	/**
	 * Move the element to a new location. The element will be placed at the end of
	 * the new parent's list of children.
	 *
	 * @param element          The element to move
	 * @param newParent        The new parent for the element.
	 * @param index            The index to insert the element at; -1 means at the
	 *                         end
	 * @param leavePlaceholder true if a placeholder for the element should be added
	 */
	<T extends MUIElement> void move(T element, MElementContainer<? super T> newParent, int index,
			boolean leavePlaceholder);

	/**
	 * Inserts the given element into the UI Model by either creating a new sash or
	 * augmenting an existing sash if the orientation permits.
	 *
	 * @param toInsert The element to insert
	 * @param relTo    The element that the new one is to be relative to
	 * @param where    Indication of where the inserted element should be placed:
	 *                 {@link #LEFT_OF}, {@link #RIGHT_OF}, {@link #ABOVE},
	 *                 {@link #BELOW}.
	 * @param ratio    The percentage of the area to be occupied by the inserted
	 *                 element; should be a number greater than 0 and less than 1
	 */
	void insert(MPartSashContainerElement toInsert, MPartSashContainerElement relTo,
			int where, float ratio);

	/**
	 * Created a separate (detached) window containing the given element.
	 *
	 * @param mPartSashContainerElement The element to detach
	 * @param x                         The X position of the new window
	 * @param y                         The Y position of the new window
	 * @param width                     The Width of the new window
	 * @param height                    The Height of the new window
	 */
	void detach(MPartSashContainerElement mPartSashContainerElement, int x, int y, int width, int height);

	/**
	 * Get the top-level window containing this UI element. A <code>null</code>
	 * return value indicates that the element is not directly contained in the UI
	 * model (but may, for example, be a model snippet hosted in a Dialog...)
	 *
	 * @param element The element to get the window for
	 *
	 * @return the top-level window containing this UI element. A <code>null</code>
	 *         return value indicates that the element is not directly contained in
	 *         the UI model (but may, for example, be a model snippet hosted in a
	 *         Dialog...)
	 */
	MWindow getTopLevelWindowFor(MUIElement element);

	/**
	 * @param element The element to get the perspective for
	 * @return The MPerspective containing this element or <code>null</code> if the
	 *         element is not in a perspective
	 */
	MPerspective getPerspectiveFor(MUIElement element);

	/**
	 * Returns the window's MTrimBar for the specified side. If necessary the bar
	 * will be created.
	 *
	 * @param window The window to get the trim bar for
	 * @param sv     The value for the specified side
	 *
	 * @return The appropriate trim bar
	 */
	MTrimBar getTrim(MTrimmedWindow window, SideValue sv);

	/**
	 * Return the active perspective for the given window. This is a convenience
	 * method that just returns the MPerspectiveStack's selectedElement.
	 *
	 * @param window The window to determine the active perspective for.
	 *
	 * @return The active perspective or <code>null</code> if there is no
	 *         MPerspectiveStack, it's empty or has no selected element.
	 */
	MPerspective getActivePerspective(MWindow window);

	/**
	 * This is a convenience method that will clean the model of all traces of a
	 * given perspective. There may be elements (i.e. minimized stacks...) in the
	 * window's trim that are associated with a perspective as well as the need to
	 * properly clean up any detached windows associated with the perspective.
	 *
	 * @param persp  the perspective to remove
	 * @param window the window to remove it from
	 */
	void resetPerspectiveModel(MPerspective persp, MWindow window);

	/**
	 * Remove the given perspective completely from the model.
	 *
	 * @param persp  the perspective to remove
	 * @param window the window to remove it from
	 */
	void removePerspectiveModel(MPerspective persp, MWindow window);

	/**
	 * Count the number of 'toBeRendered' children
	 *
	 * @param container The container to check
	 * @return The number of children whose toBeRendered flag is <code>true</code>
	 *
	 */
	int toBeRenderedCount(MElementContainer<?> container);

	/**
	 * Get the container of the given element. This is a convenience method that
	 * will always return the actual container for the element, even where the
	 * element's 'getParent' might return null (trim, detached windows...)
	 *
	 * @param element The element to get the container for
	 * @return The element's container. This may be <code>null</code> if the element
	 *         being checked is a snippet unattached to the UI Model itself.
	 */
	MUIElement getContainer(MUIElement element);

	/**
	 * Given an element this method responds with information about where the element exists within
	 * the current UI Model. This is used in cases where it is necessary to know if an element is in
	 * the 'shared area' or outside of any perspective.
	 *
	 * @param element the element of interest
	 * @return The location of the element in the UI, will be one of:
	 *         <ul>
	 *         <li><b>NOT_IN_UI:</b> The element is not in the UI model at all</li>
	 *         <li><b>OUTSIDE_PERSPECTIVE:</b> The element not within a perspective stack</li>
	 *         <li><b>IN_ACTIVE_PERSPECTIVE:</b> The element is within the currently active
	 *         perspective</li>
	 *         <li><b>IN_ANY_PERSPECTIVE:</b> The element is within a perspective but not the active
	 *         one</li>
	 *         <li><b>IN_SHARED_AREA:</b> The element is within an area that is shared between
	 *         different perspectives</li>
	 *         </ul>
	 */
	int getElementLocation(MUIElement element);

	/**
	 * Returns the descriptor for the given part id.
	 * <p>
	 * <b>NOTE:</b> In order to support multiple instance parts there is a
	 * convention where the part's id may be in the form 'partId:secondaryId'. If
	 * the given id contains a ':' then only the substring before the ':' will be
	 * used to find the descriptor.
	 * </p>
	 * <p>
	 * In order to support this convention it's required that no descriptor contain
	 * a ':' in its id
	 * </p>
	 *
	 * @param id The id of the descriptor to return
	 * @return The descriptor matching the id or <code>null</code> if none exists
	 */
	MPartDescriptor getPartDescriptor(String id);

	/**
	 * Creates a new part from the given descriptor.
	 *
	 * @param descriptor a part descriptor, must not be <code>null</code>
	 * @return a new part
	 * @see EPartService#createPart(String)
	 * @since 1.5
	 */
	MPart createPart(MPartDescriptor descriptor);

	/**
	 * This method ensures that there will never be two placeholders for the same
	 * referenced element visible in the presentation at the same time. It does this
	 * by hiding placeholders which are contained in any MPerspective if there is a
	 * placeholder for the element in any 'shared' area (i.e. visible regardless of
	 * which perspective is visible) by setting its 'toBeRendered' state to
	 * <code>false</code>.
	 *
	 * @param window      The window to modify
	 * @param perspective if non-null specifies the specific perspective to modify,
	 *                    otherwise all perspectives in the window are checked
	 */
	void hideLocalPlaceholders(MWindow window, MPerspective perspective);

	/**
	 * Returns <code>true</code> iff the supplied element represents the single
	 * visible element in the shared area. This method is used to test for this
	 * condition since (by convention) there must be at least one stack in the
	 * shared area at all times.
	 *
	 * @param stack The element to test
	 * @return <code>true</code> iff the element is the last visible stack
	 */
	boolean isLastEditorStack(MUIElement stack);

	/**
	 * Allows an element to be rendered in an arbitrary UI container (I.e. SWT
	 * Composite).
	 *
	 * @param element     The element to be rendered.
	 * @param hostWindow  The MWindow the element is being hosted under. Must be
	 *                    non-nulland rendered.
	 * @param uiContainer The UI container acting as the rendered element's parent.
	 *                    Must be non-null.
	 * @param hostContext The IEclipseContext to use for hosting the element. Must
	 *                    be non-null.
	 */
	void hostElement(MUIElement element, MWindow hostWindow, Object uiContainer,
			IEclipseContext hostContext);

	/**
	 * Tests whether the given element is being 'hosted'. This method is used to
	 * allow UI Elements to act as if they are contained within a given MWindow even
	 * though the element is not actually structurally contained in that window's UI
	 * Model.
	 *
	 * @param element    The element to test. Must be non-null.
	 * @param hostWindow The window to test the element against. Must be non-null.
	 *
	 * @return <code>true</code> iff the given element or one of its ancestors is
	 *         currently being hosted in the given MWindow.
	 */
	boolean isHostedElement(MUIElement element, MWindow hostWindow);
}
