/*******************************************************************************
 * Copyright (c) 2000, 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.ui.parts;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

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

import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;

/**
 * A utility for synchronizing the selection of multiple EditPartViewers. This
 * class performs selection synchronization by taking the selection from one
 * viewer, and mapping it to the selection in another viewer. The mapping is
 * performed by matching the models of the selected EditParts from one viewer to
 * the EditParts with the same models in another. The can be customized by
 * overriding the {@link #convert(EditPartViewer, EditPart)} method.
 * 
 * @author hudsonr
 */
public class SelectionSynchronizer implements ISelectionChangedListener {

	private List viewers = new ArrayList();
	private boolean isDispatching = false;
	private int disabled = 0;
	private EditPartViewer pendingSelection;

	/**
	 * Adds a viewer to the set of synchronized viewers
	 * 
	 * @param viewer
	 *            the viewer
	 */
	public void addViewer(EditPartViewer viewer) {
		viewer.addSelectionChangedListener(this);
		viewers.add(viewer);
	}

	/**
	 * Maps the given editpart from one viewer to an editpart in another viewer.
	 * It returns <code>null</code> if there is no corresponding part. This
	 * method can be overridden to provide custom mapping.
	 * 
	 * @param viewer
	 *            the viewer being mapped to
	 * @param part
	 *            a part from another viewer
	 * @return <code>null</code> or a corresponding editpart
	 */
	protected EditPart convert(EditPartViewer viewer, EditPart part) {
		Object temp = viewer.getEditPartRegistry().get(part.getModel());
		EditPart newPart = null;
		if (temp != null) {
			newPart = (EditPart) temp;
		}
		return newPart;
	}

	/**
	 * Removes the viewer from the set of synchronized viewers
	 * 
	 * @param viewer
	 *            the viewer to remove
	 */
	public void removeViewer(EditPartViewer viewer) {
		viewer.removeSelectionChangedListener(this);
		viewers.remove(viewer);
		if (pendingSelection == viewer)
			pendingSelection = null;
	}

	/**
	 * Receives notification from one viewer, and maps selection to all other
	 * members.
	 * 
	 * @param event
	 *            the selection event
	 */
	public void selectionChanged(SelectionChangedEvent event) {
		if (isDispatching)
			return;
		EditPartViewer source = (EditPartViewer) event.getSelectionProvider();
		if (disabled > 0) {
			pendingSelection = source;
		} else {
			ISelection selection = event.getSelection();
			syncSelection(source, selection);
		}
	}

	private void syncSelection(EditPartViewer source, ISelection selection) {
		isDispatching = true;
		for (int i = 0; i < viewers.size(); i++) {
			if (viewers.get(i) != source) {
				EditPartViewer viewer = (EditPartViewer) viewers.get(i);
				setViewerSelection(viewer, selection);
			}
		}
		isDispatching = false;
	}

	/**
	 * Enables or disabled synchronization between viewers.
	 * 
	 * @since 3.1
	 * @param value
	 *            <code>true</code> if synchronization should occur
	 */
	public void setEnabled(boolean value) {
		if (!value)
			disabled++;
		else if (--disabled == 0 && pendingSelection != null) {
			syncSelection(pendingSelection, pendingSelection.getSelection());
			pendingSelection = null;
		}
	}

	private void setViewerSelection(EditPartViewer viewer, ISelection selection) {
		ArrayList result = new ArrayList();
		Iterator iter = ((IStructuredSelection) selection).iterator();
		while (iter.hasNext()) {
			EditPart part = convert(viewer, (EditPart) iter.next());
			if (part != null)
				result.add(part);
		}
		viewer.setSelection(new StructuredSelection(result));
		if (result.size() > 0)
			viewer.reveal((EditPart) result.get(result.size() - 1));
	}

}
