/*
 * HyperSearchRequest.java - HyperSearch request, run in I/O thread
 * :tabSize=4:indentSize=4:noTabs=false:
 * :folding=explicit:collapseFolds=1:
 *
 * Copyright (C) 1998, 1999, 2000, 2001, 2002 Slava Pestov
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package org.gjt.sp.jedit.search;

//{{{ Imports
import javax.swing.tree.*;
import javax.swing.*;

import org.gjt.sp.jedit.textarea.Selection;
import org.gjt.sp.jedit.textarea.JEditTextArea;
import org.gjt.sp.jedit.Buffer;
import org.gjt.sp.jedit.GUIUtilities;
import org.gjt.sp.jedit.jEdit;
import org.gjt.sp.jedit.View;
import org.gjt.sp.util.*;
//}}}

/**
 * HyperSearch results window.
 * @author Slava Pestov
 * @version $Id: HyperSearchRequest.java 23224 2013-09-30 20:51:42Z shlomy $
 */
class HyperSearchRequest extends Task
{
	//{{{ HyperSearchRequest constructor
	HyperSearchRequest(View view, SearchMatcher matcher,
		HyperSearchResults results, Selection[] selection)
	{
		this.view = view;
		this.matcher = matcher;

		this.results = results;
		searchString = SearchAndReplace.getSearchString();
		rootSearchNode = new DefaultMutableTreeNode(new HyperSearchOperationNode(searchString, matcher));

		this.selection = selection;
	} //}}}

	//{{{ run() method
	@Override
	public void _run()
	{
		setStatus(jEdit.getProperty("hypersearch-status"));

		SearchFileSet fileset = SearchAndReplace.getSearchFileSet();
		String[] files = fileset.getFiles(view);
		if(files == null || files.length == 0)
		{
			ThreadUtilities.runInDispatchThread(new Runnable()
			{
				public void run()
				{
					GUIUtilities.error(view,"empty-fileset",null);
					results.searchDone(rootSearchNode);
				}
			});
			return;
		}

		setMaximum(fileset.getFileCount(view));

		// to minimize synchronization and stuff like that, we only
		// show a status message at most twice a second

		// initially zero, so that we always show the first message
		String searchingCaption = jEdit.getProperty("hypersearch-results.searching",
				new String[] { SearchAndReplace.getSearchString() }) + ' ';
		try
		{
			if(selection != null)
			{
				Buffer buffer = view.getBuffer();

				searchInSelection(buffer);
			}
			else
			{
				int current = 0;

				long lastStatusTime = 0L;
				int resultCount = 0;
				boolean asked = false;
				int maxResults = jEdit.getIntegerProperty("hypersearch.maxWarningResults");
				for(int i = 0; i < files.length; i++)
				{
					if(Thread.currentThread().isInterrupted())
					{
						Log.log(Log.MESSAGE, this, "Search stopped by user action (stop button)");
						break;
					}
					if (!asked && resultCount > maxResults && maxResults != 0)
					{
						Log.log(Log.DEBUG, this, "Search in progress, " + resultCount +
									 " occurrences found, asking the user to stop");
						asked = true;
						int ret = GUIUtilities.confirm(view, "hypersearch.tooManyResults",
									       new Object[]{resultCount},
									       JOptionPane.YES_NO_OPTION,
									       JOptionPane.QUESTION_MESSAGE);
						if (ret == JOptionPane.YES_OPTION)
						{
							Log.log(Log.MESSAGE, this, "Search stopped by user action");
							break;
						}
					}
					String file = files[i];
					current++;

					long currentTime = System.currentTimeMillis();
					if(currentTime - lastStatusTime > 250L)
					{
						setValue(current);
						lastStatusTime = currentTime;
						results.setSearchStatus(searchingCaption + file);
					}

					Buffer buffer = jEdit.openTemporary(null,null,file,false);
					if(buffer != null)
					{
						// Wait for the buffer to load
						if(!buffer.isLoaded())
							TaskManager.instance.waitForIoTasks();

						resultCount += doHyperSearch(buffer, 0, buffer.getLength());
					}
				}
				Log.log(Log.MESSAGE, this, resultCount +" OCCURENCES");
			}
		}
		catch(final Exception e)
		{
			Log.log(Log.ERROR,this,e);
			ThreadUtilities.runInDispatchThread(new Runnable()
			{
				public void run()
				{
					SearchAndReplace.handleError(view,e);
				}
			});
		}
		finally
		{
			ThreadUtilities.runInDispatchThread(new Runnable()
			{
				public void run()
				{
					results.searchDone(rootSearchNode, selectNode);
				}
			});
		}
	} //}}}

