/*******************************************************************************
 * Copyright (c) 2005 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.bpel.common.ui.tray;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;

/**
 * @author IBM, Original Contribution. 
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @date Jun 5, 2007
 *
 */

public class MultiViewerSelectionProvider implements ISelectionProvider {

	static ISelectionChangedListener[] EMPTY_LISTENERS = {};
	
	protected List<EditPartViewer> viewers = new ArrayList<EditPartViewer>();
	protected List<ISelectionChangedListener> listeners;
	protected boolean changingSelection = false;
	protected boolean broadcastingSelectionChange = false;
	protected IStructuredSelection cachedSelection;
	
	/**
	 * Brand new shiny MultiViewerSelectionProvider
	 * @param viewer
	 */
	
	public MultiViewerSelectionProvider(EditPartViewer viewer) {
		this();
		addViewer(viewer);
	}
	
	/**
	 * Brand new shiny MultiViewerSelectionProvider
	 */
	
	public MultiViewerSelectionProvider() {
		this.listeners = new ArrayList<ISelectionChangedListener>();
	}
	
	/**
	 * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
	 */
	public void addSelectionChangedListener(ISelectionChangedListener listener) {
		listeners.add(listener);
	}

	
	/**
	 * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
	 */
	public void removeSelectionChangedListener(ISelectionChangedListener listener) {
		listeners.remove(listener);
	}

	/**
	 * Add the viewer to the list of viewers that are listening for selection changes.
	 * 
	 * @param viewer
	 */
	
	public void addViewer(EditPartViewer viewer) {
		viewers.add(viewer);
		viewer.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				if (changingSelection) {
					return;
				}
				setSelection(event.getSelection());
			}
		});
	}
	
	
	/**
	 *  Get the current selection
	 * 
	 * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
	 */
	
	public ISelection getSelection() {
		if (cachedSelection == null) {
			List<EditPartViewer> result = new ArrayList<EditPartViewer>();
			for(EditPartViewer next : viewers) {				
				result.addAll( next.getSelectedEditParts() );
			}
			cachedSelection = calculateSelection(new StructuredSelection(result));
		}
		return cachedSelection;
	}

	/**
	 * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection)
	 */
	public void setSelection(ISelection selection) {
		if (selection instanceof IStructuredSelection == false) { 
			return;
		}
		
		cachedSelection = calculateSelection( (IStructuredSelection) selection);
		internalSetSelection(cachedSelection);
		fireSelectionChanged(this, cachedSelection);
	}
	
	protected void fireSelectionChanged(ISelectionProvider provider, ISelection selection) {		
		SelectionChangedEvent event = new SelectionChangedEvent(provider, selection);		
		try {			
			broadcastingSelectionChange = true;	
			for(ISelectionChangedListener listener : this.listeners.toArray( EMPTY_LISTENERS ) ) {
				listener.selectionChanged(event);
			}
		} finally {
			broadcastingSelectionChange = false;
		}
	}
	
	protected IStructuredSelection calculateSelection (IStructuredSelection baseSelection) {
		
		List<EditPart> result = new ArrayList<EditPart>();
		for(EditPartViewer viewer : viewers) {
			
			Map<Object,EditPart> registry = viewer.getEditPartRegistry();
			
			for( Object n : baseSelection.toArray() ) {
				EditPart part = (EditPart) n;
				Object model = part.getModel();
				EditPart viewerEditPart = registry.get(model);
				if (viewerEditPart != null) {
					result.add(viewerEditPart);
				}
			}
		}
		if (result.isEmpty()) {
			return StructuredSelection.EMPTY;
		}
		return new StructuredSelection(result);
	}
	
	// TODO: try getting rid of the isEmpty() check here and in the same
	// place in AdaptingSelectionProvider.
	
	// Set selection to each of the viewers and make sure we ignore callbacks
	
	protected void internalSetSelection (IStructuredSelection selection) {
		if (selection == null || selection.isEmpty() ) {
			return;
		}
		
		try {
			changingSelection = true;
			
			for(EditPartViewer viewer : viewers) {
				
				Map<Object,EditPart> registry = viewer.getEditPartRegistry();
				List<EditPart> newList = new ArrayList<EditPart>();
				Set<EditPart> newSet = new HashSet<EditPart>();
				
				for(Object n : selection.toArray() ) {				
					EditPart part = (EditPart) n;
					Object model = part.getModel();
					EditPart viewerEditPart = registry.get(model);
					if (viewerEditPart != null && newSet.add(viewerEditPart)) {
						newList.add(viewerEditPart);
					}
				}
				viewer.setSelection(new StructuredSelection(newList));
			}
		} finally {
			changingSelection = false;
		}
	}
	
	/**
	 * Answer true if we are broadcasting a selection change.
	 * 
	 * @return answer true if we are broadcasting selection change.
	 */
	
	public boolean isBroadcastingSelectionChange() {
		return broadcastingSelectionChange;
	}
}
