content: /*
   * Copyright (c) 2009 Andrejs Jermakovics.
   * 
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the Eclipse Public License v1.0
   * which accompanies this distribution, and is available at
   * http://www.eclipse.org/legal/epl-v10.html
   *
   * Contributors:
   *     Andrejs Jermakovics - initial implementation
   */
  package it.unibz.instasearch.ui;
  
  import it.unibz.instasearch.InstaSearchPlugin;
  import it.unibz.instasearch.indexing.SearchResultDoc;
  import it.unibz.instasearch.ui.ResultContentProvider.MatchLine;
  
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.resources.IMarker;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.IStorage;
  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.IJobChangeEvent;
  import org.eclipse.core.runtime.jobs.ISchedulingRule;
  import org.eclipse.core.runtime.jobs.Job;
  import org.eclipse.core.runtime.jobs.JobChangeAdapter;
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.search.ui.NewSearchUI;
  import org.eclipse.search.ui.text.Match;
  import org.eclipse.ui.IEditorDescriptor;
  import org.eclipse.ui.IEditorInput;
  import org.eclipse.ui.IEditorPart;
  import org.eclipse.ui.IPartListener;
  import org.eclipse.ui.IWorkbenchPage;
  import org.eclipse.ui.IWorkbenchPart;
  import org.eclipse.ui.ide.IDE;
  import org.eclipse.ui.part.MultiPageEditorPart;
  import org.eclipse.ui.texteditor.ITextEditor;
  import org.eclipse.ui.texteditor.MarkerUtilities;
  
  class MatchHighlightJob extends Job implements ISchedulingRule, IPartListener 
  {
  	private SearchResultDoc doc;
  	private MatchLine selectedLineMatches;
  	private ResultContentProvider contentProvider;
  	private ITextEditor editor;
  	private Job searchJob;
  	private IDocument document;
  	private IWorkbenchPage workbenchPage;
  	private IFile file;
  
  	/**
  	 * @param doc
  	 * @param selectedLineMatches
  	 * @param contentProvider 
  	 * @param workbenchPage 
  	 * @throws Exception 
  	 */
  	public MatchHighlightJob(SearchResultDoc doc, MatchLine selectedLineMatches, ResultContentProvider contentProvider, Job searchJob, IWorkbenchPage workbenchPage) throws Exception 
  	{
  		super("Highlight Matches");
  		
  		this.doc = doc;
  		this.selectedLineMatches = selectedLineMatches;
  		this.contentProvider = contentProvider;
  		this.searchJob = searchJob;
  		this.workbenchPage = workbenchPage;
  		
  		IEditorInput input = contentProvider.getEditorInput(doc);
  		IEditorDescriptor editorDesc = IDE.getEditorDescriptor(doc.getFileName());
  		
  		IEditorPart editorPart = IDE.openEditor(workbenchPage, input, editorDesc.getId());
  		this.editor = getTextEditor(input, editorPart);
  		this.document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
  		
  		IStorage storage = contentProvider.getStorage(doc);
  		this.file = null;
  		if( storage instanceof IFile )
  			file = (IFile) storage;
  	}
  	
  	@Override
  	protected IStatus run(IProgressMonitor monitor) 
  	{
  		try {
  			if( editor != null )
  			{
  				workbenchPage.addPartListener(this);
  				
  				findAndHighlightMatches(doc, monitor);
  			}
  		} catch (Exception e) {
  			InstaSearchPlugin.log(e);
  		} finally {
  			workbenchPage.removePartListener(this);
  		}
  
  		return Status.OK_STATUS;
  	}
  	
  	private void findAndHighlightMatches(SearchResultDoc doc, final IProgressMonitor monitor) 
  		throws Exception, BadLocationException, CoreException 
  	{
  		if( file != null ) 
  		{
  			deleteMarkers();
  			addMarkerRemover(file);
  		}
  		
  		MatchHighliter highlighter = new MatchHighliter(monitor);
  		
  		if( selectedLineMatches != null )
  			highlighter.highlightMatchLine(file, selectedLineMatches);
  		
  		contentProvider.getMatchLines(doc, false, highlighter);
  		
  		//highlightMatches(file, selectedLineMatches, lineMatchesList, monitor);
  	}
  	
  	private class MatchHighliter implements ResultContentProvider.MatchFindCallback
  	{
  		private IProgressMonitor monitor;
  		private boolean matchSelected = false;
  
  		/**
  		 * @param monitor
  		 */
  		public MatchHighliter(IProgressMonitor monitor)
  		{
  			this.monitor = monitor;
  		}
  
  		public void matchFound(MatchLine line)
  		{
  			try
  			{
  				highlightMatchLine(file, line);
  			}
  			catch (Exception e)
  			{
  				InstaSearchPlugin.log(e);
  				monitor.setCanceled(true);
  			}
  		}
  
  		public boolean isCanceled()
  		{
  			return monitor.isCanceled();
  		}
  		
  		/**
  		 * @param file
  		 * @throws CoreException 
  		 * @throws BadLocationException 
  		 */
  		protected void highlightMatchLine(IFile file, MatchLine lineMatches) throws CoreException, BadLocationException
  		{
  			int lineNum = lineMatches.getLineNumber();
  		
  			if( document.getNumberOfLines() < lineNum ) 
  				return;
  		
  			if( selectedLineMatches == null ) selectedLineMatches = lineMatches; // pick first
  			
  			int pos = document.getLineOffset(lineNum-1);
  		
  			for(Match match: lineMatches.getMatches()) 
  			{
  				if( monitor.isCanceled() ) break;
  				
  				editor.setHighlightRange(pos+match.getOffset(), match.getLength(), false);
  				
  				IMarker marker = createMarker(pos, lineNum, match, file, editor);
  				
  				if(selectedLineMatches != null && lineNum == selectedLineMatches.getLineNumber()) 
  				{
  					if( marker != null && ! matchSelected )
  					{
  						gotoEditorMarker(marker);
  						matchSelected = true;
  					}
  					
  					if( !matchSelected ) {
  						int selectedOffset = pos+match.getOffset();
  						int selectedLength = match.getLength();
  						editor.setHighlightRange(selectedOffset, selectedLength, true);
  						matchSelected = true;
  					}
  				}
  			}
  		}
  
  		private IMarker createMarker(int pos, int lineNumber, Match match, IFile file, ITextEditor textEditor) throws CoreException {
  			
  			if( file == null )
  				return null;
  			
  			IMarker marker = null;	
  			
  			marker = file.createMarker(NewSearchUI.SEARCH_MARKER);
  			marker.setAttribute(IMarker.TRANSIENT, true);
  			marker.setAttribute(IMarker.MESSAGE, match.getElement());
  			
  			MarkerUtilities.setLineNumber(marker, lineNumber);
  			MarkerUtilities.setCharStart(marker, pos + match.getOffset());
  			MarkerUtilities.setCharEnd(marker, pos + match.getOffset() + match.getLength());
  			
  			return marker;
  		}
  
  		private void gotoEditorMarker(final IMarker selectedMarker) 
  		{
  			if( selectedMarker == null )
  				return;
  			
  			Runnable runnable = new Runnable() 
  			{
  				public void run() 
  				{
  					IDE.gotoMarker(editor, selectedMarker);
  				}
  			};
  			
  			editor.getSite().getShell().getDisplay().asyncExec(runnable);	
  		}
  
  	}
  	
  	private ITextEditor getTextEditor(IEditorInput input, IEditorPart editor) 
  	{
  		if( editor instanceof MultiPageEditorPart ) 
  		{
  			MultiPageEditorPart multiPageEditor = (MultiPageEditorPart) editor;
  			IEditorPart[] editors = multiPageEditor.findEditors(input);
  			
  			for(IEditorPart ed: editors) 
  			{
  				if( ed instanceof ITextEditor ) 
  				{
  					multiPageEditor.setActiveEditor(ed);
  					return (ITextEditor) ed;
  				}
  			}
  		}
  		else if( editor instanceof ITextEditor )
  		{
  			return (ITextEditor) editor;
  		}
  		
  		return null;
  	}
  
  	/**
  	 * Remove markers when new search is made
  	 * @param file
  	 */
  	private void addMarkerRemover(final IFile file) 
  	{
  		 searchJob.addJobChangeListener(new JobChangeAdapter(){
  			public void done(IJobChangeEvent event) {
  				try {
  					cancel();
  					deleteMarkers();
  				} finally {
  					searchJob.removeJobChangeListener(this);
  				}
  			}
  		 });
  	}
  	
  	public boolean contains(ISchedulingRule rule) {
  		return rule.getClass() == this.getClass();
  	}
  
  	public boolean isConflicting(ISchedulingRule rule) {
  		return rule.getClass() == this.getClass();
  	}
  
  
  	public void partClosed(IWorkbenchPart part)
  	{
  		if( part == editor ) // if closing editor
  		{
  			this.cancel(); // cancel highlight job
  			deleteMarkers();
  		}
  	}
  
  	private void deleteMarkers()
  	{
  		try
  		{
  			if( file != null )
  				file.deleteMarkers(NewSearchUI.SEARCH_MARKER, true, IResource.DEPTH_INFINITE);
  		}
  		catch (CoreException e)
  		{
  			InstaSearchPlugin.log(e);
  		}
  	}
  
  	public void partDeactivated(IWorkbenchPart part)
  	{
  	}
  
  	public void partOpened(IWorkbenchPart part)
  	{
  	}
  	
  	public void partActivated(IWorkbenchPart part)
  	{
  	}
  
  	public void partBroughtToTop(IWorkbenchPart part)
  	{
  	}
  
  }