/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.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 java.util.Map;
import java.util.Set;

import com.ibm.icu.text.Collator;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
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.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;

import org.eclipse.core.runtime.IAdaptable;

import org.eclipse.core.resources.IResource;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.IStructuredContentProvider;
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.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;

import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchSite;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.IWorkingSetManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.IWorkingSetNewWizard;
import org.eclipse.ui.dialogs.SelectionDialog;

import org.eclipse.jdt.core.IJavaElement;

import org.eclipse.jdt.internal.corext.util.Messages;

import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.actions.SelectionDispatchAction;

import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.packageview.PackageExplorerPart;
import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;

public final class ConfigureWorkingSetAssignementAction extends SelectionDispatchAction {

	/**
	 * A set of GrayedCheckedModelElements
	 */
	private static final class GrayedCheckedModel {

		private ArrayList<GrayedCheckedModelElement> fElements;

		public GrayedCheckedModel(GrayedCheckedModelElement[] elements) {
			fElements= new ArrayList<>(Arrays.asList(elements));
		}

		public void addElement(GrayedCheckedModelElement element) {
			fElements.add(element);
		}

		public GrayedCheckedModelElement[] getElements() {
			return fElements.toArray(new GrayedCheckedModelElement[fElements.size()]);
		}

		public GrayedCheckedModelElement[] getChecked() {
			ArrayList<GrayedCheckedModelElement> result= new ArrayList<>();
			for (GrayedCheckedModelElement element : fElements) {
				if (element.isChecked())
					result.add(element);
			}
			return result.toArray(new GrayedCheckedModelElement[result.size()]);
		}

		public GrayedCheckedModelElement[] getGrayed() {
			ArrayList<GrayedCheckedModelElement> result= new ArrayList<>();
			for (GrayedCheckedModelElement element : fElements) {
				if (element.isGrayed())
					result.add(element);
			}
			return result.toArray(new GrayedCheckedModelElement[result.size()]);
		}

		public void selectAll() {
			for (GrayedCheckedModelElement fElement : fElements) {
				fElement.select();
			}
		}

		public void deselectAll() {
			for (GrayedCheckedModelElement fElement : fElements) {
				fElement.deselect();
			}
		}

	}