	//{{{ Private members

	//{{{ Instance variables
	private final View view;
	private final SearchMatcher matcher;
	private final HyperSearchResults results;
	private final DefaultMutableTreeNode rootSearchNode;
	private final Selection[] selection;
	private final String searchString;
	private DefaultMutableTreeNode selectNode;
	//}}}

	//{{{ searchInSelection() method
	private int searchInSelection(Buffer buffer) throws Exception
	{
		setCancellable(false);

		int resultCount = 0;

		try
		{
			buffer.readLock();

			for (Selection s : selection)
			{
				if (s instanceof Selection.Rect)
				{
					for (int j = s.getStartLine(); j <= s.getEndLine(); j++)
					{
						resultCount += doHyperSearch(buffer, s.getStart(buffer, j),
									     s.getEnd(buffer, j));
					}
				}
				else
				{
					resultCount += doHyperSearch(buffer, s.getStart(), s.getEnd());
				}
			}
		}
		finally
		{
			buffer.readUnlock();
		}
		setCancellable(true);

		return resultCount;
	} //}}}

	//{{{ doHyperSearch() method
	private int doHyperSearch(Buffer buffer, int start, int end)
		throws Exception
	{
		if(matcher instanceof BoyerMooreSearchMatcher)
			setCancellable(true);
		else
			setCancellable(false);

		HyperSearchFileNode hyperSearchFileNode = new HyperSearchFileNode(buffer.getPath());
		DefaultMutableTreeNode bufferNode = new DefaultMutableTreeNode(hyperSearchFileNode);

		int resultCount = doHyperSearch(buffer,start,end,bufferNode);
		hyperSearchFileNode.setCount(resultCount);
		if(resultCount != 0)
			rootSearchNode.insert(bufferNode,rootSearchNode.getChildCount());

		setCancellable(true);

		return resultCount;
	} //}}}

	//{{{ doHyperSearch() method
	private int doHyperSearch(Buffer buffer, int start, int end,
		DefaultMutableTreeNode bufferNode)
	{
		if(matcher.wholeWord)
		{
			buffer.setMode();
			String noWordSep = buffer.getStringProperty("noWordSep");
			matcher.setNoWordSep(noWordSep);
		}
		int resultCount = 0;
		JEditTextArea textArea = jEdit.getActiveView().getTextArea();
		int caretLine = textArea.getBuffer() == buffer ? textArea.getCaretLine() : -1;
		try
		{
			buffer.readLock();

			boolean endOfLine = buffer.getLineEndOffset(
				buffer.getLineOfOffset(end)) - 1 == end;

			int offset = start;

			HyperSearchResult lastResult = null;
			for(int counter = 0; ; counter++)
			{
				boolean startOfLine = buffer.getLineStartOffset(
					buffer.getLineOfOffset(offset)) == offset;

				SearchMatcher.Match match = null;
				try {
					match = matcher.nextMatch(
						buffer.getSegment(offset, end - offset),
						startOfLine,endOfLine,counter == 0,
						false);
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
				}
				if(match == null)
					break;

				int newLine = buffer.getLineOfOffset(
					offset + match.start);
				if(lastResult == null || lastResult.line != newLine)
				{
					lastResult = new HyperSearchResult(
						buffer,newLine);
					DefaultMutableTreeNode child = new DefaultMutableTreeNode(
						lastResult, false);
					if (lastResult.line == caretLine)
						selectNode = child;
					bufferNode.add(child);
				}

				lastResult.addOccur(offset + match.start,
					offset + match.end);

				offset += match.end;
				resultCount++;
			}
		}
		finally
		{
			buffer.readUnlock();
		}

		return resultCount;
	} //}}}

	//}}}
}
