/**
 * File:    SelectionSynchronizer.java
 * Created: 25.03.2006
 *
 *
/*******************************************************************************
 * Copyright (c) 2005-2011
 * University of Augsburg, Germany <www.ds-lab.org>
 *
 * 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:
 *    Markus Bauer, Programming distributed Systems Lab, University of Augsburg 
 *    	- initial API and implementation
 *******************************************************************************/

package org.eclipse.jwt.we.editors.selection;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jwt.we.model.view.Reference;

/**
 * An utility to synchronize a selection between mutliple
 * {@link ISelectionProvider}s.
 * <p>
 * When the selection of a registered {@link ISelectionProvider} changes all
 * other {@link ISelectionProvider}s are notified of the new selection.
 * </p>
 * <p>
 * The {@link ISelectionProvider}s may provide a {@link SelectionConverter}. The
 * selection is then converted before setting or dispatching.
 * </p>
 * <p>
 * This class also keeps track of the current selection and provides this as
 * {@link ISelectionProvider}.
 * </p>
 * 
 * @version $Id: SelectionSynchronizer.java,v 1.5 2009/11/26 12:41:36 chsaad Exp
 *          $
 * @author Markus Bauer (markusbauer@users.sourceforge.net), Programming
 *         distributed Systems Lab, University of Augsburg, Germany,
 *         www.ds-lab.org
 * @since 0.5.0
 */
public class SelectionSynchronizer extends AbstractSelectionProvider implements
		ISelectionChangedListener
{

	/**
	 * The registered {@link ISelectionProvider}s combined with their
	 * SelectionConverters.
	 */
	private HashMap<ISelectionProvider, SelectionConverter> selectionProviders = new HashMap<ISelectionProvider, SelectionConverter>();

	/**
	 * The current selection. Must never be <code>null</code>.
	 */
	private ISelection currentSelection = StructuredSelection.EMPTY;

	/**
	 * Indicates that a new selection is currently dispatched.
	 */
	private boolean isDispatching = false;


	/**
	 * Creates a new instance.
	 */
	public SelectionSynchronizer()
	{
		// dispatch the selections on this object.
		addSelectionChangedListener(this);
		addSelectionProvider(this);
	}


	/**
	 * Adds a new SelectionProvider.
	 * 
	 * @param selectionProvider
	 *            The SelectionProvider.
	 */
	public void addSelectionProvider(ISelectionProvider selectionProvider)
	{
		addSelectionProvider(selectionProvider, null);
	}


	/**
	 * Adds a new SelectionProvider with a converter.
	 * 
	 * @param selectionProvider
	 *            The SelectionProvider.
	 * @param selectionConverter
	 *            The converter to convert the selection.
	 */
	public void addSelectionProvider(ISelectionProvider selectionProvider,
			SelectionConverter selectionConverter)
	{
		// synchronize the new provider with the current selection
		selectionProvider.setSelection(getSelection());

		// add the new provider
		selectionProvider.addSelectionChangedListener(this);
		selectionProviders.put(selectionProvider, selectionConverter);
	}


	/**
	 * Removes a SelectionProvider.
	 * 
	 * @param selectionProvider
	 *            The SelectionProvider.
	 */
	public void removeSelectionProvider(ISelectionProvider selectionProvider)
	{
		selectionProvider.removeSelectionChangedListener(this);
		selectionProviders.remove(selectionProvider);
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(
	 * org.eclipse.jface.viewers.SelectionChangedEvent)
	 */
	public void selectionChanged(SelectionChangedEvent event)
	{
		if (isDispatching)
		{
			return;
		}

		ISelectionProvider source = event.getSelectionProvider();
		ISelection selection = event.getSelection();

		SelectionConverter sourceConverter = selectionProviders.get(source);
		if (sourceConverter != null)
		{
			selection = sourceConverter.convertFromTargetSelection(selection);
		}

		dispatchSelection(selection, source);
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
	 */
	public ISelection getSelection()
	{
		return currentSelection;
	}


	/**
	 * Sets a selection for all registered {@link #selectionProviders}.
	 * 
	 * @param selection
	 *            The new selection. The selection must not be <code>null</code>
	 *            . To clear selections use {@link StructuredSelection#EMPTY}.
	 */
	public void setSelection(ISelection selection)
	{
		assert selection != null;

		if (currentSelection == selection)
		{
			return;
		}

		currentSelection = selection;
		fireSelectionChanged(new SelectionChangedEvent(this, selection));
	}


	/**
	 * Dispatches a selection to all registered {@link #selectionProviders}.
	 * 
	 * @param selection
	 *            The new selection.
	 * @param source
	 *            The source provider that changed the selection. It is ignored
	 *            from the dispatching. May be <code>null</code>.
	 */
	private void dispatchSelection(ISelection selection, ISelectionProvider source)
	{
		if (isDispatching)
		{
			return;
		}

		isDispatching = true;
		for (Entry<ISelectionProvider, SelectionConverter> entry : selectionProviders
				.entrySet())
		{
			if (entry.getKey() != source)
			{
				ISelection targetSelection;
				if (entry.getValue() == null)
				{
					targetSelection = selection;
				}
				else
				{
					targetSelection = entry.getValue()
							.convertToTargetSelection(selection);
				}

				// substitute references with refelems (except for actions,
				// propertiesview is handled in modelpropertysourceprovider,
				// also addchild/sibling action in actionbarcontributor)
				if (!(entry.getKey() instanceof SelectionSynchronizer)
						&& targetSelection instanceof StructuredSelection)
				{
					List newSelection = new ArrayList();

					for (Object selected : ((StructuredSelection) targetSelection)
							.toArray())
					{
						if (selected instanceof Reference)
						{
							newSelection.add(((Reference) selected).getReference());
						}
						else
						{
							newSelection.add(selected);
						}
					}

					targetSelection = new StructuredSelection(newSelection);
				}

				entry.getKey().setSelection(targetSelection);
			}
		}
		isDispatching = false;
	}
}
