/*******************************************************************************
 * Copyright (c) 2000, 2017 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.search;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
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.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IAdaptable;

import org.eclipse.core.resources.IFile;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.DialogPage;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;

import org.eclipse.jface.text.ITextSelection;

import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.IWorkingSetManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.model.IWorkbenchAdapter;

import org.eclipse.search.ui.ISearchPage;
import org.eclipse.search.ui.ISearchPageContainer;
import org.eclipse.search.ui.NewSearchUI;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IModularClassFile;
import org.eclipse.jdt.core.IModuleDescription;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.formatter.IndentManipulation;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchPattern;

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

import org.eclipse.jdt.ui.search.ElementQuerySpecification;
import org.eclipse.jdt.ui.search.PatternQuerySpecification;
import org.eclipse.jdt.ui.search.QuerySpecification;

import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.actions.SelectionConverter;
import org.eclipse.jdt.internal.ui.browsing.LogicalPackage;
import org.eclipse.jdt.internal.ui.dialogs.TextFieldNavigationHandler;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.internal.ui.search.MatchLocations.MatchLocationSelectionDialog;
import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
import org.eclipse.jdt.internal.ui.util.SWTUtil;

public class JavaSearchPage extends DialogPage implements ISearchPage {

	private static class SearchPatternData {
		private final int searchFor;
		private final int limitTo;
		private final String pattern;
		private final boolean isCaseSensitive;
		private final int includeMask;
		private final int matchLocations;
		private final int scope;
		private final IWorkingSet[] workingSets;
		private IJavaElement javaElement;

		public SearchPatternData(int searchFor, int limitTo, int matchLocations, boolean isCaseSensitive, String pattern, IJavaElement element, int includeMask) {
			this(searchFor, limitTo, matchLocations, pattern, isCaseSensitive, element, ISearchPageContainer.WORKSPACE_SCOPE, null, includeMask);
		}

		public SearchPatternData(int searchFor, int limitTo, int matchLocations, String pattern, boolean isCaseSensitive, IJavaElement element, int scope, IWorkingSet[] workingSets, int includeMask) {
			this.searchFor= searchFor;
			this.limitTo= limitTo;
			this.matchLocations= matchLocations;
			this.pattern= pattern;
			this.isCaseSensitive= isCaseSensitive;
			this.scope= scope;
			this.workingSets= workingSets;
			this.includeMask= includeMask;

			setJavaElement(element);
		}

		public void setJavaElement(IJavaElement javaElement) {
			this.javaElement= javaElement;
		}

		public boolean isCaseSensitive() {
			return isCaseSensitive;
		}

		public IJavaElement getJavaElement() {
			return javaElement;
		}

		public int getLimitTo() {
			return limitTo;
		}

		public String getPattern() {
			return pattern;
		}

		public int getScope() {
			return scope;
		}

		public int getSearchFor() {
			return searchFor;
		}

		public IWorkingSet[] getWorkingSets() {
			return workingSets;
		}

		public int getIncludeMask() {
			return includeMask;
		}

		public int getMatchLocations() {
			return matchLocations;
		}

		public void store(IDialogSettings settings) {
			settings.put("searchFor", searchFor); //$NON-NLS-1$
			settings.put("scope", scope); //$NON-NLS-1$
			settings.put("pattern", pattern); //$NON-NLS-1$
			settings.put("limitTo", limitTo); //$NON-NLS-1$
			settings.put("matchLocations", matchLocations); //$NON-NLS-1$
			settings.put("javaElement", javaElement != null ? javaElement.getHandleIdentifier() : ""); //$NON-NLS-1$ //$NON-NLS-2$
			settings.put("isCaseSensitive", isCaseSensitive); //$NON-NLS-1$
			if (workingSets != null) {
				String[] wsIds= new String[workingSets.length];
				for (int i= 0; i < workingSets.length; i++) {
					wsIds[i]= workingSets[i].getName();
				}
				settings.put("workingSets", wsIds); //$NON-NLS-1$
			} else {
				settings.put("workingSets", new String[0]); //$NON-NLS-1$
			}
			settings.put("includeMask", includeMask); //$NON-NLS-1$
		}

		public static SearchPatternData create(IDialogSettings settings) {
			String pattern= settings.get("pattern"); //$NON-NLS-1$
			if (pattern.length() == 0) {
				return null;
			}
			IJavaElement elem= null;
			String handleId= settings.get("javaElement"); //$NON-NLS-1$
			if (handleId != null && handleId.length() > 0) {
				IJavaElement restored= JavaCore.create(handleId);
				if (restored != null && isSearchableType(restored) && restored.exists()) {
					elem= restored;
				}
			}
			String[] wsIds= settings.getArray("workingSets"); //$NON-NLS-1$
			IWorkingSet[] workingSets= null;
			if (wsIds != null && wsIds.length > 0) {
				IWorkingSetManager workingSetManager= PlatformUI.getWorkbench().getWorkingSetManager();
				workingSets= new IWorkingSet[wsIds.length];
				for (int i= 0; workingSets != null && i < wsIds.length; i++) {
					workingSets[i]= workingSetManager.getWorkingSet(wsIds[i]);
					if (workingSets[i] == null) {
						workingSets= null;
					}
				}
			}

			try {
				int searchFor= settings.getInt("searchFor"); //$NON-NLS-1$
				int scope= settings.getInt("scope"); //$NON-NLS-1$
				int limitTo= settings.getInt("limitTo"); //$NON-NLS-1$

				int matchLocations= 0;
				if (settings.get("matchLocations") != null) { //$NON-NLS-1$
					matchLocations= settings.getInt("matchLocations"); //$NON-NLS-1$
				}

				boolean isCaseSensitive= settings.getBoolean("isCaseSensitive"); //$NON-NLS-1$

				int includeMask;
				if (settings.get("includeMask") != null) { //$NON-NLS-1$
					includeMask= settings.getInt("includeMask"); //$NON-NLS-1$
				} else {
					includeMask= JavaSearchScopeFactory.NO_JRE;
					if (settings.get("includeJRE") == null ? forceIncludeAll(limitTo, elem) : settings.getBoolean("includeJRE")) {  //$NON-NLS-1$ //$NON-NLS-2$
						includeMask= JavaSearchScopeFactory.ALL;
					}
				}
				return new SearchPatternData(searchFor, limitTo, matchLocations, pattern, isCaseSensitive, elem, scope, workingSets, includeMask);
			} catch (NumberFormatException e) {
				return null;
			}
		}

	}

	// search for
	private final static int TYPE= IJavaSearchConstants.TYPE;
	private final static int METHOD= IJavaSearchConstants.METHOD;
	private final static int PACKAGE= IJavaSearchConstants.PACKAGE;
	private final static int CONSTRUCTOR= IJavaSearchConstants.CONSTRUCTOR;
	private final static int MODULE= IJavaSearchConstants.MODULE;
	private final static int FIELD= IJavaSearchConstants.FIELD;

	// limit to
	private final static int DECLARATIONS= IJavaSearchConstants.DECLARATIONS;
	private final static int IMPLEMENTORS= IJavaSearchConstants.IMPLEMENTORS;
	private final static int REFERENCES= IJavaSearchConstants.REFERENCES;
	private final static int SPECIFIC_REFERENCES= IJavaSearchConstants.CAST_TYPE_REFERENCE;
	private final static int ALL_OCCURRENCES= IJavaSearchConstants.ALL_OCCURRENCES;
	private final static int READ_ACCESSES= IJavaSearchConstants.READ_ACCESSES;
	private final static int WRITE_ACCESSES= IJavaSearchConstants.WRITE_ACCESSES;

	public static final String PARTICIPANT_EXTENSION_POINT= "org.eclipse.jdt.ui.queryParticipants"; //$NON-NLS-1$

	public static final String EXTENSION_POINT_ID= "org.eclipse.jdt.ui.JavaSearchPage"; //$NON-NLS-1$

	private static final int HISTORY_SIZE= 12;

	// Dialog store id constants
	private final static String PAGE_NAME= "JavaSearchPage"; //$NON-NLS-1$
	private final static String STORE_CASE_SENSITIVE= "CASE_SENSITIVE"; //$NON-NLS-1$
	private final static String STORE_INCLUDE_MASK= "INCLUDE_MASK"; //$NON-NLS-1$
	private final static String STORE_HISTORY= "HISTORY"; //$NON-NLS-1$
	private final static String STORE_HISTORY_SIZE= "HISTORY_SIZE"; //$NON-NLS-1$

	private final List<SearchPatternData> fPreviousSearchPatterns;

	private SearchPatternData fInitialData;
	private IJavaElement fJavaElement;
	private boolean fFirstTime= true;
	private IDialogSettings fDialogSettings;
	private boolean fIsCaseSensitive;

	private Combo fPattern;
	private ISearchPageContainer fContainer;
	private Button fCaseSensitive;

	private Button[] fSearchFor;
	private Button[] fLimitTo;
	private Button[] fIncludeMasks;
	private Group fLimitToGroup;

	private int fMatchLocations;
	private Link fMatchLocationsLink;

	/**
	 *
	 */
	public JavaSearchPage() {
		fPreviousSearchPatterns= new ArrayList<>();
	}


	//---- Action Handling ------------------------------------------------

	@Override
	public boolean performAction() {
		return performNewSearch();
	}

	private boolean performNewSearch() {
		SearchPatternData data= getPatternData();

		// Setup search scope
		IJavaSearchScope scope= null;
		String scopeDescription= ""; //$NON-NLS-1$

		int searchFor= data.getSearchFor();
		int limitTo= data.getLimitTo();
		if (limitTo == SPECIFIC_REFERENCES) {
			limitTo= fMatchLocations;
		}

		int includeMask= data.getIncludeMask();
		JavaSearchScopeFactory factory= JavaSearchScopeFactory.getInstance();

		switch (getContainer().getSelectedScope()) {
			case ISearchPageContainer.WORKSPACE_SCOPE:
				scopeDescription= factory.getWorkspaceScopeDescription(includeMask);
				scope= factory.createWorkspaceScope(includeMask);
				break;
			case ISearchPageContainer.SELECTION_SCOPE:
				IJavaElement[] javaElements= new IJavaElement[0];
				if (getContainer().getActiveEditorInput() != null) {
					IFile file= getContainer().getActiveEditorInput().getAdapter(IFile.class);
					if (file != null && file.exists()) {
						IJavaElement javaElement= JavaCore.create(file);
						if (javaElement != null)
							javaElements= new IJavaElement[] { javaElement };
					}
				} else
					javaElements= factory.getJavaElements(getContainer().getSelection());
				scope= factory.createJavaSearchScope(javaElements, includeMask);
				scopeDescription= factory.getSelectionScopeDescription(javaElements, includeMask);
				break;
			case ISearchPageContainer.SELECTED_PROJECTS_SCOPE: {
				String[] projectNames= getContainer().getSelectedProjectNames();
				scope= factory.createJavaProjectSearchScope(projectNames, includeMask);
				scopeDescription= factory.getProjectScopeDescription(projectNames, includeMask);
				break;
			}
			case ISearchPageContainer.WORKING_SET_SCOPE: {
				IWorkingSet[] workingSets= getContainer().getSelectedWorkingSets();
				// should not happen - just to be sure
				if (workingSets == null || workingSets.length < 1)
					return false;
				scopeDescription= factory.getWorkingSetScopeDescription(workingSets, includeMask);
				scope= factory.createJavaSearchScope(workingSets, includeMask);
				SearchUtil.updateLRUWorkingSets(workingSets);
			}
		}

		QuerySpecification querySpec= null;
		if (data.getJavaElement() != null && getPattern().equals(fInitialData.getPattern())) {
			if (limitTo == REFERENCES)
				SearchUtil.warnIfBinaryConstant(data.getJavaElement(), getShell());
			querySpec= new ElementQuerySpecification(data.getJavaElement(), limitTo, scope, scopeDescription);
		} else {
			querySpec= new PatternQuerySpecification(data.getPattern(), searchFor, data.isCaseSensitive(), limitTo, scope, scopeDescription);
			data.setJavaElement(null);
		}

		JavaSearchQuery textSearchJob= new JavaSearchQuery(querySpec);
		NewSearchUI.runQueryInBackground(textSearchJob);
		return true;
	}

	private int getLimitTo() {
		for (Button button : fLimitTo) {
			if (button.getSelection()) {
				return getIntData(button);
			}
		}
		return -1;
	}

	private int setLimitTo(int searchFor, int limitTo) {
		if (searchFor != TYPE && limitTo == IMPLEMENTORS) {
			limitTo= REFERENCES;
		}

		if (searchFor != FIELD && (limitTo == READ_ACCESSES || limitTo == WRITE_ACCESSES)) {
			limitTo= REFERENCES;
		}

		if (searchFor != TYPE /*&& searchFor != FIELD*/ && searchFor != METHOD && searchFor != CONSTRUCTOR && limitTo == SPECIFIC_REFERENCES) {
			limitTo= REFERENCES;
		}
		fillLimitToGroup(searchFor, limitTo);
		return limitTo;
	}

	private int getIncludeMask() {
		int mask= 0;
		for (Button button : fIncludeMasks) {
			if (button.getSelection()) {
				mask |= getIntData(button);
			}
		}
		return mask;
	}

	private void setIncludeMask(int includeMask) {
		for (Button button : fIncludeMasks) {
			button.setSelection((includeMask & getIntData(button)) != 0);
		}
	}

	private void setMatchLocations(int matchLocations) {
		fMatchLocations= matchLocations;
		updateMatchLocationText();
	}

	private String[] getPreviousSearchPatterns() {
		// Search results are not persistent
		int patternCount= fPreviousSearchPatterns.size();
		String [] patterns= new String[patternCount];
		for (int i= 0; i < patternCount; i++)
			patterns[i]= fPreviousSearchPatterns.get(i).getPattern();
		return patterns;
	}

	private int getSearchFor() {
		for (Button button : fSearchFor) {
			if (button.getSelection()) {
				return getIntData(button);
			}
		}
		Assert.isTrue(false, "shouldNeverHappen"); //$NON-NLS-1$
		return -1;
	}

	private void setSearchFor(int searchFor) {
		for (Button button : fSearchFor) {
			button.setSelection(searchFor == getIntData(button));
		}
	}

	private int getIntData(Button button) {
		return ((Integer) button.getData());
	}

	private String getPattern() {
		return fPattern.getText();
	}


	private SearchPatternData findInPrevious(String pattern) {
		for (SearchPatternData element : fPreviousSearchPatterns) {
			if (pattern.equals(element.getPattern())) {
				return element;
			}
		}
		return null;
	}

	/**
	 * Return search pattern data and update previous searches.
	 * An existing entry will be updated.
	 * @return the pattern data
	 */
	private SearchPatternData getPatternData() {
		String pattern= getPattern();
		SearchPatternData match= findInPrevious(pattern);
		if (match != null) {
			fPreviousSearchPatterns.remove(match);
		}
		match= new SearchPatternData(
				getSearchFor(),
				getLimitTo(),
				fMatchLocations,
				pattern,
				fCaseSensitive.getSelection(),
				fJavaElement,
				getContainer().getSelectedScope(),
				getContainer().getSelectedWorkingSets(),
				getIncludeMask()
		);

		fPreviousSearchPatterns.add(0, match); // insert on top
		return match;
	}

	/*
	 * Implements method from IDialogPage
	 */
	@Override
	public void setVisible(boolean visible) {
		if (visible && fPattern != null) {
			if (fFirstTime) {
				fFirstTime= false;
				// Set item and text here to prevent page from resizing
				fPattern.setItems(getPreviousSearchPatterns());
				initSelections();
			}
			fPattern.setFocus();
		}
		updateOKStatus();

		IEditorInput editorInput= getContainer().getActiveEditorInput();
		getContainer().setActiveEditorCanProvideScopeSelection(editorInput != null && editorInput.getAdapter(IFile.class) != null);

		super.setVisible(visible);
	}

	public boolean isValid() {
		return true;
	}

	//---- Widget creation ------------------------------------------------


	@Override
	public void createControl(Composite parent) {
		initializeDialogUnits(parent);
		readConfiguration();

		Composite result= new Composite(parent, SWT.NONE);

		GridLayout layout= new GridLayout(2, true);
		layout.horizontalSpacing= 10;
		result.setLayout(layout);

		Control expressionComposite= createExpression(result);
		expressionComposite.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 2, 1));

		Label separator= new Label(result, SWT.NONE);
		separator.setVisible(false);
		GridData data= new GridData(GridData.FILL, GridData.FILL, false, false, 2, 1);
		data.heightHint= convertHeightInCharsToPixels(1) / 3;
		separator.setLayoutData(data);

		Control searchFor= createSearchFor(result);
		searchFor.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false, 1, 1));

		Control limitTo= createLimitTo(result);
		limitTo.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false, 1, 1));

		Control includeMask= createIncludeMask(result);
		includeMask.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false, 2, 1));

		//createParticipants(result);

		SelectionAdapter javaElementInitializer= new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent event) {
				if (getSearchFor() == fInitialData.getSearchFor())
					fJavaElement= fInitialData.getJavaElement();
				else
					fJavaElement= null;
				setLimitTo(getSearchFor(), getLimitTo());
				setIncludeMask(getIncludeMask());
				doPatternModified();
			}
		};

		for (Button b : fSearchFor) {
			b.addSelectionListener(javaElementInitializer);
		}

		setControl(result);

		Dialog.applyDialogFont(result);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(result, IJavaHelpContextIds.JAVA_SEARCH_PAGE);
	}


	/*private Control createParticipants(Composite result) {
		if (!SearchParticipantsExtensionPoint.hasAnyParticipants())
			return new Composite(result, SWT.NULL);
		Button selectParticipants= new Button(result, SWT.PUSH);
		selectParticipants.setText(SearchMessages.getString("SearchPage.select_participants.label")); //$NON-NLS-1$
		GridData gd= new GridData();
		gd.verticalAlignment= GridData.VERTICAL_ALIGN_BEGINNING;
		gd.horizontalAlignment= GridData.HORIZONTAL_ALIGN_END;
		gd.grabExcessHorizontalSpace= false;
		gd.horizontalAlignment= GridData.END;
		gd.horizontalSpan= 2;
		selectParticipants.setLayoutData(gd);
		selectParticipants.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				PreferencePageSupport.showPreferencePage(getShell(), "org.eclipse.jdt.ui.preferences.SearchParticipantsExtensionPoint", new SearchParticipantsExtensionPoint()); //$NON-NLS-1$
			}

		});
		return selectParticipants;
	}*/


	private Control createExpression(Composite parent) {
		Composite result= new Composite(parent, SWT.NONE);
		GridLayout layout= new GridLayout(2, false);
		layout.marginWidth= 0;
		layout.marginHeight= 0;
		result.setLayout(layout);

		// Pattern text + info
		Label label= new Label(result, SWT.LEFT);
		label.setText(SearchMessages.SearchPage_expression_label);
		label.setLayoutData(new GridData(GridData.FILL, GridData.FILL, false, false, 2, 1));

		// Pattern combo
		fPattern= new Combo(result, SWT.SINGLE | SWT.BORDER);
		SWTUtil.setDefaultVisibleItemCount(fPattern);
		fPattern.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handlePatternSelected();
				updateOKStatus();
			}
		});
		fPattern.addModifyListener(e -> {
			doPatternModified();
			updateOKStatus();

		});
		TextFieldNavigationHandler.install(fPattern);
		GridData data= new GridData(GridData.FILL, GridData.FILL, true, false, 1, 1);
		data.widthHint= convertWidthInCharsToPixels(50);
		fPattern.setLayoutData(data);

		// Ignore case checkbox
		fCaseSensitive= new Button(result, SWT.CHECK);
		fCaseSensitive.setText(SearchMessages.SearchPage_expression_caseSensitive);
		fCaseSensitive.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				fIsCaseSensitive= fCaseSensitive.getSelection();
			}
		});
		fCaseSensitive.setLayoutData(new GridData(GridData.FILL, GridData.FILL, false, false, 1, 1));

		return result;
	}

	final void updateOKStatus() {
		boolean isValidPattern= isValidSearchPattern();
		boolean isValidMask= getIncludeMask() != 0;
		getContainer().setPerformActionEnabled(isValidPattern && isValidMask);
	}

	private boolean isValidSearchPattern() {
		if (getPattern().length() == 0) {
			return false;
		}
		if (fJavaElement != null) {
			return true;
		}
		return SearchPattern.createPattern(getPattern(), getSearchFor(), getLimitTo(), SearchPattern.R_EXACT_MATCH) != null;
	}


	@Override
	public void dispose() {
		writeConfiguration();
		super.dispose();
	}

	private void doPatternModified() {
		if (fInitialData != null && getPattern().equals(fInitialData.getPattern()) && fInitialData.getJavaElement() != null && fInitialData.getSearchFor() == getSearchFor()) {
			fCaseSensitive.setEnabled(false);
			fCaseSensitive.setSelection(true);
			fJavaElement= fInitialData.getJavaElement();
		} else {
			fCaseSensitive.setEnabled(true);
			fCaseSensitive.setSelection(fIsCaseSensitive);
			fJavaElement= null;
		}
	}

	private void handlePatternSelected() {
		int selectionIndex= fPattern.getSelectionIndex();
		if (selectionIndex < 0 || selectionIndex >= fPreviousSearchPatterns.size())
			return;

		SearchPatternData initialData= fPreviousSearchPatterns.get(selectionIndex);

		setSearchFor(initialData.getSearchFor());
		setLimitTo(initialData.getSearchFor(), initialData.getLimitTo());
		setIncludeMask(initialData.getIncludeMask());
		setMatchLocations(initialData.getMatchLocations());

		fPattern.setText(initialData.getPattern());
		fIsCaseSensitive= initialData.isCaseSensitive();
		fJavaElement= initialData.getJavaElement();
		fCaseSensitive.setEnabled(fJavaElement == null);
		fCaseSensitive.setSelection(initialData.isCaseSensitive());


		if (initialData.getWorkingSets() != null)
			getContainer().setSelectedWorkingSets(initialData.getWorkingSets());
		else
			getContainer().setSelectedScope(initialData.getScope());

		fInitialData= initialData;
	}


	private Control createSearchFor(Composite parent) {
		Group result= new Group(parent, SWT.NONE);
		result.setText(SearchMessages.SearchPage_searchFor_label);
		result.setLayout(new GridLayout(2, true));

		fSearchFor= new Button[] {
			createButton(result, SWT.RADIO, SearchMessages.SearchPage_searchFor_type, TYPE, true),
			createButton(result, SWT.RADIO, SearchMessages.SearchPage_searchFor_method, METHOD, false),
			createButton(result, SWT.RADIO, SearchMessages.SearchPage_searchFor_package, PACKAGE, false),
			createButton(result, SWT.RADIO, SearchMessages.SearchPage_searchFor_constructor, CONSTRUCTOR, false),
			createButton(result, SWT.RADIO, SearchMessages.SearchPage_searchFor_module, MODULE, false),
			createButton(result, SWT.RADIO, SearchMessages.SearchPage_searchFor_field, FIELD, false)
		};

		return result;
	}

	private Control createLimitTo(Composite parent) {
		fLimitToGroup= new Group(parent, SWT.NONE);
		fLimitToGroup.setText(SearchMessages.SearchPage_limitTo_label);
		fLimitToGroup.setLayout(new GridLayout(2, false));

		fillLimitToGroup(TYPE, ALL_OCCURRENCES);

		return fLimitToGroup;
	}

	private void fillLimitToGroup(int searchFor, int limitTo) {
		for (Control child : fLimitToGroup.getChildren()) {
			child.dispose();
		}
		fMatchLocationsLink= null;


		ArrayList<Button> buttons= new ArrayList<>();
		buttons.add(createButton(fLimitToGroup, SWT.RADIO, SearchMessages.SearchPage_limitTo_allOccurrences, ALL_OCCURRENCES, limitTo == ALL_OCCURRENCES));
		buttons.add(createButton(fLimitToGroup, SWT.RADIO, SearchMessages.SearchPage_limitTo_declarations, DECLARATIONS, limitTo == DECLARATIONS));

		buttons.add(createButton(fLimitToGroup, SWT.RADIO, SearchMessages.SearchPage_limitTo_references, REFERENCES, limitTo == REFERENCES));
		if (searchFor == TYPE) {
			buttons.add(createButton(fLimitToGroup, SWT.RADIO, SearchMessages.SearchPage_limitTo_implementors, IMPLEMENTORS, limitTo == IMPLEMENTORS));

			buttons.add(createMethodLocationRadio(limitTo == SPECIFIC_REFERENCES));
		}

		if (searchFor == FIELD) {
			buttons.add(createButton(fLimitToGroup, SWT.RADIO, SearchMessages.SearchPage_limitTo_readReferences, READ_ACCESSES, limitTo == READ_ACCESSES));
			buttons.add(createButton(fLimitToGroup, SWT.RADIO, SearchMessages.SearchPage_limitTo_writeReferences, WRITE_ACCESSES, limitTo == WRITE_ACCESSES));

//			buttons.add(createMethodLocationRadio(limitTo == SPECIFIC_REFERENCES));
		}

		if (searchFor == METHOD || searchFor == CONSTRUCTOR) {
			buttons.add(createMethodLocationRadio(limitTo == SPECIFIC_REFERENCES));
		}

		fLimitTo= buttons.toArray(new Button[buttons.size()]);

		SelectionAdapter listener= new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				performLimitToSelectionChanged((Button) e.widget);
			}
		};
		for (Button b : fLimitTo) {
			b.addSelectionListener(listener);
		}
		Dialog.applyDialogFont(fLimitToGroup); // re-apply font as we disposed the previous widgets

		fLimitToGroup.layout();