	/**
	 * Connects a IWorkingSet with its grayed-checked state
	 */
	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;
		}

		public int getElementCount() {
			return fElementCount;
		}

	}

	/**
	 * Content provider for a GrayedCheckedModel input
	 */
	private static final class GrayedCheckedModelContentProvider implements IStructuredContentProvider {
		private GrayedCheckedModelElement[] fElements;

		@Override
		public Object[] getElements(Object element) {
			return fElements;
		}

		@Override
		public void dispose() {
			// do nothing
		}

		@Override
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			if (newInput instanceof GrayedCheckedModel) {
				fElements= ((GrayedCheckedModel)newInput).getElements();
			} else {
				fElements= new GrayedCheckedModelElement[0];
			}
		}
	}

	/**
	 * Label provider for GrayedCheckedModelElements
	 */
	private static class GrayedCheckedModelLabelProvider extends LabelProvider {

		private Map<ImageDescriptor, Image> fIcons;

		public GrayedCheckedModelLabelProvider() {
			fIcons= new Hashtable<>();
		}

		@Override
		public void dispose() {
			Iterator<Image> iterator= fIcons.values().iterator();
			while (iterator.hasNext()) {
				Image icon= iterator.next();
				icon.dispose();
			}
			super.dispose();
		}

		@Override
		public Image getImage(Object object) {
			IWorkingSet workingSet= ((GrayedCheckedModelElement)object).getWorkingSet();
			ImageDescriptor imageDescriptor= workingSet.getImageDescriptor();
			if (imageDescriptor == null)
				return null;

			Image icon= fIcons.get(imageDescriptor);
			if (icon == null) {
				icon= imageDescriptor.createImage();
				fIcons.put(imageDescriptor, icon);
			}

			return icon;
		}

		@Override
		public String getText(Object object) {
			GrayedCheckedModelElement modelElement= (GrayedCheckedModelElement)object;
			IWorkingSet workingSet= modelElement.getWorkingSet();
			if (!modelElement.isGrayed()) {
				return BasicElementLabels.getWorkingSetLabel(workingSet);
			} else {
				return Messages.format(WorkingSetMessages.ConfigureWorkingSetAssignementAction_XofY_label, new Object[] { BasicElementLabels.getWorkingSetLabel(workingSet), Integer.valueOf(modelElement.getCheckCount()), Integer.valueOf(modelElement.getElementCount())});
			}

		}

	}

	private final class WorkingSetModelAwareSelectionDialog extends SelectionDialog {

		/**
		 * Section ID for the WorkingSetModelAwareSelectionDialog class.
		 *
		 * @since 3.5
		 */
		private static final String DIALOG_SETTINGS_SECTION= "WorkingSetModelAwareSelectionDialog"; //$NON-NLS-1$

		/**
		 * Key associated with the 'Show Only PE Visible Working Sets' check box.
		 *
		 * @since 3.5
		 */
		private static final String SETTINGS_SHOW_VISIBLE_ONLY= "ShowVisibleOnly"; //$NON-NLS-1$


		private final class GrayedCheckModelElementSorter extends ViewerComparator {
			@Override
			public int compare(Viewer viewer, Object e1, Object e2) {
				GrayedCheckedModelElement w1= (GrayedCheckedModelElement)e1;
				GrayedCheckedModelElement w2= (GrayedCheckedModelElement)e2;
				return Collator.getInstance().compare(w1.getWorkingSet().getLabel(), w2.getWorkingSet().getLabel());
			}
		}

		private class Filter extends ViewerFilter {

			@Override
			public boolean select(Viewer viewer, Object parentElement, Object element) {
				GrayedCheckedModelElement model= (GrayedCheckedModelElement) element;
				IWorkingSet set= model.getWorkingSet();
				return accept(set);
			}

			private boolean accept(IWorkingSet set) {
				if (!isValidWorkingSet(set))
					return false;

				PackageExplorerPart activePart= getActivePackageExplorer();
				if (fWorkingSetModel == null || activePart != null && activePart.getRootMode() == PackageExplorerPart.PROJECTS_AS_ROOTS)
					return true;

				if (fShowVisibleOnly && !fWorkingSetModel.isActiveWorkingSet(set))
					return false;

				return true;
			}
		}

		private CheckboxTableViewer fTableViewer;
		private boolean fShowVisibleOnly;
		private GrayedCheckedModel fModel;
		private final IAdaptable[] fElements;
		private final ArrayList<IWorkingSet> fCreatedWorkingSets;

		/**
		 * @since 3.5
		 */
		private IDialogSettings fSettings;

		private WorkingSetModelAwareSelectionDialog(Shell shell, GrayedCheckedModel model, IAdaptable[] elements) {
			super(shell);

			setTitle(WorkingSetMessages.ConfigureWorkingSetAssignementAction_WorkingSetAssignments_title);
			fModel= model;
			fElements= elements;
			fCreatedWorkingSets= new ArrayList<>();
			fSettings= JavaPlugin.getDefault().getDialogSettingsSection(DIALOG_SETTINGS_SECTION);
			if (fSettings.get(SETTINGS_SHOW_VISIBLE_ONLY) == null) {
				fSettings.put(SETTINGS_SHOW_VISIBLE_ONLY, true);
			}
			fShowVisibleOnly= fSettings.getBoolean(SETTINGS_SHOW_VISIBLE_ONLY);
		}

		/**
		 * {@inheritDoc}
		 *
		 * @since 3.5
		 */
		@Override
		protected void configureShell(Shell shell) {
			super.configureShell(shell);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(shell, IJavaHelpContextIds.WORKING_SET_ASSIGNMENTS_DIALOG);
		}

		public IWorkingSet[] getGrayed() {
			GrayedCheckedModelElement[] grayed= fModel.getGrayed();
			IWorkingSet[] result= new IWorkingSet[grayed.length];
			for (int i= 0; i < grayed.length; i++) {
				result[i]= grayed[i].getWorkingSet();
			}
			return result;
		}

		public IWorkingSet[] getSelection() {
			GrayedCheckedModelElement[] checked= fModel.getChecked();
			IWorkingSet[] result= new IWorkingSet[checked.length];
			for (int i= 0; i < checked.length; i++) {
				result[i]= checked[i].getWorkingSet();
			}
			return result;
		}

		@Override
		protected Control createDialogArea(Composite parent) {
			Composite composite= (Composite)super.createDialogArea(parent);

			createMessageArea(composite);
			Composite inner= new Composite(composite, SWT.NONE);
			inner.setLayoutData(new GridData(GridData.FILL_BOTH));
			GridLayout layout= new GridLayout();
			layout.numColumns= 2;
			layout.marginHeight= 0;
			layout.marginWidth= 0;
			inner.setLayout(layout);

			Composite tableComposite= new Composite(inner, SWT.NONE);
			tableComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
			layout= new GridLayout();
			layout.marginHeight= 0;
			layout.marginWidth= 0;
			tableComposite.setLayout(layout);

			fTableViewer= createTableViewer(tableComposite);
			createShowVisibleOnly(tableComposite);

			createRightButtonBar(inner);

			Dialog.applyDialogFont(composite);
			return composite;
		}

		protected void createRightButtonBar(Composite parent) {
			Composite composite= new Composite(parent, SWT.NONE);
			composite.setLayoutData(new GridData(GridData.FILL_VERTICAL));
			GridLayout layout= new GridLayout();
			layout.marginHeight= 0;
			layout.marginWidth= 0;
			composite.setLayout(layout);

			Button selectAll= new Button(composite, SWT.PUSH);
			selectAll.setText(WorkingSetMessages.ConfigureWorkingSetAssignementAction_SelectAll_button);
			setButtonLayoutData(selectAll);
			selectAll.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					fTableViewer.setAllChecked(true);

					fModel.selectAll();
					fTableViewer.setGrayedElements(new Object[0]);
					fTableViewer.refresh();
				}
			});

			Button deselectAll= new Button(composite, SWT.PUSH);
			deselectAll.setText(WorkingSetMessages.ConfigureWorkingSetAssignementAction_DeselectAll_button);
			setButtonLayoutData(deselectAll);
			deselectAll.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					fTableViewer.setAllChecked(false);

					fModel.deselectAll();
					fTableViewer.setGrayedElements(new Object[0]);
					fTableViewer.refresh();
				}
			});

			@SuppressWarnings("unused")
			Label spacer= new Label(composite, SWT.NONE);

			Button newWorkingSet= new Button(composite, SWT.PUSH);
			newWorkingSet.setText(WorkingSetMessages.ConfigureWorkingSetAssignementAction_New_button);
			setButtonLayoutData(newWorkingSet);
			newWorkingSet.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					IWorkingSetManager manager= PlatformUI.getWorkbench().getWorkingSetManager();

					//can only allow to create java working sets at the moment, see bug 186762
					IWorkingSetNewWizard wizard= manager.createWorkingSetNewWizard(VALID_WORKING_SET_IDS);
					if (wizard == null)
						return;

					WizardDialog dialog= new WizardDialog(getShell(), wizard);
					dialog.create();
					if (dialog.open() == Window.OK) {
						IWorkingSet workingSet= wizard.getSelection();
						if (isValidWorkingSet(workingSet)) {
							manager.addWorkingSet(workingSet);
							addNewWorkingSet(workingSet);
							fCreatedWorkingSets.add(workingSet);
						}
					}
				}
			});
		}

		protected CheckboxTableViewer createTableViewer(Composite parent) {

			final CheckboxTableViewer result= CheckboxTableViewer.newCheckList(parent, SWT.BORDER | SWT.MULTI);
			result.addCheckStateListener(event -> {
				GrayedCheckedModelElement element= (GrayedCheckedModelElement)event.getElement();
				result.setGrayed(element, false);
				if (event.getChecked()) {
					element.select();
				} else {
					element.deselect();
				}
				result.update(element, null);
			});
			GridData data= new GridData(GridData.FILL_BOTH);
			data.heightHint= convertHeightInCharsToPixels(20);
			data.widthHint= convertWidthInCharsToPixels(50);
			result.getTable().setLayoutData(data);

			result.addFilter(new Filter());
			result.setLabelProvider(new GrayedCheckedModelLabelProvider());
			result.setComparator(new GrayedCheckModelElementSorter());
			result.setContentProvider(new GrayedCheckedModelContentProvider());

			result.setInput(fModel);
			result.setCheckedElements(fModel.getChecked());
			result.setGrayedElements((Object[]) fModel.getGrayed());

			return result;
		}

		protected void addNewWorkingSet(IWorkingSet workingSet) {
			int checkCount= 0;
			for (IAdaptable element : fElements) {
				IAdaptable adapted= adapt(workingSet, element);
				if (adapted != null && contains(workingSet, adapted)) {
					checkCount++;
				}
			}

			GrayedCheckedModelElement element= new GrayedCheckedModelElement(workingSet, checkCount, fElements.length);
			fModel.addElement(element);

			fTableViewer.setInput(fModel);
			fTableViewer.refresh();

			fTableViewer.setCheckedElements(fModel.getChecked());
			fTableViewer.setGrayedElements((Object[]) fModel.getGrayed());

			fTableViewer.setSelection(new StructuredSelection(element));
		}

		private void createShowVisibleOnly(Composite parent) {
			PackageExplorerPart activePart= getActivePackageExplorer();
			if (fWorkingSetModel == null || activePart != null && activePart.getRootMode() == PackageExplorerPart.PROJECTS_AS_ROOTS)
				return;

			final Button showVisibleOnly= new Button(parent, SWT.CHECK);
			showVisibleOnly.setText(WorkingSetMessages.ConfigureWorkingSetAssignementAction_OnlyShowVisible_check);
			showVisibleOnly.setSelection(fShowVisibleOnly);
			showVisibleOnly.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false));
			showVisibleOnly.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					fShowVisibleOnly= showVisibleOnly.getSelection();

					fTableViewer.refresh();

					fTableViewer.setCheckedElements(fModel.getChecked());
					fTableViewer.setGrayedElements((Object[]) fModel.getGrayed());
				}
			});

			Link ppwsLink= new Link(parent, SWT.NONE);
			ppwsLink.setText(WorkingSetMessages.ConfigureWorkingSetAssignementAction_OnlyShowVisible_link);
			ppwsLink.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false));
			ppwsLink.addSelectionListener(new SelectionAdapter() {

				@Override
				public void widgetSelected(SelectionEvent e) {

					List<IWorkingSet> workingSets= new ArrayList<>(Arrays.asList(fWorkingSetModel.getAllWorkingSets()));
					boolean isSortingEnabled= fWorkingSetModel.isSortingEnabled();
					WorkingSetConfigurationDialog dialog= new WorkingSetConfigurationDialog(getShell(), workingSets.toArray(new IWorkingSet[workingSets.size()]), isSortingEnabled);
					dialog.setSelection(fWorkingSetModel.getActiveWorkingSets());
					if (dialog.open() == IDialogConstants.OK_ID) {
						isSortingEnabled= dialog.isSortingEnabled();
						fWorkingSetModel.setWorkingSets(dialog.getAllWorkingSets(), isSortingEnabled, dialog.getSelection());
					}

					recalculateCheckedState(dialog.getNewlyAddedWorkingSets());
				}
			});
		}

		/*
		 * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int)
		 * @since 3.5
		 */
		@Override
		protected void buttonPressed(int buttonId) {
			if (IDialogConstants.OK_ID == buttonId)
				fSettings.put(SETTINGS_SHOW_VISIBLE_ONLY, fShowVisibleOnly);
			super.buttonPressed(buttonId);
		}

		private void recalculateCheckedState(List<IWorkingSet> addedWorkingSets) {
			Set<IWorkingSet> checkedWorkingSets= new HashSet<>();
			for (GrayedCheckedModelElement element : fModel.getChecked()) {
				checkedWorkingSets.add(element.getWorkingSet());
			}

			if (addedWorkingSets != null)
				checkedWorkingSets.addAll(addedWorkingSets);

			fModel= createGrayedCheckedModel(fElements, getAllWorkingSets(), checkedWorkingSets);

			fTableViewer.setInput(fModel);
			fTableViewer.refresh();
			fTableViewer.setCheckedElements(fModel.getChecked());
			fTableViewer.setGrayedElements((Object[]) fModel.getGrayed());
		}

		@Override
		protected void cancelPressed() {
			IWorkingSetManager manager= PlatformUI.getWorkbench().getWorkingSetManager();
			for (IWorkingSet fCreatedWorkingSet : fCreatedWorkingSets) {
				manager.removeWorkingSet(fCreatedWorkingSet);
			}

			super.cancelPressed();
		}
	}

	private static final String[] VALID_WORKING_SET_IDS= new String[] {
			IWorkingSetIDs.JAVA,
			IWorkingSetIDs.RESOURCE
	};

	private WorkingSetModel fWorkingSetModel;
	private final IWorkbenchSite fSite;

	public ConfigureWorkingSetAssignementAction(IWorkbenchSite site) {
		super(site);
		fSite= site;
		setText(WorkingSetMessages.ConfigureWorkingSetAssignementAction_WorkingSets_actionLabel);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.ASSIGN_WORKING_SETS_ACTION);
		setEnabled(false);
	}

	public void setWorkingSetModel(WorkingSetModel workingSetModel) {
		fWorkingSetModel= workingSetModel;
	}

	@Override
	public void selectionChanged(IStructuredSelection selection) {
		setEnabled(canEnable(selection));
	}

	private boolean canEnable(IStructuredSelection selection) {
		if (selection.isEmpty())
			return false;

		for (Object object : selection.toList()) {
			if (!(object instanceof IResource) && !(object instanceof IJavaElement))
				return false;
		}

		return true;
	}

	private IAdaptable[] getSelectedElements(IStructuredSelection selection) {
		ArrayList<Object> result= new ArrayList<>();

		for (Object object : selection.toList()) {
			if (object instanceof IResource || object instanceof IJavaElement) {
				result.add(object);
			}
		}

		return result.toArray(new IAdaptable[result.size()]);
	}

	@Override
	public void run(IStructuredSelection selection) {
		IAdaptable[] elements= getSelectedElements(selection);
		GrayedCheckedModel model= createGrayedCheckedModel(elements, getAllWorkingSets(), null);
		WorkingSetModelAwareSelectionDialog dialog= new WorkingSetModelAwareSelectionDialog(fSite.getShell(), model, elements);

		if (elements.length == 1) {
			IAdaptable element= elements[0];
			String elementName;
			if (element instanceof IResource) {
				elementName= org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels.getResourceName((IResource) element);
			} else {
				elementName= JavaElementLabels.getElementLabel((IJavaElement)element, JavaElementLabels.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, Set<IWorkingSet> checkedWorkingSets) {
		GrayedCheckedModelElement[] result= new GrayedCheckedModelElement[workingSets.length];

		for (int i= 0; i < workingSets.length; i++) {
			IWorkingSet workingSet= workingSets[i];

			int checkCount= 0;
			for (IAdaptable element : elements) {
				if (checkedWorkingSets == null) {
					IAdaptable adapted= adapt(workingSet, element);
					if (adapted != null && contains(workingSet, adapted))
						checkCount++;
				} else {
					if (checkedWorkingSets.contains(workingSet))
						checkCount++;
				}
			}

			result[i]= new GrayedCheckedModelElement(workingSet, checkCount, elements.length);
		}

		return new GrayedCheckedModel(result);
	}

	private void updateWorkingSets(IWorkingSet[] newWorkingSets, IWorkingSet[] grayedWorkingSets, IAdaptable[] elements) {
		HashSet<IWorkingSet> selectedSets= new HashSet<>(Arrays.asList(newWorkingSets));
		HashSet<IWorkingSet> grayedSets= new HashSet<>(Arrays.asList(grayedWorkingSets));

		for (IWorkingSet workingSet : getAllWorkingSets()) {
			if (isValidWorkingSet(workingSet) && !selectedSets.contains(workingSet) && !grayedSets.contains(workingSet)) {
				for (IAdaptable element : elements) {
					IAdaptable adapted= adapt(workingSet, element);
					if (adapted != null && contains(workingSet, adapted)) {
						remove(workingSet, adapted);
					}
				}
			}
		}
		for (IWorkingSet set : newWorkingSets) {
			if (isValidWorkingSet(set) && !grayedSets.contains(set)) {
				boolean checkForYetHiddenWorkingSet= false;
				for (IAdaptable element : elements) {
					IAdaptable adapted= adapt(set, element);
					if (adapted != null && !contains(set, adapted)) {
						add(set, adapted);
						checkForYetHiddenWorkingSet= true;
					}
				}
				if (checkForYetHiddenWorkingSet) {
					IWorkingSet[] activeSets= getActiveWorkingSets();
					if (activeSets != null) {
						List<IWorkingSet> activeWorkingSets= new ArrayList<>(Arrays.asList(activeSets));
						if (!activeWorkingSets.contains(set))
							activateWorkingSet(set);
					}
				}
			}
		}
	}

	/**
	 * Adds the given working set to the set of currently active working sets.
	 *
	 * @param workingSet working set to be activated
	 * @since 3.5
	 */
	private void activateWorkingSet(IWorkingSet workingSet) {
		if (fWorkingSetModel != null) {
			fWorkingSetModel.addActiveWorkingSet(workingSet);
		} else {
			PackageExplorerPart activePart= getActivePackageExplorer();
			if (activePart != null) {
				activePart.getWorkingSetModel().addActiveWorkingSet(workingSet);
			}
		}

	}

	/**
	 * Returns an array of currently active WorkingSets.
	 *
	 * @return array of active working sets or <code>null</code> if none
	 * @since 3.5
	 */
	private IWorkingSet[] getActiveWorkingSets() {
		if (fWorkingSetModel != null) {
			return fWorkingSetModel.getActiveWorkingSets();
		} else {
			WorkingSetModel model= null;
			PackageExplorerPart activePart= getActivePackageExplorer();
			if (activePart != null)
				model= activePart.getWorkingSetModel();
			return model == null ? null : model.getActiveWorkingSets();
		}
	}

	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;

		return Arrays.asList(VALID_WORKING_SET_IDS).contains(set.getId());
	}

	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) {
		return Arrays.asList(set.getElements()).contains(adaptedElement);
	}

	private static void remove(IWorkingSet workingSet, IAdaptable adaptedElement) {
		HashSet<IAdaptable> set= new HashSet<>(Arrays.asList(workingSet.getElements()));
		set.remove(adaptedElement);
		workingSet.setElements(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= JavaPlugin.getActivePage();
		if (page != null) {
			IWorkbenchPart activePart= page.getActivePart();
			if (activePart instanceof PackageExplorerPart) {
				return (PackageExplorerPart) activePart;
			}
		}
		return null;
	}

}