/*******************************************************************************
 * Copyright (c) 2000, 2022 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 org.eclipse.swt.widgets.Composite;

import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.commands.ActionHandler;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;

import org.eclipse.jface.text.ITextSelection;

import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.part.IPageSite;

import org.eclipse.ui.texteditor.ITextEditor;

import org.eclipse.search.ui.NewSearchUI;
import org.eclipse.search.ui.text.AbstractTextSearchResult;
import org.eclipse.search.ui.text.AbstractTextSearchViewPage;
import org.eclipse.search.ui.text.Match;

import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;

import org.eclipse.jdt.internal.core.manipulation.search.ExceptionOccurrencesFinder;
import org.eclipse.jdt.internal.core.manipulation.search.IOccurrencesFinder;
import org.eclipse.jdt.internal.core.manipulation.search.ImplementOccurrencesFinder;
import org.eclipse.jdt.internal.core.manipulation.search.OccurrencesFinder;

import org.eclipse.jdt.ui.JavaUI;

import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.actions.AbstractToggleLinkingAction;
import org.eclipse.jdt.internal.ui.viewsupport.ColoringLabelProvider;
import org.eclipse.jdt.internal.ui.viewsupport.ISelectionListenerWithAST;
import org.eclipse.jdt.internal.ui.viewsupport.SelectionListenerWithASTManager;


public class OccurrencesSearchResultPage extends AbstractTextSearchViewPage {

	private static final String DIALOGSTORE_LINKEDITORS= "TypeHierarchyViewPart.linkeditors"; //$NON-NLS-1$

	private TextSearchTableContentProvider fContentProvider;

	private boolean fLinkingEnabled;
	private AbstractToggleLinkingAction fToggleLinkingAction;
	private LinkWithEditorListener fLinkWithEditorListener;

	private class LinkWithEditorListener implements IPartListener2, ISelectionListenerWithAST {

		private ITextEditor fActiveEditor;
		private boolean fIsVisible;

		public void install(IWorkbenchPage page) {
			page.addPartListener(this);
			fIsVisible= page.isPartVisible(getViewPart());
			if (fIsVisible) {
				installOnActiveEditor(page);
			}
		}

		private void installOnActiveEditor(IWorkbenchPage page) {
			IEditorPart activeEditor= page.getActiveEditor();
			if (activeEditor instanceof ITextEditor) {
				editorActive(activeEditor);
				ISelection selection= activeEditor.getSite().getSelectionProvider().getSelection();
				ITypeRoot typeRoot= JavaUI.getEditorInputTypeRoot(activeEditor.getEditorInput());
				if (typeRoot != null && selection instanceof ITextSelection) {
					CompilationUnit astRoot= SharedASTProviderCore.getAST(typeRoot, SharedASTProviderCore.WAIT_ACTIVE_ONLY, null);
					if (astRoot != null) {
						preformEditorSelectionChanged((ITextSelection) selection, astRoot);
					}
				}
			}
		}

		private void uninstallOnActiveEditor() {
			if (fActiveEditor != null) {
				SelectionListenerWithASTManager.getDefault().removeListener(fActiveEditor, this);
				fActiveEditor= null;
			}
		}

		public void uninstall(IWorkbenchPage page) {
			page.removePartListener(this);
			uninstallOnActiveEditor();
		}

		@Override
		public void partActivated(IWorkbenchPartReference partRef) {
			if (fIsVisible && partRef instanceof IEditorReference) {
				editorActive(((IEditorReference) partRef).getEditor(true));
			}
		}

		private void editorActive(IEditorPart editor) {
			if (editor instanceof ITextEditor) {
				if (editor != fActiveEditor) {
					setInput(null, null);
				}
				fActiveEditor= (ITextEditor) editor;
				SelectionListenerWithASTManager.getDefault().addListener(fActiveEditor, this);
			}
		}

		@Override
		public void partDeactivated(IWorkbenchPartReference partRef) {
		}

		@Override
		public void selectionChanged(IEditorPart part, ITextSelection selection, CompilationUnit astRoot) {
			preformEditorSelectionChanged(selection, astRoot);
		}

		@Override
		public void partVisible(IWorkbenchPartReference partRef) {
			if (NewSearchUI.SEARCH_VIEW_ID.equals(partRef.getId()) && partRef.getPart(true) == getViewPart()) {
				if (fActiveEditor == null) {
					fIsVisible= true;
				}
			}
		}

		@Override
		public void partHidden(IWorkbenchPartReference partRef) {
			if (NewSearchUI.SEARCH_VIEW_ID.equals(partRef.getId()) && partRef.getPart(true) == getViewPart()) {
				fIsVisible= false;
				uninstallOnActiveEditor();
			} else if (partRef instanceof IEditorReference && partRef.getPart(true) == fActiveEditor) {
				uninstallOnActiveEditor();
			}
		}

		@Override
		public void partBroughtToTop(IWorkbenchPartReference partRef) {
		}

		@Override
		public void partClosed(IWorkbenchPartReference partRef) {
			if (partRef instanceof IEditorReference && partRef.getPart(true) == fActiveEditor) {
				uninstallOnActiveEditor();
			}
		}

		@Override
		public void partInputChanged(IWorkbenchPartReference partRef) {
		}

		@Override
		public void partOpened(IWorkbenchPartReference partRef) {
		}
	}

	private class ToggleLinkingAction extends AbstractToggleLinkingAction {
		@Override
		public void run() {
			setLinkingEnabled(isChecked());
		}
	}

	public OccurrencesSearchResultPage() {
		super(AbstractTextSearchViewPage.FLAG_LAYOUT_FLAT);
		fLinkWithEditorListener= new LinkWithEditorListener();
		fLinkingEnabled= false;
		fToggleLinkingAction= new ToggleLinkingAction();
		fToggleLinkingAction.setActionDefinitionId(IWorkbenchCommandConstants.NAVIGATE_TOGGLE_LINK_WITH_EDITOR);
	}

	@Override
	public void init(IPageSite pageSite) {
		super.init(pageSite);
		// wait until site is set
		setLinkingEnabled(getDialogSettings().getBoolean(DIALOGSTORE_LINKEDITORS));
	}

	@Override
	public void dispose() {
		if (fLinkingEnabled) {
			fLinkWithEditorListener.uninstall(getSite().getPage());
		}
		super.dispose();
	}

	@Override
	protected void fillToolbar(IToolBarManager tbm) {
		super.fillToolbar(tbm);
		tbm.add(fToggleLinkingAction);
	}

	@Override
	public void createControl(Composite parent) {
		super.createControl(parent);

		IActionBars bars= getSite().getActionBars();
		IMenuManager menu= bars.getMenuManager();
		menu.add(fToggleLinkingAction);

		IHandlerService handlerService= getSite().getService(IHandlerService.class);
		handlerService.activateHandler(IWorkbenchCommandConstants.NAVIGATE_TOGGLE_LINK_WITH_EDITOR, new ActionHandler(fToggleLinkingAction));
	}

	/*
	 * @see org.eclipse.search.ui.text.AbstractTextSearchViewPage#showMatch(org.eclipse.search.ui.text.Match, int, int)
	 */
	@Override
	protected void showMatch(Match match, int currentOffset, int currentLength, boolean activate) throws PartInitException {
		JavaElementLine element= (JavaElementLine) match.getElement();
		IJavaElement javaElement= element.getJavaElement();
		try {
			IEditorPart editor= JavaUI.openInEditor(javaElement, false, false);
			if (editor instanceof ITextEditor) {
				ITextEditor textEditor= (ITextEditor) editor;
				textEditor.selectAndReveal(currentOffset, currentLength);
			}
			// activating at the end avoids an outdated selection event from JavaUI.openInEditor(..):
			if (editor != null && activate)
				editor.getEditorSite().getPage().activate(editor);
		} catch (PartInitException | JavaModelException e1) {
			return;
		}

	}

	/*
	 * @see org.eclipse.search.ui.text.AbstractTextSearchViewPage#elementsChanged(java.lang.Object[])
	 */
	@Override
	protected void elementsChanged(Object[] objects) {
		if (fContentProvider != null)
			fContentProvider.elementsChanged(objects);
	}

	/*
	 * @see org.eclipse.search.ui.text.AbstractTextSearchViewPage#clear()
	 */
	@Override
	protected void clear() {
		if (fContentProvider != null)
			fContentProvider.clear();
	}

	/*
	 * @see org.eclipse.search.ui.text.AbstractTextSearchViewPage#configureTreeViewer(org.eclipse.jface.viewers.TreeViewer)
	 */
	@Override
	protected void configureTreeViewer(TreeViewer viewer) {
		throw new IllegalStateException("Doesn't support tree mode."); //$NON-NLS-1$
	}

	/*
	 * @see org.eclipse.search.ui.text.AbstractTextSearchViewPage#configureTableViewer(org.eclipse.jface.viewers.TableViewer)
	 */
	@Override
	protected void configureTableViewer(TableViewer viewer) {
		viewer.setComparator(new ViewerComparator() {
			@Override
			public int compare(Viewer v, Object e1, Object e2) {
				JavaElementLine jel1= (JavaElementLine) e1;
				JavaElementLine jel2= (JavaElementLine) e2;
				return jel1.getLineNumber() - jel2.getLineNumber();
			}
		});
		viewer.setLabelProvider(new ColoringLabelProvider(new OccurrencesSearchLabelProvider(this)));
		fContentProvider= new TextSearchTableContentProvider();
		viewer.setContentProvider(fContentProvider);
	}

	public boolean isLinkingEnabled() {
		return fLinkingEnabled;
	}

	public void setLinkingEnabled(boolean enabled) {
		if (fLinkingEnabled != enabled) {
			fLinkingEnabled= enabled;
			fToggleLinkingAction.setChecked(enabled);
			getDialogSettings().put(DIALOGSTORE_LINKEDITORS, enabled);

			if (enabled) {
				fLinkWithEditorListener.install(getSite().getPage());
			} else {
				fLinkWithEditorListener.uninstall(getSite().getPage());
			}
		}
	}

	private IDialogSettings getDialogSettings() {
		return JavaPlugin.getDefault().getDialogSettingsSection("OccurrencesSearchResultPage"); //$NON-NLS-1$
	}


	private void preformEditorSelectionChanged(ITextSelection selection, CompilationUnit astRoot) {
		if (!isLinkingEnabled()) {
			return;
		}
		IOccurrencesFinder finder;

		AbstractTextSearchResult input= getInput();
		if (input == null) {
			finder= new OccurrencesFinder();
		} else {
			String id= ((OccurrencesSearchQuery) input.getQuery()).getFinderId();
			if (id == OccurrencesFinder.ID) {
				finder= new OccurrencesFinder();
			} else if (ExceptionOccurrencesFinder.ID.equals(id)) {
				finder= new ExceptionOccurrencesFinder();
			} else {
				finder= new ImplementOccurrencesFinder();
			}
		}

		int offset= selection.getOffset();
		int length= selection.getLength();
		if (finder.initialize(astRoot, offset, length) == null) {
			final OccurrencesSearchQuery query= new OccurrencesSearchQuery(finder, astRoot.getTypeRoot());
			query.run(null);
			OccurrencesSearchResult result= (OccurrencesSearchResult) query.getSearchResult();
			final JavaElementLine line= getMatchingLine(result, offset, length);

			getSite().getShell().getDisplay().asyncExec(() -> setInput(query.getSearchResult(), line == null ? null : new StructuredSelection(line)));
		}
	}

	private static JavaElementLine getMatchingLine(OccurrencesSearchResult result, int offset, int length) {
		for (Object element : result.getElements()) {
			JavaElementLine line = (JavaElementLine) element;
			for (Match matche : result.getMatches(line)) {
				OccurrenceMatch match= (OccurrenceMatch) matche;
				if (match.getOriginalOffset() <= offset && offset + length <= match.getOriginalOffset() + match.getOriginalLength()) {
					return line;
				}
			}
		}
		return null;
	}
}
