/*******************************************************************************
 * Copyright (c) 2014, 2020 TwelveTone LLC 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:
 * Steven Spungin <steven@spungin.tv> - initial API and implementation, Bug 424730, Bug 435625, Bug 436281
 * Andrej ten Brummelhuis <andrejbrummelhuis@gmail.com> - Bug 395283
 * Marco Descher <marco@descher.at> - Bug 442647
 *******************************************************************************/

package org.eclipse.e4.tools.emf.ui.internal.common.component.dialogs;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;

import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.tools.emf.ui.common.IClassContributionProvider.ContributionData;
import org.eclipse.e4.tools.emf.ui.common.IClassContributionProvider.ContributionResultHandler;
import org.eclipse.e4.tools.emf.ui.common.IClassContributionProvider.Filter;
import org.eclipse.e4.tools.emf.ui.common.ProviderStatus;
import org.eclipse.e4.tools.emf.ui.common.ResourceSearchScope;
import org.eclipse.e4.tools.emf.ui.internal.Messages;
import org.eclipse.e4.tools.emf.ui.internal.common.ClassContributionCollector;
import org.eclipse.e4.tools.emf.ui.internal.common.component.dialogs.AbstractIconDialogWithScopeAndFilter.Entry;
import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.E;
import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.TitleAreaFilterDialog;
import org.eclipse.e4.tools.emf.ui.internal.common.resourcelocator.TargetPlatformClassContributionCollector;
import org.eclipse.e4.tools.emf.ui.internal.common.resourcelocator.TargetPlatformContributionCollector;
import org.eclipse.e4.tools.emf.ui.internal.common.resourcelocator.TargetPlatformIconContributionCollector;
import org.eclipse.e4.tools.emf.ui.internal.common.resourcelocator.dialogs.NonReferencedResourceDialog;
import org.eclipse.e4.tools.emf.ui.internal.common.resourcelocator.dialogs.NonReferencedResourceWizard;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.pde.internal.core.project.PDEProject;
import org.eclipse.pde.internal.core.text.bundle.BundleModel;
import org.eclipse.pde.internal.core.text.bundle.ImportPackageHeader;
import org.eclipse.pde.internal.core.text.bundle.ImportPackageObject;
import org.eclipse.pde.internal.core.text.bundle.RequireBundleHeader;
import org.eclipse.pde.internal.core.text.bundle.RequireBundleObject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
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.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

/**
 * Abstract base class for a find contribution dialog.<br />
 * Includes a filter text box, scope options, and filter options.
 *
 * @author Steven Spungin
 */
@SuppressWarnings("restriction") // uses org.eclipse.pde.internal
public abstract class FilteredContributionDialog extends SaveDialogBoundsSettingsDialog {

	private static final int MAX_RESULTS = 500;
	private static final int DIALOG_MINIMUM_WIDTH = 600;
	private static final int DIALOG_MINIMUM_HEIGHT = 350;

	private Image contributionTypeImage;
	private TableViewer viewer;
	private static final String PREF_SEARCHSCOPE = "searchScope"; //$NON-NLS-1$
	private ResourceSearchScope searchScope = ResourceSearchScope.PROJECT;
	private static final String PREF_SEARCHSCOPES = "searchScopes"; //$NON-NLS-1$
	private EnumSet<ResourceSearchScope> searchScopes = EnumSet.of(ResourceSearchScope.PROJECT);
	// private EnumSet<SearchScope> searchScopes =
	// EnumSet.of(SearchScope.PROJECT, SearchScope.REFERENCES);
	protected ClassContributionCollector collector;
	private Text textBox;
	private Button btnFilterNone;
	private Button btnFilterBundle;
	private Button btnFilterPackage;
	private List<String> filterBundles;
	private List<String> filterPackages;
	private Button btnFilterLocation;
	private List<String> filterLocations;
	private Button btnClearCache;
	private final IEclipseContext context;
	private Composite compOptions;
	protected boolean includeNonBundles;
	private Label lblStatus;
	private Button btnIncludeNoneBundle;
	private WritableList<ContributionData> viewerList;
	protected BundleImageCache imageCache;
	protected Job currentSearchThread;
	private ContributionResultHandlerImpl currentResultHandler;
	protected ProviderStatus providerStatus;
	protected int hint;
	protected int maxResults;
	protected boolean searching;

	abstract protected ClassContributionCollector getCollector();