//		// searchFor == FIELD needs one more row than the others
//		fLimitToGroup.getShell().layout(new Control[] { fLimitToGroup.getChildren()[0] });
	}


	private Button createMethodLocationRadio(boolean isSelected) {
		if (fLimitToGroup.getChildren().length % 2 != 0)
			new Label(fLimitToGroup, SWT.NONE);

		Button button= createButton(fLimitToGroup, SWT.RADIO, SearchMessages.JavaSearchPage_match_locations_label, SPECIFIC_REFERENCES, isSelected);
		fMatchLocationsLink= new Link(fLimitToGroup, SWT.NONE);
		fMatchLocationsLink.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		fMatchLocationsLink.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				performConfigureMatchLocation();
			}
			@Override
			public void widgetSelected(SelectionEvent e) {
				performConfigureMatchLocation();
			}
		});
		fMatchLocationsLink.getAccessible().addAccessibleListener(new AccessibleAdapter() {
			/*
			 * @see org.eclipse.swt.accessibility.AccessibleAdapter#getName(org.eclipse.swt.accessibility.AccessibleEvent)
			 * @since 3.8
			 */
			@Override
			public void getName(AccessibleEvent e) {
				if (e.childID == ACC.CHILDID_SELF)
					e.result= SearchMessages.JavaSearchPage_match_location_link_label_tooltip;
			}
		});
		updateMatchLocationText();

		return button;
	}

	private void updateMatchLocationText() {
		if (fMatchLocationsLink != null) {
			int searchFor= getSearchFor();
			int totNum= MatchLocations.getTotalNumberOfSettings(searchFor);
			int currNum= MatchLocations.getNumberOfSelectedSettings(fMatchLocations, searchFor);

			fMatchLocationsLink.setText(Messages.format(SearchMessages.JavaSearchPage_match_location_link_label, new Object[] { Integer.valueOf(currNum), Integer.valueOf(totNum) }));
			fMatchLocationsLink.setToolTipText(SearchMessages.JavaSearchPage_match_location_link_label_tooltip);
		}
	}

	protected final void performLimitToSelectionChanged(Button button) {
		if (button.getSelection()) {
			for (Button curr : fLimitTo) {
				if (curr != button) {
					curr.setSelection(false);
				}
			}
		}
		updateUseJRE();
	}


	protected final void performConfigureMatchLocation() {
		for (Button curr : fLimitTo) {
			curr.setSelection(getIntData(curr) == SPECIFIC_REFERENCES);
		}

		MatchLocationSelectionDialog locationSelectionDialog= new MatchLocationSelectionDialog(getShell(), fMatchLocations, getSearchFor());
		if (locationSelectionDialog.open() == Window.OK) {
			setMatchLocations(locationSelectionDialog.getCurrentSelection());
		}
	}


	private Control createIncludeMask(Composite parent) {
		Group result= new Group(parent, SWT.NONE);
		result.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
		result.setText(SearchMessages.SearchPage_searchIn_label);
		result.setLayout(new GridLayout(4, false));
		fIncludeMasks= new Button[] {
			createButton(result, SWT.CHECK, SearchMessages.SearchPage_searchIn_sources, JavaSearchScopeFactory.SOURCES, true),
			createButton(result, SWT.CHECK, SearchMessages.SearchPage_searchIn_projects, JavaSearchScopeFactory.PROJECTS, true),
			createButton(result, SWT.CHECK, SearchMessages.SearchPage_searchIn_jre, JavaSearchScopeFactory.JRE, false),
			createButton(result, SWT.CHECK, SearchMessages.SearchPage_searchIn_libraries, JavaSearchScopeFactory.LIBS, true),
		};

		SelectionAdapter listener= new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				updateOKStatus();
			}
		};
		for (Button fIncludeMask : fIncludeMasks) {
			fIncludeMask.addSelectionListener(listener);
		}

		return result;
	}

	private Button createButton(Composite parent, int style, String text, int data, boolean isSelected) {
		Button button= new Button(parent, style);
		button.setText(text);
		button.setData(Integer.valueOf(data));
		button.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		button.setSelection(isSelected);
		return button;
	}

	private void initSelections() {
		ISelection sel= getContainer().getSelection();

		IWorkbenchPage activePage= JavaPlugin.getActivePage();
		if (activePage != null) {
			IWorkbenchPart activePart= activePage.getActivePart();
			if (activePart instanceof JavaEditor) {
				JavaEditor javaEditor= (JavaEditor) activePart;
				if (javaEditor.isBreadcrumbActive()) {
					sel= javaEditor.getBreadcrumb().getSelectionProvider().getSelection();
				}
			}
		}

		SearchPatternData initData= null;

		if (sel instanceof IStructuredSelection) {
			initData= tryStructuredSelection((IStructuredSelection) sel);
		} else if (sel instanceof ITextSelection) {
			IEditorPart activeEditor= getActiveEditor();
			if (activeEditor instanceof JavaEditor) {
				try {
					IJavaElement[] elements= SelectionConverter.codeResolve((JavaEditor) activeEditor);
					if (elements != null && elements.length > 0) {
						initData= determineInitValuesFrom(elements[0]);
					}
				} catch (JavaModelException e) {
					// ignore
				}
			}
			if (initData == null) {
				initData= trySimpleTextSelection((ITextSelection) sel);
			}
		}
		if (initData == null) {
			initData= getDefaultInitValues();
		}

		fInitialData= initData;
		fJavaElement= initData.getJavaElement();
		fCaseSensitive.setSelection(initData.isCaseSensitive());
		fCaseSensitive.setEnabled(fJavaElement == null);

		setSearchFor(initData.getSearchFor());
		setLimitTo(initData.getSearchFor(), initData.getLimitTo());
		setIncludeMask(initData.getIncludeMask());
		setMatchLocations(initData.getMatchLocations());

		fPattern.setText(initData.getPattern());
	}

	private void updateUseJRE() {
		setIncludeMask(getIncludeMask());
	}

	private static boolean forceIncludeAll(int limitTo, IJavaElement elem) {
		return elem != null && (limitTo == DECLARATIONS || limitTo == IMPLEMENTORS);
	}

	private SearchPatternData tryStructuredSelection(IStructuredSelection selection) {
		if (selection == null || selection.size() > 1)
			return null;

		Object o= selection.getFirstElement();
		SearchPatternData res= null;
		if (o instanceof IJavaElement) {
			res= determineInitValuesFrom((IJavaElement) o);
		} else if (o instanceof LogicalPackage) {
			LogicalPackage lp= (LogicalPackage)o;
			return new SearchPatternData(PACKAGE, REFERENCES, 0, fIsCaseSensitive, lp.getElementName(), null, getLastIncludeMask());
		} else if (o instanceof IAdaptable) {
			IJavaElement element= ((IAdaptable) o).getAdapter(IJavaElement.class);
			if (element != null) {
				res= determineInitValuesFrom(element);
			}
		}
		if (res == null && o instanceof IAdaptable) {
			IWorkbenchAdapter adapter= ((IAdaptable)o).getAdapter(IWorkbenchAdapter.class);
			if (adapter != null) {
				return new SearchPatternData(TYPE, REFERENCES, 0, fIsCaseSensitive, adapter.getLabel(o), null, getLastIncludeMask());
			}
		}
		return res;
	}

	final static boolean isSearchableType(IJavaElement element) {
		switch (element.getElementType()) {
			case IJavaElement.PACKAGE_FRAGMENT:
			case IJavaElement.PACKAGE_DECLARATION:
			case IJavaElement.IMPORT_DECLARATION:
			case IJavaElement.TYPE:
			case IJavaElement.FIELD:
			case IJavaElement.METHOD:
			case IJavaElement.JAVA_MODULE:
				return true;
		}
		return false;
	}

	private SearchPatternData determineInitValuesFrom(IJavaElement element) {
		try {
			//JavaSearchScopeFactory factory= JavaSearchScopeFactory.getInstance();
			//boolean isInsideJRE= factory.isInsideJRE(element);
			int includeMask= getLastIncludeMask();

			switch (element.getElementType()) {
				case IJavaElement.JAVA_MODULE:
					return new SearchPatternData(MODULE, REFERENCES, 0, true, element.getElementName(), element, includeMask);
				case IJavaElement.PACKAGE_FRAGMENT:
				case IJavaElement.PACKAGE_DECLARATION:
					return new SearchPatternData(PACKAGE, REFERENCES, 0, true, element.getElementName(), element, includeMask);
				case IJavaElement.IMPORT_DECLARATION: {
					IImportDeclaration declaration= (IImportDeclaration) element;
					if (declaration.isOnDemand()) {
						String name= Signature.getQualifier(declaration.getElementName());
						return new SearchPatternData(PACKAGE, DECLARATIONS, 0, true, name, element, JavaSearchScopeFactory.ALL);
					}
					return new SearchPatternData(TYPE, DECLARATIONS, 0, true, element.getElementName(), element, JavaSearchScopeFactory.ALL);
				}
				case IJavaElement.TYPE:
					return new SearchPatternData(TYPE, REFERENCES, 0, true, PatternStrings.getTypeSignature((IType) element), element, includeMask);
				case IJavaElement.COMPILATION_UNIT: {
					ICompilationUnit cu= (ICompilationUnit) element;
					IType mainType= cu.findPrimaryType();
					if (mainType != null) {
						return new SearchPatternData(TYPE, REFERENCES, 0, true, PatternStrings.getTypeSignature(mainType), mainType, includeMask);
					}
					IModuleDescription module= cu.getModule();
					if (module != null && module.exists()) {
						return new SearchPatternData(MODULE, REFERENCES, 0, true, PatternStrings.getSignature(module), module, includeMask);
					}
					break;
				}
				case IJavaElement.CLASS_FILE: {
					if (element instanceof IOrdinaryClassFile) {
						IType mainType= ((IOrdinaryClassFile) element).getType();
						if (mainType.exists()) {
							return new SearchPatternData(TYPE, REFERENCES, 0, true, PatternStrings.getTypeSignature(mainType), mainType, includeMask);
						}
					} else if (element instanceof IModularClassFile) {
						IModuleDescription module= ((IModularClassFile) element).getModule();
						if (module != null && module.exists()) {
							return new SearchPatternData(MODULE, REFERENCES, 0, true, PatternStrings.getSignature(module), module, includeMask);
						}

					}
					break;
				}
				case IJavaElement.FIELD:
					return new SearchPatternData(FIELD, REFERENCES, 0, true, PatternStrings.getFieldSignature((IField) element), element, includeMask);
				case IJavaElement.METHOD:
					IMethod method= (IMethod) element;
					int searchFor= method.isConstructor() ? CONSTRUCTOR : METHOD;
					return new SearchPatternData(searchFor, REFERENCES, 0, true, PatternStrings.getMethodSignature(method), element, includeMask);
			}

		} catch (JavaModelException e) {
			if (!e.isDoesNotExist()) {
				ExceptionHandler.handle(e, SearchMessages.Search_Error_javaElementAccess_title, SearchMessages.Search_Error_javaElementAccess_message);
			}
			// element might not exist
		}
		return null;
	}

	private SearchPatternData trySimpleTextSelection(ITextSelection selection) {
		String selectedText= selection.getText();
		if (selectedText != null && selectedText.length() > 0) {
			int i= 0;
			while (i < selectedText.length() && !IndentManipulation.isLineDelimiterChar(selectedText.charAt(i))) {
				i++;
			}
			if (i > 0) {
				return new SearchPatternData(TYPE, REFERENCES, 0, fIsCaseSensitive, selectedText.substring(0, i), null, JavaSearchScopeFactory.ALL);
			}
		}
		return null;
	}

	private SearchPatternData getDefaultInitValues() {
		if (!fPreviousSearchPatterns.isEmpty()) {
			return fPreviousSearchPatterns.get(0);
		}

		return new SearchPatternData(TYPE, REFERENCES, 0, fIsCaseSensitive, "", null, getLastIncludeMask()); //$NON-NLS-1$
	}

	private int getLastIncludeMask() {
		try {
			return getDialogSettings().getInt(STORE_INCLUDE_MASK);
		} catch (NumberFormatException e) {
			return JavaSearchScopeFactory.NO_JRE;
		}
	}

	/*
	 * Implements method from ISearchPage
	 */
	@Override
	public void setContainer(ISearchPageContainer container) {
		fContainer= container;
	}

	/**
	 * Returns the search page's container.
	 * @return the search page container
	 */
	private ISearchPageContainer getContainer() {
		return fContainer;
	}

	private IEditorPart getActiveEditor() {
		IWorkbenchPage activePage= JavaPlugin.getActivePage();
		if (activePage != null) {
			return activePage.getActiveEditor();
		}
		return null;
	}

	//--------------- Configuration handling --------------

	/**
	 * Returns the page settings for this Java search page.
	 *
	 * @return the page settings to be used
	 */
	private IDialogSettings getDialogSettings() {
		if (fDialogSettings == null) {
			fDialogSettings= JavaPlugin.getDefault().getDialogSettingsSection(PAGE_NAME);
		}
		return fDialogSettings;
	}

	/**
	 * Initializes itself from the stored page settings.
	 */
	private void readConfiguration() {
		IDialogSettings s= getDialogSettings();
		fIsCaseSensitive= s.getBoolean(STORE_CASE_SENSITIVE);

		try {
			int historySize= s.getInt(STORE_HISTORY_SIZE);
			for (int i= 0; i < historySize; i++) {
				IDialogSettings histSettings= s.getSection(STORE_HISTORY + i);
				if (histSettings != null) {
					SearchPatternData data= SearchPatternData.create(histSettings);
					if (data != null) {
						fPreviousSearchPatterns.add(data);
					}
				}
			}
		} catch (NumberFormatException e) {
			// ignore
		}
	}

	/**
	 * Stores the current configuration in the dialog store.
	 */
	private void writeConfiguration() {
		IDialogSettings s= getDialogSettings();
		s.put(STORE_CASE_SENSITIVE, fIsCaseSensitive);
		s.put(STORE_INCLUDE_MASK, getIncludeMask());

		int historySize= Math.min(fPreviousSearchPatterns.size(), HISTORY_SIZE);
		s.put(STORE_HISTORY_SIZE, historySize);
		for (int i= 0; i < historySize; i++) {
			IDialogSettings histSettings= s.addNewSection(STORE_HISTORY + i);
			SearchPatternData data= fPreviousSearchPatterns.get(i);
			data.store(histSettings);
		}
	}
}
