content: /*******************************************************************************
   * Copyright (c) 2000, 2008 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.wst.jsdt.internal.ui.workingsets;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.Hashtable;
  import java.util.Iterator;
  import java.util.List;
  
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.runtime.IAdaptable;
  import org.eclipse.jface.dialogs.IDialogConstants;
  import org.eclipse.jface.viewers.CheckStateChangedEvent;
  import org.eclipse.jface.viewers.CheckboxTableViewer;
  import org.eclipse.jface.viewers.ICheckStateListener;
  import org.eclipse.jface.viewers.IStructuredSelection;
  import org.eclipse.jface.viewers.LabelProvider;
  import org.eclipse.jface.viewers.StructuredSelection;
  import org.eclipse.jface.viewers.Viewer;
  import org.eclipse.jface.viewers.ViewerFilter;
  import org.eclipse.jface.viewers.ViewerSorter;
  import org.eclipse.jface.window.Window;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.events.SelectionAdapter;
  import org.eclipse.swt.events.SelectionEvent;
  import org.eclipse.swt.layout.GridData;
  import org.eclipse.swt.layout.GridLayout;
  import org.eclipse.swt.widgets.Button;
  import org.eclipse.swt.widgets.Composite;
  import org.eclipse.swt.widgets.Link;
  import org.eclipse.swt.widgets.Shell;
  import org.eclipse.ui.IWorkbenchPage;
  import org.eclipse.ui.IWorkbenchPart;
  import org.eclipse.ui.IWorkbenchSite;
  import org.eclipse.ui.IWorkingSet;
  import org.eclipse.ui.PlatformUI;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.packageview.PackageExplorerPart;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
  import org.eclipse.wst.jsdt.ui.actions.SelectionDispatchAction;
  
  public final class ConfigureWorkingSetAssignementAction extends SelectionDispatchAction {
  	
  	private static final class GrayedCheckedModel {
  
  		private GrayedCheckedModelElement[] fElements;
  		private final Hashtable fLookup;
  		
  		public GrayedCheckedModel(GrayedCheckedModelElement[] elements) {
  			fElements= elements;
  			fLookup= new Hashtable();
  			for (int i= 0; i < elements.length; i++) {
  				fLookup.put(elements[i].getWorkingSet(), elements[i]);
  			}
  		}
  		
  		public void addElement(GrayedCheckedModelElement element) {
  			ArrayList list= new ArrayList(Arrays.asList(fElements));
  			list.add(element);
  			fElements= (GrayedCheckedModelElement[])list.toArray(new GrayedCheckedModelElement[list.size()]);
  			
  			fLookup.put(element.getWorkingSet(), element);
  		}
  
  		public IWorkingSet[] getAll() {
  			IWorkingSet[] result= new IWorkingSet[fElements.length];
  			for (int i= 0; i < fElements.length; i++) {
  				result[i]= fElements[i].getWorkingSet();
  			}
  			return result;
  		}
  		
  		public GrayedCheckedModelElement getModelElement(IWorkingSet element) {
  			return (GrayedCheckedModelElement)fLookup.get(element);
  		}
  
  		public IWorkingSet[] getChecked() {
  			ArrayList result= new ArrayList();
  			for (int i= 0; i < fElements.length; i++) {
  				if (fElements[i].isChecked())
  					result.add(fElements[i].getWorkingSet());
  			}
  			return (IWorkingSet[])result.toArray(new IWorkingSet[result.size()]);
  		}
  
  		public IWorkingSet[] getGrayed() {
  			ArrayList result= new ArrayList();
  			for (int i= 0; i < fElements.length; i++) {
  				if (fElements[i].isGrayed())
  					result.add(fElements[i].getWorkingSet());
  			}
  			return (IWorkingSet[])result.toArray(new IWorkingSet[result.size()]);
  		}
  
  		public void selectAll() {
  			for (int i= 0; i < fElements.length; i++) {
  				fElements[i].select();
  			}
  		}
  
  		public void deselectAll() {
  			for (int i= 0; i < fElements.length; i++) {
  				fElements[i].deselect();
  			}
  		}
  		
  	}
  
  	private final static class GrayedCheckedModelElement {
  		
  		private final IWorkingSet fWorkingSet;
  		private final int fElementCount;
  		private int fCheckCount;
  		
  		public GrayedCheckedModelElement(IWorkingSet workingSet, int checkCount, int elementCount) {
  			fWorkingSet= workingSet;
  			fCheckCount= checkCount;
  			fElementCount= elementCount;
  		}
  		
  		public IWorkingSet getWorkingSet() {
  			return fWorkingSet;
  		}
  		
  		public int getCheckCount() {
  			return fCheckCount;
  		}
  		
  		public boolean isGrayed() {
  			return isChecked() && fCheckCount < fElementCount;
  		}
  		
  		public boolean isChecked() {
  			return fCheckCount > 0;
  		}
  
  		public void deselect() {
  			fCheckCount= 0;
  		}
  		
  		public void select() {
  			fCheckCount= fElementCount;
  		}
  		
  	}
  	
  	private final class WorkingSetModelAwareSelectionDialog extends SimpleWorkingSetSelectionDialog {
  		
  		private CheckboxTableViewer fTableViewer;
  		private boolean fShowVisibleOnly;
  		private GrayedCheckedModel fModel;
  		private final IAdaptable[] fElements;
  		
  		private WorkingSetModelAwareSelectionDialog(Shell shell, GrayedCheckedModel model, IAdaptable[] elements) {
  			super(shell, VALID_WORKING_SET_IDS, model.getChecked());
  			setTitle(WorkingSetMessages.ConfigureWorkingSetAssignementAction_WorkingSetAssignments_title);
  			fModel= model;
  			fShowVisibleOnly= true;
  			fElements= elements;
  		}
  		
  		public IWorkingSet[] getGrayed() {
  			return fModel.getGrayed();
  		}
  		
  		public IWorkingSet[] getSelection() {
  			return fModel.getChecked();
  		}
  
  		protected CheckboxTableViewer createTableViewer(Composite parent) {
  			fTableViewer= super.createTableViewer(parent);
  			fTableViewer.setGrayedElements(fModel.getGrayed());
  			fTableViewer.addCheckStateListener(new ICheckStateListener() {
  				public void checkStateChanged(CheckStateChangedEvent event) {
  					IWorkingSet element= (IWorkingSet)event.getElement();
  					fTableViewer.setGrayed(element, false);
  					GrayedCheckedModelElement modelElement= fModel.getModelElement(element);
  					if (event.getChecked()) {
  						modelElement.select();
  					} else {
  						modelElement.deselect();
  					}
  					fTableViewer.update(element, null);
  				}
  			});
  			
  			createShowVisibleOnly(parent);
  			
  			return fTableViewer;
  		}
  		
  		protected void selectAll() {
  			super.selectAll();
  			fModel.selectAll();
  			fTableViewer.setGrayedElements(new Object[0]);
  			fTableViewer.refresh();
  		}
  
  		protected void deselectAll() {
  			super.deselectAll();
  			fModel.deselectAll();
  			fTableViewer.setGrayedElements(new Object[0]);
  			fTableViewer.refresh();
  		}
  		
  		/**
  		 * {@inheritDoc}
  		 */
  		protected void addNewWorkingSet(IWorkingSet workingSet) {
  			if (fWorkingSetModel != null) {
  				IWorkingSet[] workingSets= fWorkingSetModel.getActiveWorkingSets();
  				IWorkingSet[] activeWorkingSets= new IWorkingSet[workingSets.length+ 1];
  				activeWorkingSets[0]= workingSet;
  				System.arraycopy(workingSets, 0, activeWorkingSets, 1, workingSets.length);
  				fWorkingSetModel.setActiveWorkingSets(activeWorkingSets);
  			}
  			
  			int checkCount= 0;
  			for (int i= 0; i < fElements.length; i++) {
  				IAdaptable adapted= adapt(workingSet, fElements[i]);
  				if (adapted != null && contains(workingSet, adapted)) {
  					checkCount++;
  				}
  			}
  			
  			fModel.addElement(new GrayedCheckedModelElement(workingSet, checkCount, fElements.length));
  			
  			fTableViewer.setInput(fModel.getAll());
  			fTableViewer.refresh();
  			
  			fTableViewer.setCheckedElements(fModel.getChecked());
  			fTableViewer.setGrayedElements(fModel.getGrayed());
  			
  			fTableViewer.setSelection(new StructuredSelection(workingSet));
  		}
  
  		protected ViewerFilter createTableFilter() {
  			final ViewerFilter superFilter= super.createTableFilter();
  			return new ViewerFilter() {
  				public boolean select(Viewer viewer, Object parentElement, Object element) {
  					if (!superFilter.select(viewer, parentElement, element))
  						return false;
  					
  					IWorkingSet set= (IWorkingSet)element;
  					if (!isValidWorkingSet(set))
  						return false;
  					
  					if (fWorkingSetModel == null)
  						return true;
  					
  					if (fShowVisibleOnly) {
  						if (!fWorkingSetModel.isActiveWorkingSet(set))
  							return false;
  						
  						return true;						
  					} else {
  						return true;
  					}
  				}					
  			};
  		}
  		
  		protected ViewerSorter createTableSorter() {
  			final ViewerSorter superSorter= super.createTableSorter();
  			return new ViewerSorter() {
  				public int compare(Viewer viewer, Object e1, Object e2) {
  					if (fWorkingSetModel == null)
  						return superSorter.compare(viewer, e1, e2);
  					
  					IWorkingSet[] activeWorkingSets= fWorkingSetModel.getActiveWorkingSets();
  					for (int i= 0; i < activeWorkingSets.length; i++) {
  						IWorkingSet active= activeWorkingSets[i];
  						if (active == e1) {
  							return -1;
  						} else if (active == e2) {
  							return 1;
  						}
  					}
  					
  					return superSorter.compare(viewer, e1, e2);
  				}
  			};
  		}
  		
  		protected LabelProvider createTableLabelProvider() {
  			final LabelProvider superLabelProvider= super.createTableLabelProvider();
  			return new LabelProvider() {
  				public String getText(Object element) {
  					String superText= superLabelProvider.getText(element);
  					if (superText == null)
  						return null;
  					
  					GrayedCheckedModelElement modelElement= fModel.getModelElement((IWorkingSet)element);
  					if (!modelElement.isGrayed())
  						return superText;
  					
  					return superText + Messages.format(WorkingSetMessages.ConfigureWorkingSetAssignementAction_XofY_label, new Object[] {Integer.valueOf(modelElement.getCheckCount()), Integer.valueOf(fElements.length)});
  				}
  			};
  		}	
  	
  		private void createShowVisibleOnly(Composite parent) {
  			if (fWorkingSetModel == null)
  				return;
  			
  			Composite bar= new Composite(parent, SWT.NONE);
  			bar.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
  			GridLayout gridLayout= new GridLayout(2, false);
  			gridLayout.marginHeight= 0;
  			gridLayout.marginWidth= 0;
  			bar.setLayout(gridLayout);
  			
  			final Button showVisibleOnly= new Button(bar, SWT.CHECK);
  			showVisibleOnly.setSelection(fShowVisibleOnly);
  			showVisibleOnly.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, false, true));
  			showVisibleOnly.addSelectionListener(new SelectionAdapter() {
  				public void widgetSelected(SelectionEvent e) {
  					fShowVisibleOnly= showVisibleOnly.getSelection();
  					
  					fTableViewer.refresh();
  					
  					fTableViewer.setCheckedElements(fModel.getChecked());
  					fTableViewer.setGrayedElements(fModel.getGrayed());
  				}
  			});
  			
  			Link ppwsLink= new Link(bar, SWT.NONE);
  			ppwsLink.setText(WorkingSetMessages.ConfigureWorkingSetAssignementAction_OnlyShowVisible_link);
  			ppwsLink.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
  			ppwsLink.addSelectionListener(new SelectionAdapter() {
  				public void widgetSelected(SelectionEvent e) {
  					
  					List workingSets= new ArrayList(Arrays.asList(fWorkingSetModel.getAllWorkingSets()));
  					IWorkingSet[] activeWorkingSets= fWorkingSetModel.getActiveWorkingSets();
  					WorkingSetConfigurationDialog dialog= new WorkingSetConfigurationDialog(
  						getShell(), 
  						(IWorkingSet[])workingSets.toArray(new IWorkingSet[workingSets.size()]),
  						activeWorkingSets); 
  					dialog.setSelection(activeWorkingSets);
  					if (dialog.open() == IDialogConstants.OK_ID) {
  						IWorkingSet[] selection= dialog.getSelection();
  						fWorkingSetModel.setActiveWorkingSets(selection);
  					}
  					
  					recalculateCheckedState();
  				}
  			});
  		}
  		
  		private void recalculateCheckedState() {
  			fModel= createGrayedCheckedModel(fElements, getAllWorkingSets());
  			
  			fTableViewer.setInput(fModel.getAll());
  			fTableViewer.refresh();
  			fTableViewer.setCheckedElements(fModel.getChecked());
  			fTableViewer.setGrayedElements(fModel.getGrayed());
  		}
  	}
  
  	private static final String[] VALID_WORKING_SET_IDS= new String[] {
  			JavaWorkingSetUpdater.ID,
  			"org.eclipse.ui.resourceWorkingSetPage" //$NON-NLS-1$
  	};
  	
  	private WorkingSetModel fWorkingSetModel;
  	private final IWorkbenchSite fSite;
  
  	public ConfigureWorkingSetAssignementAction(IWorkbenchSite site) {
  		super(site);
  		fSite= site;
  		setText(WorkingSetMessages.ConfigureWorkingSetAssignementAction_WorkingSets_actionLabel);
  		setEnabled(false);
  	}
  	
  	public void setWorkingSetModel(WorkingSetModel workingSetModel) {
  		fWorkingSetModel= workingSetModel;
  	}
  	
  	public void selectionChanged(IStructuredSelection selection) {
  		setEnabled(canEnable(selection));
  	}
  
  	private boolean canEnable(IStructuredSelection selection) {
  		if (selection.isEmpty())
  			return false;
  		
  		List list= selection.toList();
  		for (Iterator iterator= list.iterator(); iterator.hasNext();) {
  			Object object= iterator.next();
  			if (!(object instanceof IResource) && !(object instanceof IJavaScriptElement))
  				return false;
  		}
  		
  		return true;
  	}
  
  	private IAdaptable[] getSelectedElements(IStructuredSelection selection) {
  		ArrayList result= new ArrayList();
  		
  		List list= selection.toList();
  		for (Iterator iterator= list.iterator(); iterator.hasNext();) {
  			Object object= iterator.next();
  			if (object instanceof IResource || object instanceof IJavaScriptElement) {
  				result.add(object);
  			}
  		}
  		
  		return (IAdaptable[])result.toArray(new IAdaptable[result.size()]);
  	}
  	
  	public void run(IStructuredSelection selection) {
  		IAdaptable[] elements= getSelectedElements(selection);
  		GrayedCheckedModel model= createGrayedCheckedModel(elements, getAllWorkingSets());
  		WorkingSetModelAwareSelectionDialog dialog= new WorkingSetModelAwareSelectionDialog(fSite.getShell(), model, elements);
  		
  		if (elements.length == 1) {
  			IAdaptable element= elements[0];
  			String elementName;
  			if (element instanceof IResource) {
  				elementName= ((IResource)element).getName();
  			} else {
  				elementName= JavaScriptElementLabels.getElementLabel((IJavaScriptElement)element, JavaScriptElementLabels.ALL_DEFAULT);
  			}
  			dialog.setMessage(Messages.format(WorkingSetMessages.ConfigureWorkingSetAssignementAction_DialogMessage_specific, elementName));
  		} else {
  			dialog.setMessage(Messages.format(WorkingSetMessages.ConfigureWorkingSetAssignementAction_DialogMessage_multi, Integer.valueOf(elements.length)));
  		}
  		if (dialog.open() == Window.OK) {
  			updateWorkingSets(dialog.getSelection(), dialog.getGrayed(), elements);
  			selectAndReveal(elements);
  		}
  	}
  	
  	private static GrayedCheckedModel createGrayedCheckedModel(IAdaptable[] elements, IWorkingSet[] workingSets) {
  		GrayedCheckedModelElement[] result= new GrayedCheckedModelElement[workingSets.length];
  		
  		for (int i= 0; i < workingSets.length; i++) {
  			IWorkingSet set= workingSets[i];
  			
  			int checkCount= 0;
  			for (int j= 0; j < elements.length; j++) {
  				IAdaptable adapted= adapt(set, elements[j]);
  				if (adapted != null && contains(set, adapted)) {
  					checkCount++;
  				}
  			}
  			
  			result[i]= new GrayedCheckedModelElement(set, checkCount, elements.length);
  		}
  		
  		return new GrayedCheckedModel(result);
  	}
  
  	private void updateWorkingSets(IWorkingSet[] newWorkingSets, IWorkingSet[] grayedWorkingSets, IAdaptable[] elements) {
  		HashSet selectedSets= new HashSet(Arrays.asList(newWorkingSets));
  		HashSet grayedSets= new HashSet(Arrays.asList(grayedWorkingSets));
  		IWorkingSet[] workingSets= getAllWorkingSets();
  		
  		for (int i= 0; i < workingSets.length; i++) {
  			IWorkingSet workingSet= workingSets[i];
  			if (isValidWorkingSet(workingSet) && !selectedSets.contains(workingSet) && !grayedSets.contains(workingSet)) {
  				for (int j= 0; j < elements.length; j++) {							
  					IAdaptable adapted= adapt(workingSet, elements[j]);
  					if (adapted != null && contains(workingSet, adapted)) {
  						remove(workingSet, adapted);
  					}
  				}
  			}
  		}
  
  		for (int i= 0; i < newWorkingSets.length; i++) {
  			IWorkingSet set= newWorkingSets[i];
  			if (isValidWorkingSet(set) && !grayedSets.contains(set)) {
  				for (int j= 0; j < elements.length; j++) {						
  					IAdaptable adapted= adapt(set, elements[j]);
  					if (adapted != null && !contains(set, adapted)) {
  						add(set, adapted);
  					}
  				}
  			}
  		}
  	}
  	
  	private IWorkingSet[] getAllWorkingSets() {
  		if (fWorkingSetModel != null) {
  			return fWorkingSetModel.getAllWorkingSets();
  		} else {
  			return PlatformUI.getWorkbench().getWorkingSetManager().getWorkingSets();
  		}
  	}
  
  	private static boolean isValidWorkingSet(IWorkingSet set) {
  		if (set.isAggregateWorkingSet() || !set.isSelfUpdating())
  			return false;
  		
  		if (!set.isVisible())
  			return false;
  		
  		if (!set.isEditable())
  			return false;
  
  		for (int i= 0; i < VALID_WORKING_SET_IDS.length; i++) {
  			if (VALID_WORKING_SET_IDS[i].equals(set.getId()))
  				return true;
  		}
  
  		return false;
  	}
  
  	private static IAdaptable adapt(IWorkingSet set, IAdaptable element) {
  		IAdaptable[] adaptedElements= set.adaptElements(new IAdaptable[] {
  			element
  		});
  		if (adaptedElements.length != 1)
  			return null;
  
  		return adaptedElements[0];
  	}
  
  	private static boolean contains(IWorkingSet set, IAdaptable adaptedElement) {
  		IAdaptable[] elements= set.getElements();
  		for (int i= 0; i < elements.length; i++) {
  			if (elements[i].equals(adaptedElement))
  				return true;
  		}
  
  		return false;
  	}
  
  	private static void remove(IWorkingSet workingSet, IAdaptable adaptedElement) {
  		HashSet set= new HashSet(Arrays.asList(workingSet.getElements()));
  		set.remove(adaptedElement);
  		workingSet.setElements((IAdaptable[])set.toArray(new IAdaptable[set.size()]));
  	}
  
  	private static void add(IWorkingSet workingSet, IAdaptable adaptedElement) {
  		IAdaptable[] elements= workingSet.getElements();
  		IAdaptable[] newElements= new IAdaptable[elements.length + 1];
  		System.arraycopy(elements, 0, newElements, 0, elements.length);
  		newElements[elements.length]= adaptedElement;
  		workingSet.setElements(newElements);
  	}
  	
  	private void selectAndReveal(IAdaptable[] elements) {
  		PackageExplorerPart explorer= getActivePackageExplorer();
  		if (explorer != null)
  			explorer.selectReveal(new StructuredSelection(elements));
  	}
  	
  	private PackageExplorerPart getActivePackageExplorer() {
  		IWorkbenchPage page= JavaScriptPlugin.getActivePage();
  		if (page != null) {
  			IWorkbenchPart activePart= page.getActivePart();
  			if (activePart instanceof PackageExplorerPart) {
  				return (PackageExplorerPart) activePart;
  			}
  		}
  		return null;
  	}
  
  }
  