/*******************************************************************************
 * Copyright (c) 2006, 2010 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.gef;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.eclipse.swt.widgets.Control;

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;

/**
 * Manages a viewer's selection model. Selection management includes
 * representing a form of selection which is available to clients of a viewer as
 * an ISelection. It also includes managing the notion of focus, which is
 * closely tied to the current selection. The selection manager provides the
 * mechanism for modifying the selection and any validation.
 * <P>
 * WARNING: Subclassing this class is considered experimental at this point.
 * 
 * @since 3.2
 */
public class SelectionManager {

	private EditPart focusPart;
	private Runnable notifier;
	private List selection;
	private EditPartViewer viewer;

	/**
	 * Default Constructor
	 * 
	 * @since 3.2
	 */
	protected SelectionManager() {
	}

	/**
	 * Creates the default implementation for a selection manager.
	 * 
	 * @return the default selection manager
	 * @since 3.2
	 */
	public static SelectionManager createDefault() {
		return new SelectionManager();
	}

	/**
	 * Appends the <code>EditPart</code> to the current selection. The EditPart
	 * becomes the new primary selection. Fires selection changed to all
	 * {@link org.eclipse.jface.viewers.ISelectionChangedListener}s.
	 * 
	 * @param editpart
	 *            the EditPart to append
	 * @since 3.2
	 */
	public void appendSelection(EditPart editpart) {
		if (editpart != getFocus())
			viewer.setFocus(null);
		if (!selection.isEmpty()) {
			EditPart primary = (EditPart) selection.get(selection.size() - 1);
			primary.setSelected(EditPart.SELECTED);
		}
		// if the editpart is already in the list, re-order it to be the last
		// one
		selection.remove(editpart);
		selection.add(editpart);
		editpart.setSelected(EditPart.SELECTED_PRIMARY);

		fireSelectionChanged();
	}

	/**
	 * Removes the <code>EditPart</code> from the current selection.
	 * 
	 * @param editpart
	 *            the editpart
	 * @since 3.2
	 */
	public void deselect(EditPart editpart) {
		editpart.setSelected(EditPart.SELECTED_NONE);

		selection.remove(editpart);
		if (!selection.isEmpty()) {
			// IMPORTANT: it may (temporarily) happen that the selection list
			// contains edit parts, which are not selectable (any more) when
			// this method gets called. Consider e.g. that the selectable state
			// of an edit part may bound to its activation state (by overwriting
			// isSelectable()); in this case, when deleting a selected edit part
			// and its primary selected child simultaneously, the parent edit
			// part may have already become non selectable, while not having
			// been deselected yet (because deselection is performed within
			// removeNotify() after deactivation), when the child edit part gets
			// deselected. Therefore, we do not simply choose the last edit part
			// in the list as the new primary selection, but reverse-search the
			// list for the first that is (still) selectable.
			for (int i = selection.size() - 1; i >= 0; i--) {
				EditPart primaryCandidate = (EditPart) selection.get(i);
				if (primaryCandidate.isSelectable()) {
					primaryCandidate.setSelected(EditPart.SELECTED_PRIMARY);
					break;
				}
			}
		}
		fireSelectionChanged();
	}

	/**
	 * Deselects everything.
	 * 
	 * @since 3.2
	 */
	public void deselectAll() {
		EditPart part;
		setFocus(null);
		for (int i = 0; i < selection.size(); i++) {
			part = (EditPart) selection.get(i);
			part.setSelected(EditPart.SELECTED_NONE);
		}
		selection.clear();
		fireSelectionChanged();
	}

	/**
	 * Causes the viewer to fire selection changed notification to all
	 * listeners.
	 * 
	 * @since 3.2
	 */
	protected final void fireSelectionChanged() {
		notifier.run();
	}

	/**
	 * Returns the focus editpart.
	 * 
	 * @return the focus editpart
	 * @since 3.2
	 */
	protected EditPart getFocus() {
		return focusPart;
	}

	/**
	 * Returns the current selection.
	 * 
	 * @return the selection
	 * @since 3.2
	 */
	public ISelection getSelection() {
		if (selection.isEmpty() && viewer.getContents() != null)
			return new StructuredSelection(viewer.getContents());
		return new StructuredSelection(selection);
	}

	/**
	 * Returns <code>null</code> or the viewer whose selection is managed.
	 * 
	 * @return <code>null</code> or the viewer
	 * @since 3.2
	 */
	protected EditPartViewer getViewer() {
		return viewer;
	}

	/**
	 * For internal use only. This API is subject to change.
	 * 
	 * @param control
	 *            the control
	 * @since 3.2
	 */
	public void internalHookControl(Control control) {
	}

	/**
	 * For internal use only. This API is subject to change.
	 * 
	 * @since 3.2
	 */
	public void internalUninstall() {
	}

	/**
	 * Provides a hook for when the viewer has been set.
	 * 
	 * @param viewer
	 *            the viewer.
	 * @since 3.2
	 */
	protected void hookViewer(EditPartViewer viewer) {
	}

	/**
	 * For internal use only.
	 * 
	 * @param viewer
	 *            viewer
	 * @param selection
	 *            selection
	 * @param notifier
	 *            notifier
	 * @since 3.2
	 */
	public void internalInitialize(EditPartViewer viewer, List selection,
			Runnable notifier) {
		this.viewer = viewer;
		this.selection = selection;
		this.notifier = notifier;

		hookViewer(viewer);
	}

	/**
	 * Sets the focus part.
	 * 
	 * @param part
	 *            the focus part
	 * @since 3.2
	 */
	public void setFocus(EditPart part) {
		if (focusPart == part)
			return;
		if (focusPart != null)
			focusPart.setFocus(false);
		focusPart = part;
		if (focusPart != null)
			focusPart.setFocus(true);
	}

	/**
	 * Sets the selection.
	 * 
	 * @param newSelection
	 *            the new selection
	 * @since 3.2
	 */
	public void setSelection(ISelection newSelection) {
		if (!(newSelection instanceof IStructuredSelection))
			return;

		List orderedSelection = ((IStructuredSelection) newSelection).toList();
		// Convert to HashSet to optimize performance.
		Collection hashset = new HashSet(orderedSelection);

		setFocus(null);
		for (int i = 0; i < selection.size(); i++) {
			EditPart part = (EditPart) selection.get(i);
			if (!hashset.contains(part))
				part.setSelected(EditPart.SELECTED_NONE);
		}
		selection.clear();

		if (!orderedSelection.isEmpty()) {
			Iterator itr = orderedSelection.iterator();
			while (true) {
				EditPart part = (EditPart) itr.next();
				selection.add(part);
				if (!itr.hasNext()) {
					part.setSelected(EditPart.SELECTED_PRIMARY);
					break;
				}
				part.setSelected(EditPart.SELECTED);
			}
		}
		fireSelectionChanged();
	}

}