	abstract protected String getFilterTextMessage();

	abstract protected String getResourceNameText();

	abstract protected String getDialogMessage();

	abstract protected String getDialogTitle();

	abstract protected String getShellTitle();

	private void updateStatusMessage() {
		String message = ""; //$NON-NLS-1$
		if (searching) {
			message += Messages.FilteredContributionDialog_Searching;
		}
		// dlg.setStatus("More than " + filter.maxResults +
		// " items were found and have not been displayed");
		if (hint != 0) {
			if (hint == ContributionResultHandler.MORE_CANCELED) {
				message += Messages.FilteredContributionDialog_SearchWasCancelled;
			} else {
				message += Messages.FilteredContributionDialog_MoreThan + maxResults
						+ Messages.FilteredContributionDialog_itemsWereFound;
			}
		}

		if (getCollector() instanceof TargetPlatformContributionCollector) {
			if (providerStatus != null) {
				switch (providerStatus) {
				case READY:
					break;
				case INITIALIZING:
					message += Messages.FilteredContributionDialog_ProviderInitializing;
					break;
				case CANCELLED:
					message += Messages.FilteredContributionDialog_ProviderWasCancelled;
					break;
				}
			}
		}
		setMessage(message);
	}

	private class ContributionResultHandlerImpl implements ContributionResultHandler {
		private boolean cancled = false;
		private final IObservableList<ContributionData> list;

		public ContributionResultHandlerImpl(IObservableList<ContributionData> list) {
			this.list = list;
		}

		@Override
		public void result(final ContributionData data) {
			if (!cancled) {
				getShell().getDisplay().syncExec(() -> list.add(data));
			}
		}

		@Override
		public void moreResults(final int hint, final Filter filter) {
			if (!cancled) {
				getShell().getDisplay().syncExec(() -> {
					final FilteredContributionDialog dlg = (FilteredContributionDialog) filter.userData;
					dlg.hint = hint;
					dlg.maxResults = filter.maxResults;
					dlg.updateStatusMessage();
				});
			}
		}
	}

	@Override
	public boolean close() {
		stopSearchThread(true);
		getPreferences().put(PREF_SEARCHSCOPE, searchScope.toString());
		getPreferences().put(PREF_SEARCHSCOPES, searchScopes.toString());
		return super.close();
	}

	@Override
	protected Control createContents(Composite parent) {
		final Control ret = super.createContents(parent);
		textBox.notifyListeners(SWT.Modify, new Event());
		textBox.setFocus();
		return ret;
	}

	public FilteredContributionDialog(Shell parentShell, IEclipseContext context) {
		super(parentShell);
		this.context = context;
		imageCache = new BundleImageCache(context.get(Display.class), getClass().getClassLoader());

		final String searchScopeString = getPreferences().get(PREF_SEARCHSCOPE, ResourceSearchScope.PROJECT.toString());
		searchScope = ResourceSearchScope.valueOf(searchScopeString);

		final String searchScopesString = getPreferences().get(PREF_SEARCHSCOPES,
				EnumSet.of(ResourceSearchScope.PROJECT).toString());
		searchScopes = valueOf(ResourceSearchScope.class, searchScopesString);
	}

	public static <TheType extends Enum<TheType>> EnumSet<TheType> valueOf(Class<TheType> eClass, String str) {
		final String[] arr = str.substring(1, str.length() - 1).split(","); //$NON-NLS-1$
		final EnumSet<TheType> set = EnumSet.noneOf(eClass);
		for (final String e : arr) {
			set.add(Enum.valueOf(eClass, e.trim()));
		}
		return set;
	}

	public void setStatus(final String message) {
		getShell().getDisplay().asyncExec(() -> lblStatus.setText(message));
	}

	@Override
	protected void configureShell(Shell newShell) {
		super.configureShell(newShell);
		newShell.setMinimumSize(DIALOG_MINIMUM_WIDTH, DIALOG_MINIMUM_HEIGHT);
	}

