/*******************************************************************************
 * Copyright (c) 2008, 2011 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.viewsupport;

import java.util.ArrayList;
import java.util.StringTokenizer;

import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TreeItem;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;

import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.progress.WorkbenchJob;

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

import org.eclipse.jdt.internal.ui.util.SWTUtil;
import org.eclipse.jdt.internal.ui.util.StringMatcher;


/**
 * An element tree selection dialog with a filter box on top.
 */
public class FilteredElementTreeSelectionDialog extends ElementTreeSelectionDialog {

	private static class MultiplePatternFilter extends PatternFilter {

		private StringMatcher[] fMatchers;
		private final boolean fIsDeepFiltering;


		public MultiplePatternFilter(boolean deepFiltering) {
			fIsDeepFiltering= deepFiltering;
		}

		@Override
		public void setPattern(String patternString) {
			super.setPattern(patternString);
			fMatchers= null;
			if (patternString != null && patternString.length() > 0) {
				ArrayList<StringMatcher> res= new ArrayList<>();
				StringTokenizer tok= new StringTokenizer(patternString, ",;"); //$NON-NLS-1$
				int tokenCount= tok.countTokens();
				for (int i= 0; i < tokenCount; i++) {
					String token= tok.nextToken();
					if (token.length() > 0) {
						res.add(new StringMatcher(token + '*', true, false));
					}
				}
				if (!res.isEmpty()) {
					fMatchers= res.toArray(new StringMatcher[res.size()]);
				}
			}
		}

		@Override
		protected boolean wordMatches(String text) {
			if (text != null) {
				if (fMatchers == null || fMatchers.length == 0) {
					return true;
				}
				for (StringMatcher matcher : fMatchers) {
					if (matcher.match(text)) {
						return true;
					}
				}
			}
			return false;
		}

		/*
		 * @see org.eclipse.ui.dialogs.PatternFilter#isElementVisible(org.eclipse.jface.viewers.Viewer, java.lang.Object)
		 * @since 3.5
		 */
		@Override
		public boolean isElementVisible(Viewer viewer, Object element) {
			boolean hasChildren= ((ITreeContentProvider) ((AbstractTreeViewer) viewer).getContentProvider()).hasChildren(element);
			if (fIsDeepFiltering) {
				if (!super.isElementVisible(viewer, element))
					return false;

				// Also apply deep filtering to the other registered filters
				for (ViewerFilter filter : ((TreeViewer)viewer).getFilters()) {
					if (filter == this) {
						continue;
					}
					if (!filter.select(viewer, element, element)) {
						return false;
					}
				}
				return true;
			}
			return hasChildren || isLeafMatch(viewer, element);
		}
	}

	private static class FilteredTreeWithFilter extends FilteredTree {
		private boolean narrowingDown;
		private String previousFilterText;

		public FilteredTreeWithFilter(Composite parent, int treeStyle, String initialFilter, boolean deepFiltering) {
			super(parent, treeStyle, new MultiplePatternFilter(deepFiltering), true);
			if (initialFilter != null) {
				setFilterText(initialFilter);
				textChanged();
			}

		}

		@Override
		protected void textChanged() {
			narrowingDown= previousFilterText == null || getFilterString().startsWith(previousFilterText);
			previousFilterText= getFilterString();
			super.textChanged();
		}

		// This is a copy of the super method, but without auto-expansion.
		@Override
		protected WorkbenchJob doCreateRefreshJob() {
			return new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$


				@Override
				public IStatus runInUIThread(IProgressMonitor monitor) {
					if (treeViewer.getControl().isDisposed()) {
						return Status.CANCEL_STATUS;
					}

					String text= getFilterString();
					if (text == null) {
						return Status.OK_STATUS;
					}

					boolean initial= initialText != null && initialText.equals(text);
					if (initial) {
						getPatternFilter().setPattern(null);
					} else {
						getPatternFilter().setPattern(text);
					}

					Control redrawFalseControl= treeComposite != null ? treeComposite : treeViewer.getControl();
					try {
						// don't want the user to see updates that will be made to
						// the tree
						// we are setting redraw(false) on the composite to avoid
						// dancing scrollbar
						redrawFalseControl.setRedraw(false);
						if (!narrowingDown) {
							// collapse all
							for (TreeItem item : treeViewer.getTree().getItems()) {
								if (item.getExpanded()) {
									treeViewer.setExpandedState(item.getData(), false);
								}
							}
						}
						treeViewer.refresh(true);

						updateToolbar(text.length() > 0 && !initial);

					} finally {
						// done updating the tree - set redraw back to true
						TreeItem[] items= getViewer().getTree().getItems();
						if (items.length > 0 && getViewer().getTree().getSelectionCount() == 0) {
							treeViewer.getTree().setTopItem(items[0]);
						}
						redrawFalseControl.setRedraw(true);
					}
					return Status.OK_STATUS;
				}
			};
		}

	}

	private String fInitialFilter;
	private boolean fIsDeepFiltering;

	public FilteredElementTreeSelectionDialog(Shell parent, ILabelProvider labelProvider, ITreeContentProvider contentProvider) {
		this(parent, labelProvider, contentProvider, true);
	}

	public FilteredElementTreeSelectionDialog(Shell parent, ILabelProvider labelProvider, ITreeContentProvider contentProvider, boolean isDeepFiltering) {
		super(parent, labelProvider, contentProvider);
		fInitialFilter= null;
		fIsDeepFiltering= isDeepFiltering;
	}

	/**
	 * A comma separate list of patterns that are filled in initial filter list.
	 * Example is: '*.jar, *.zip'
	 *
	 * @param initialFilter the initial filter or <code>null</code>.
	 */
	public void setInitialFilter(String initialFilter) {
		fInitialFilter= initialFilter;
	}

	@Override
	protected TreeViewer doCreateTreeViewer(Composite parent, int style) {
		FilteredTree tree= new FilteredTreeWithFilter(parent, style, fInitialFilter, fIsDeepFiltering);
		tree.setLayoutData(new GridData(GridData.FILL_BOTH));

		applyDialogFont(tree);

		TreeViewer viewer= tree.getViewer();
		SWTUtil.setAccessibilityText(viewer.getControl(), Strings.removeMnemonicIndicator(getMessage()));
		return viewer;
	}

}