	@Override
	protected void createButtonsForButtonBar(Composite parent) {

		// Rename the default OK text for the button
		createButton(parent, IDialogConstants.OK_ID, Messages.FilteredContributionDialog_Select, true);
		createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
		((GridLayout) parent.getLayout()).numColumns = 4;

		btnClearCache = new Button(parent, SWT.PUSH);
		btnClearCache.setText(Messages.FilteredContributionDialog_ClearCache);
		btnClearCache.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				getCollector().clearModelCache();
			}
		});

		btnClearCache.moveAbove(getButton(0));

		lblStatus = new Label(parent, SWT.NONE);
		lblStatus.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		lblStatus.setText(""); //$NON-NLS-1$
		lblStatus.moveAbove(btnClearCache);

		// This is called here instead of create contents because btnClearCache
		// is referenced in updateUiState.
		updateUiState();
	}

	// TODO add results found (and/or more indicator)
	@Override
	protected Control createDialogArea(Composite parent) {
		final Composite comp = (Composite) super.createDialogArea(parent);
		getShell().addDisposeListener(e -> {
			imageCache.dispose();

			if (contributionTypeImage.isDisposed() == false) {
				contributionTypeImage.dispose();
			}
			if (getTitleImageLabel().getImage() != null && getTitleImage().isDisposed() == false) {
				getTitleImageLabel().getImage().dispose();
			}
		});

		getShell().setText(getShellTitle());
		setTitle(getDialogTitle());
		setMessage(getDialogMessage());

		final Image titleImage = getTitleImage();
		setTitleImage(titleImage);

		// TODO param or context
		contributionTypeImage = imageCache.create("/icons/full/obj16/class_obj.png"); //$NON-NLS-1$

		compOptions = new Composite(comp, SWT.NONE);
		compOptions.setLayoutData(new GridData(GridData.FILL_BOTH));
		compOptions.setLayout(new GridLayout(2, false));

		createOptions(compOptions);

		final Label l = new Label(compOptions, SWT.NONE);
		l.setText(getResourceNameText());

		textBox = new Text(compOptions, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH);
		textBox.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		textBox.setMessage(getFilterTextMessage());

		new Label(compOptions, SWT.NONE);

		rebuildViewer();

		collector = getCollector();

		textBox.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				if (e.keyCode == SWT.ARROW_DOWN) {
					if (viewer.getTable().getItemCount() > 0) {
						viewer.getTable().setFocus();
						viewer.getTable().select(0);
					}
				}
			}
		});

		viewer.getTable().addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				super.keyPressed(e);
				if (e.keyCode == SWT.ARROW_UP && viewer.getTable().getSelectionIndex() == 0) {
					textBox.setFocus();
				}
			}
		});

		textBox.addModifyListener(e -> {
			stopSearchThread(true);
			setMessage(""); //$NON-NLS-1$

			viewerList.clear();
			if (doSearch() == true) {
				return;
			}
			searching = true;
			updateStatusMessage();

			currentSearchThread = new Job(Messages.FilteredContributionDialog_ContributionSearch) {

				Filter filter;

				@Override
				protected IStatus run(IProgressMonitor monitor) {
					monitor.beginTask(Messages.FilteredContributionDialog_ContributionSearch, IProgressMonitor.UNKNOWN);
					currentResultHandler = new ContributionResultHandlerImpl(viewerList);
					getShell().getDisplay().syncExec(() -> {
						if (searchScopes.contains(ResourceSearchScope.PROJECT)) {
							filter = new Filter(context.get(IProject.class), textBox.getText());
						} else {
							// filter = new Filter(null,
							// textBox.getText());
							filter = new Filter(context.get(IProject.class), textBox.getText());
						}
					});
					filter.maxResults = MAX_RESULTS;
					filter.userData = FilteredContributionDialog.this;
					filter.setBundles(filterBundles);
					filter.setPackages(filterPackages);
					filter.setLocations(filterLocations);
					filter.setSearchScope(searchScopes);
					filter.setIncludeNonBundles(includeNonBundles);
					filter.setProgressMonitor(monitor);
					filter.setProviderStatusCallback(status -> {
						providerStatus = status;
						try {
							getShell().getDisplay().syncExec(() -> {
								updateStatusMessage();
								switch (status) {
								case READY:
									// This will deadlock if
									// currentSearchThread is not
									// null
									currentSearchThread = null;
									if (currentResultHandler != null) {
										currentResultHandler.cancled = true;
									}
									refreshSearch();
									break;
								case CANCELLED:
								case INITIALIZING:
									break;
								}
							});
						} catch (final Exception e2) {
							// Dialog may have been closed while
							// provider was still indexing
						}
					});
					collector.findContributions(filter, currentResultHandler);

					monitor.done();
					searching = false;
					currentSearchThread = null;
					getShell().getDisplay().syncExec(() -> updateStatusMessage());
					return Status.OK_STATUS;
				}

			};
			currentSearchThread.schedule();

		});

		return comp;
	}

	protected Image getTitleImage() {
		return imageCache.create("/icons/full/wizban/newsearch_wiz.png"); //$NON-NLS-1$
	}

	protected void createOptions(Composite compOptions) {
		{
			final Label lblScope = new Label(compOptions, SWT.NONE);
			lblScope.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
			lblScope.setText(Messages.FilteredContributionDialog_Scope);

			final Composite compScope = new Composite(compOptions, SWT.NONE);
			compScope.setLayoutData(new GridData(SWT.BEGINNING, SWT.TOP, false, false));
			compScope.setLayout(new RowLayout());

			final Button btnScopeProject = new Button(compScope, SWT.RADIO);
			btnScopeProject.setText(Messages.FilteredContributionDialog_ProjectOnly);
			btnScopeProject.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (btnScopeProject.getSelection()) {
						searchScope = ResourceSearchScope.PROJECT;
						searchScopes = EnumSet.of(ResourceSearchScope.PROJECT);
						updateUiState();
						getCollector();
						refreshSearch();
					}
				}
			});
			btnScopeProject.setSelection(searchScopes.contains(ResourceSearchScope.PROJECT)
					&& !searchScopes.contains(ResourceSearchScope.REFERENCES));

			final Button btnProjectAndReferences = new Button(compScope, SWT.RADIO);
			btnProjectAndReferences.setText(Messages.FilteredContributionDialog_ProjectAndReferences);
			btnProjectAndReferences.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (btnProjectAndReferences.getSelection()) {
						searchScope = ResourceSearchScope.PROJECT;
						searchScopes = EnumSet.of(ResourceSearchScope.PROJECT, ResourceSearchScope.REFERENCES);
						updateUiState();
						getCollector();
						refreshSearch();
					}
				}
			});
			btnProjectAndReferences.setSelection(searchScopes.contains(ResourceSearchScope.PROJECT)
					&& searchScopes.contains(ResourceSearchScope.REFERENCES));

			final Button btnScopeWorkspace = new Button(compScope, SWT.RADIO);
			btnScopeWorkspace.setText(Messages.FilteredContributionDialog_Workspace);
			btnScopeWorkspace.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (btnScopeWorkspace.getSelection()) {
						searchScope = ResourceSearchScope.WORKSPACE;
						searchScopes = EnumSet.of(ResourceSearchScope.WORKSPACE);
						updateUiState();
						getCollector();
						refreshSearch();
					}
				}
			});
			btnScopeWorkspace.setSelection(searchScopes.contains(ResourceSearchScope.WORKSPACE));

			final Button btnScopeTargetPlatform = new Button(compScope, SWT.RADIO);
			btnScopeTargetPlatform.setText(Messages.FilteredContributionDialog_TargetPlatform);
			btnScopeTargetPlatform.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (btnScopeTargetPlatform.getSelection()) {
						searchScope = ResourceSearchScope.TARGET_PLATFORM;
						searchScopes = EnumSet.of(ResourceSearchScope.TARGET_PLATFORM);
						updateUiState();
						getCollector();
						refreshSearch();
					}
				}
			});
			btnScopeTargetPlatform.setSelection(searchScopes.contains(ResourceSearchScope.TARGET_PLATFORM));
		}

		{
			final Label lblFilter = new Label(compOptions, SWT.NONE);
			lblFilter.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
			lblFilter.setText(Messages.FilteredContributionDialog_ScopeFilter);

			final Composite compFilter = new Composite(compOptions, SWT.NONE);
			compFilter.setLayoutData(new GridData(SWT.BEGINNING, SWT.TOP, false, false));
			compFilter.setLayout(new RowLayout());

			btnFilterNone = new Button(compFilter, SWT.CHECK);
			btnFilterNone.setText(Messages.FilteredContributionDialog_None);
			btnFilterNone.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (btnFilterNone.getSelection()) {
						removeFilters();
					}
				}
			});

			btnFilterBundle = new Button(compFilter, SWT.CHECK);
			btnFilterBundle.setText(Messages.FilteredContributionDialog_Bundle);
			btnFilterBundle.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (btnFilterBundle.getSelection()) {
						showBundleFilter();
					} else {
						filterBundles = null;
						refreshSearch();
						updateUiState();
					}
				}
			});

			btnFilterPackage = new Button(compFilter, SWT.CHECK);
			btnFilterPackage.setText(Messages.FilteredContributionDialog_Package);
			btnFilterPackage.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (btnFilterPackage.getSelection()) {
						showPackageFilter();
					} else {
						filterPackages = null;
						refreshSearch();
						updateUiState();
					}
				}
			});

			btnFilterLocation = new Button(compFilter, SWT.CHECK);
			btnFilterLocation.setText(Messages.FilteredContributionDialog_Location);
			btnFilterLocation.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (btnFilterLocation.getSelection()) {
						showLocationFilter();
					} else {
						filterLocations = null;
						refreshSearch();
						updateUiState();
					}
				}
			});

		}
		{
			final Label lblIncludeNoneBundle = new Label(compOptions, SWT.NONE);
			lblIncludeNoneBundle.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
			lblIncludeNoneBundle.setText(Messages.FilteredContributionDialog_NonBundles);

			btnIncludeNoneBundle = new Button(compOptions, SWT.CHECK);
			btnIncludeNoneBundle.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
			btnIncludeNoneBundle.setText(""); //$NON-NLS-1$
			btnIncludeNoneBundle.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					includeNonBundles = btnIncludeNoneBundle.getSelection();
					refreshSearch();
				}
			});
		}
	}

	/**
	 *
	 * @return false if default search should be performed, or true if virtual
	 *         function will handle
	 */
	protected boolean doSearch() {
		return false;
	}

	protected void updateUiState() {
		btnFilterNone.setSelection(E.isEmpty(filterBundles) && E.isEmpty(filterPackages) && E.isEmpty(filterLocations));
		btnFilterBundle.setSelection(E.notEmpty(filterBundles));
		btnFilterPackage.setSelection(E.notEmpty(filterPackages));
		btnFilterLocation.setSelection(E.notEmpty(filterLocations));

		// original (default) contribution filter does not support this
		// filtering API
		final boolean enabled = !searchScopes.contains(ResourceSearchScope.PROJECT);
		btnFilterNone.setEnabled(enabled);
		btnFilterBundle.setEnabled(enabled);
		btnFilterLocation.setEnabled(enabled);
		btnFilterPackage.setEnabled(enabled);
		btnClearCache.setEnabled(enabled);
		btnIncludeNoneBundle.setEnabled(enabled);
	}

	protected void removeFilters() {
		filterBundles = null;
		setFilterPackages(null);
		filterLocations = null;
		refreshSearch();
		updateUiState();
	}

	public List<String> getFilterPackages() {
		return filterPackages;
	}

	public void setFilterPackages(List<String> filterPackages) {
		this.filterPackages = filterPackages;
	}

	public List<String> getFilterLocations() {
		return filterLocations;
	}

	public void setFilterLocations(List<String> filterLocations) {
		this.filterLocations = filterLocations;
	}

	public List<String> getFilterBundles() {
		return filterBundles;
	}

	public void setFilterBundles(List<String> filterBundles) {
		this.filterBundles = filterBundles;
	}

	protected void refreshSearch() {
		textBox.notifyListeners(SWT.Modify, new Event());
	}

	protected void showBundleFilter() {
		final Collection<String> bundleIds;
		// TODO make HasBundles an interface so we are not tied to
		// implementation
		if (getCollector() instanceof TargetPlatformClassContributionCollector) {
			bundleIds = TargetPlatformClassContributionCollector.getInstance().getBundleIds();
		} else if (getCollector() instanceof TargetPlatformIconContributionCollector) {
			bundleIds = TargetPlatformIconContributionCollector.getInstance().getBundleIds();
		} else {
			return;
		}

		final ArrayList<String> sorted = new ArrayList<>(bundleIds);
		sorted.sort(null);

		final TitleAreaFilterDialog dlg = new TitleAreaFilterDialog(getShell(), new ColumnLabelProvider()) {
			@Override
			protected Control createContents(Composite parent) {
				final Control ret = super.createContents(parent);
				getViewer().setInput(sorted);
				setMessage(Messages.FilteredContributionDialog_SelectTheBundle);
				setTitle(Messages.FilteredContributionDialog_BundleFilter);
				getShell().setText(Messages.FilteredContributionDialog_BundleFilter);
				try {
					setTitleImage(imageCache.create("/icons/full/wizban/plugin_wiz.gif")); //$NON-NLS-1$
				} catch (final Exception e) {
					e.printStackTrace();
				}
				return ret;
			}
		};
		if (dlg.open() == Window.OK) {
			final ArrayList<String> result = new ArrayList<>();
			result.add(dlg.getFirstSelection());
			setFilterBundles(result);
			refreshSearch();
		}
		updateUiState();
	}

	protected void showPackageFilter() {
		final Collection<String> packages;
		// TODO make HasPackages an interface so we are not tied to
		// implementation
		if (getCollector() instanceof TargetPlatformClassContributionCollector) {
			packages = TargetPlatformClassContributionCollector.getInstance().getPackages();
		} else if (getCollector() instanceof TargetPlatformIconContributionCollector) {
			packages = TargetPlatformIconContributionCollector.getInstance().getPackages();
		} else {
			return;
		}

		final ArrayList<String> sorted = new ArrayList<>(packages);
		sorted.sort(null);

		final TitleAreaFilterDialog dlg = new TitleAreaFilterDialog(getShell(), new ColumnLabelProvider()) {
			@Override
			protected Control createContents(Composite parent) {
				final Control ret = super.createContents(parent);
				getViewer().setInput(sorted);
				setMessage(Messages.FilteredContributionDialog_SelectThePackage);
				setTitle(Messages.FilteredContributionDialog_PackageFilter);
				getShell().setText(Messages.FilteredContributionDialog_PackageFilter);
				setTitleImage(imageCache.create("/icons/full/wizban/package_wiz.png")); //$NON-NLS-1$
				return ret;
			}
		};
		if (dlg.open() == Window.OK) {
			final ArrayList<String> result = new ArrayList<>();
			result.add(dlg.getFirstSelection());
			setFilterBundles(result);
			refreshSearch();
		}
		updateUiState();
	}

	public ResourceSearchScope getScope() {
		return searchScope;
	}

	public void setScope(ResourceSearchScope scope) {
		searchScope = scope;
	}

	public void setCollector(ClassContributionCollector collector) {
		this.collector = collector;
	}

	protected void showLocationFilter() {
		final Collection<String> locations;
		// TODO make HasLocations an interface so we are not tied to
		// implementation
		if (getCollector() instanceof TargetPlatformClassContributionCollector) {
			locations = TargetPlatformClassContributionCollector.getInstance().getLocations();
		} else if (getCollector() instanceof TargetPlatformIconContributionCollector) {
			locations = TargetPlatformIconContributionCollector.getInstance().getLocations();
		} else {
			return;
		}

		// add all parent paths
		final HashSet<String> parentLocations = new HashSet<>();
		for (String location : locations) {
			if (location.endsWith(".jar")) { //$NON-NLS-1$
				final int index = location.lastIndexOf(File.separator);
				if (index >= 0) {
					location = location.substring(0, index);
					parentLocations.add(location);
				}
			} else {
				parentLocations.add(location);
			}
		}

		final ArrayList<String> sorted = new ArrayList<>(parentLocations);
		sorted.sort(null);

		final TitleAreaFilterDialog dlg = new TitleAreaFilterDialog(getShell(), new ColumnLabelProvider()) {
			@Override
			protected Control createContents(Composite parent) {
				final Control ret = super.createContents(parent);
				getViewer().setInput(sorted);
				setMessage(Messages.FilteredContributionDialog_SelectTheLocation);
				setTitle(Messages.FilteredContributionDialog_LocationFilter);
				getShell().setText(Messages.FilteredContributionDialog_LocationFilter);
				setTitleImage(imageCache.create("/icons/full/wizban/location_wiz.png")); //$NON-NLS-1$
				return ret;
			}
		};
		if (dlg.open() == Window.OK) {
			final ArrayList<String> result = new ArrayList<>();
			result.add(dlg.getFirstSelection());
			setFilterBundles(result);
			refreshSearch();
		}
		updateUiState();
	}

	protected void rebuildViewer() {

		viewerList = new WritableList<>();

		final TableViewer oldViewer = viewer;
		viewer = new TableViewer(compOptions, SWT.FULL_SELECTION | SWT.BORDER);
		if (oldViewer != null) {
			viewer.getTable().moveAbove(oldViewer.getTable());
			oldViewer.getTable().dispose();
		}
		final GridData gd = new GridData(GridData.FILL_BOTH);
		viewer.getControl().setLayoutData(gd);
		viewer.setContentProvider(new ObservableListContentProvider<>());
		viewer.setLabelProvider(new StyledCellLabelProvider() {
			@Override
			public void update(ViewerCell cell) {
				ContributionData data;
				if (cell.getElement() instanceof ContributionData) {
					data = (ContributionData) cell.getElement();
				} else if (cell.getElement() instanceof ContributionDataFile) {
					data = ((ContributionDataFile) cell.getElement()).getContributionData();
				} else {
					return;
				}

				final StyledString styledString = new StyledString();
				if (data.className != null) {
					styledString.append(data.className, null);
				}

				if (data.bundleName != null) {
					styledString.append(" - " + data.bundleName, StyledString.DECORATIONS_STYLER); //$NON-NLS-1$
				} else if (data.installLocation != null) {
					styledString.append(" - " + data.installLocation, StyledString.DECORATIONS_STYLER); //$NON-NLS-1$
				}

				if (data.sourceType != null) {
					styledString.append(" - ", StyledString.DECORATIONS_STYLER); //$NON-NLS-1$
					styledString.append(data.sourceType + "", StyledString.COUNTER_STYLER); //$NON-NLS-1$
				}

				if (data.iconPath == null) {
					cell.setImage(contributionTypeImage);
				}

				cell.setText(styledString.getString());
				cell.setStyleRanges(styledString.getStyleRanges());
			}
		});
		viewer.addDoubleClickListener(event -> okPressed());

		viewer.setInput(viewerList);

		if (oldViewer != null) {
			getViewer().getTable().getParent().layout(true, true);
			getViewer().getTable().getParent().redraw();
		}
	}

	public TableViewer getViewer() {
		return viewer;
	}

	public void setViewer(TableViewer viewer) {
		this.viewer = viewer;
	}

	protected Text getFilterTextBox() {
		return textBox;
	}

	public ResourceSearchScope getSearchScope() {
		return searchScope;
	}

	protected IFile getSelectedIfile() {
		final IStructuredSelection s = (IStructuredSelection) getViewer().getSelection();
		if (!s.isEmpty()) {
			final Object selected = s.getFirstElement();
			if (selected instanceof ContributionData) {
				final ContributionData contributionData = (ContributionData) selected;
				return new ContributionDataFile(contributionData);
			} else if (selected instanceof IFile) {
				return (IFile) selected;
			} else if (selected instanceof Entry) {
				final Entry entry = (Entry) selected;
				final ContributionData cd = new ContributionData(null, null, Messages.FilteredContributionDialog_Java,
						entry.file.getFullPath().toOSString());
				cd.installLocation = entry.installLocation;
				cd.resourceRelativePath = entry.file.getProjectRelativePath().toOSString();
				return new ContributionDataFile(cd);
			}
		}
		return null;
	}

	/**
	 * Returns non null if the selected resource is accessible from the current
	 * project<br />
	 * Restrictions may include non-existent file, non exported class, or the
	 * resource is in a location that is not a bundle.<br />
	 * The function, through user intervention, may find a way to resolve the
	 * file and return a resolution.
	 *
	 * @return The original file, a fixed-up (copied or referred) file, or null.
	 */
	protected IFile checkResourceAccessible(final IFile file, String installLocation) {

		// Obviously null is not accessible
		if (file == null) {
			return null;
		}

		// Not a bundle
		final String bundle = getBundle(file);
		if (bundle == null) {
			final String message = Messages.FilteredContributionDialog_ResourceIsNotContainedInABundle;
			final NonReferencedResourceWizard wizard = new NonReferencedResourceWizard(getShell(),
					context.get(IProject.class), bundle, file, installLocation, context);
			wizard.setMessage(message);
			final WizardDialog wizDlg = new WizardDialog(getShell(), wizard);
			wizDlg.setBlockOnOpen(true);
			if (wizDlg.open() == IDialogConstants.OK_ID) {
				return wizard.getResult();
			}
			return null;
		}

		// Reference by current project
		final IProject currentProject = context.get(IProject.class);
		if (currentProject != null && !getBundle(currentProject).equals(bundle)) {
			boolean found = false;
			// search the current project's manifest for require-bundle
			try {
				final BundleModel model = loadBundleModel(currentProject);

				final RequireBundleHeader rbh = (RequireBundleHeader) model.getBundle().getManifestHeader(
						"Require-Bundle"); //$NON-NLS-1$
				if (rbh != null) {
					for (final RequireBundleObject item : rbh.getRequiredBundles()) {
						if (item.getValue().equals(bundle)) {
							found = true;
							break;
						}
					}
				}
				// search the current project's manifest for import-package
				if (!found) {
					if (file instanceof ContributionDataFile) {
						final ContributionDataFile cdFile = (ContributionDataFile) file;
						final String className = cdFile.getContributionData().className;
						if (className != null) {
							final String pakage = NonReferencedResourceDialog.getPackageFromClassName(className);
							final ImportPackageHeader iph = (ImportPackageHeader) model.getBundle().getManifestHeader(
									"Import-Package"); //$NON-NLS-1$
							if (iph != null) {
								for (final ImportPackageObject item : iph.getPackages()) {
									if (item.getValue().equals(pakage)) {
										found = true;
										break;
									}
								}
							}
						}
					}
				}
			} catch (final Exception e) {
			}

			if (!found) {
				final String message = Messages.FilteredContributionDialog_ResourceIsNotReferencedByThisBundle;
				final NonReferencedResourceWizard wizard = new NonReferencedResourceWizard(getShell(),
						context.get(IProject.class), bundle, file, installLocation, context);
				wizard.setMessage(message);
				final WizardDialog wiz = new WizardDialog(getShell(), wizard);
				wiz.setBlockOnOpen(true);
				if (wiz.open() == IDialogConstants.OK_ID) {
					return wizard.getResult();
				}
				return null;
			}
		}
		return file;
	}

	public BundleModel loadBundleModel(IProject currentProject) throws CoreException {
		final Document document = new Document();
		try (final Scanner scanner = new Scanner(PDEProject.getManifest(currentProject).getContents())) {
			final String content = scanner.useDelimiter("\\Z").next(); //$NON-NLS-1$
			document.set(content);
			final BundleModel model = new BundleModel(document, false);
			model.load();
			return model;
		}
	}

	protected EnumSet<ResourceSearchScope> getSearchScopes() {
		return searchScopes;
	}

	public void stopSearchThread(boolean bJoin) {
		if (currentSearchThread != null) {
			currentResultHandler.cancled = true;
			currentSearchThread.cancel();
			if (bJoin) {
				try {
					if (currentSearchThread != null) {
						currentSearchThread.join();
					}
				} catch (final InterruptedException e) {
				} finally {
					currentSearchThread = null;
				}
			} else {
				currentSearchThread = null;
			}
		}
	}

	static public String getBundle(IFile file) {

		if (file instanceof ContributionDataFile) {
			final ContributionDataFile cdFile = (ContributionDataFile) file;
			final String ret = cdFile.getBundle();
			if (ret != null) {
				return ret;
			} else if (cdFile.getContributionData().installLocation != null) {
				return getBundle(cdFile.getContributionData().installLocation);
			} else {
				return null;
			}
		}

		final IProject project = file.getProject();
		return getBundle(project);
	}

	static String getBundle(IProject project) {
		final IFile f = PDEProject.getManifest(project);
		if (f != null && f.exists()) {
			try (final InputStream s = f.getContents();
					BufferedReader r = new BufferedReader(new InputStreamReader(s));) {
				String line;
				while ((line = r.readLine()) != null) {
					if (line.startsWith("Bundle-SymbolicName:")) { //$NON-NLS-1$
						final int start = line.indexOf(':');
						int end = line.indexOf(';');
						if (end == -1) {
							end = line.length();
						}
						return line.substring(start + 1, end).trim();
					}
				}
			} catch (final CoreException e) {
				e.printStackTrace();
			} catch (final IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * Searches the directory for a manifest and parses the symbolic name.
	 *
	 * @return the id of the bundle if existing
	 */
	public static String getBundle(String rootDirectory) {
		final File f = new File(new File(rootDirectory), "/META-INF/MANIFEST.MF"); //$NON-NLS-1$

		if (f.exists()) {
			try (final InputStream s = new FileInputStream(f);
					BufferedReader r = new BufferedReader(new InputStreamReader(s))) {

				String line;
				while ((line = r.readLine()) != null) {
					if (line.startsWith("Bundle-SymbolicName:")) { //$NON-NLS-1$
						final int start = line.indexOf(':');
						int end = line.indexOf(';');
						if (end == -1) {
							end = line.length();
						}
						return line.substring(start + 1, end).trim();
					}
				}
			} catch (final IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
}
