/*******************************************************************************
 * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
 *
 * 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
 *******************************************************************************/
package org.eclipse.egit.ui.internal.history;

import java.io.IOException;

import org.eclipse.egit.ui.Activator;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;

/**
 * This class executes the search function for the find toolbar. Only one thread
 * is executed at a time.
 * <p>
 * This class maintains a <code>globalThreadIx</code> internal variable that
 * is incremented for each new thread started and the current running thread
 * constantly checks this variable. If the current thread has the same value as
 * <code>globalThreadIx</code> it continues executing, if it has a lower value
 * it means that a more recent search needs to be done and the current isn't
 * necessary any more, so the current thread returns.
 * </p>
 * <p>
 * To avoid consuming all the memory in the system, this class limits the
 * maximum results it stores.
 * </p>
 *
 * @see FindToolbar
 * @see FindResults
 */
public class FindToolbarThread extends Thread {

	private static final Object EXEC_LOCK = new Object();

	private static final int MAX_RESULTS = 20000;

	String pattern;

	SWTCommit[] fileRevisions;

	FindToolbar toolbar;

	boolean ignoreCase;

	boolean findInCommitId;

	boolean findInComments;

	boolean findInAuthor;

	boolean findInCommitter;

	boolean findInReference;

	private volatile static int globalThreadIx = 0;

	private int currentThreadIx;

	/**
	 * Creates a new object and increments the internal
	 * <code>globalThreadIx</code> variable causing any earlier running thread
	 * to return.
	 */
	public FindToolbarThread() {
		super("history_find_thread" + ++globalThreadIx); //$NON-NLS-1$
		currentThreadIx = globalThreadIx;
	}

	public void run() {
		synchronized (EXEC_LOCK) {
			execFind();
		}
	}

	private void execFind() {
		// If it isn't the last event, just ignore it.
		if (currentThreadIx < globalThreadIx) {
			return;
		}

		FindResults findResults = toolbar.findResults;
		findResults.clear();

		boolean maxResultsOverflow = false;
		if (pattern.length() > 0 && fileRevisions != null) {
			String findPattern = pattern;
			if (ignoreCase) {
				findPattern = pattern.toLowerCase();
			}

			long lastUIUpdate = System.currentTimeMillis();

			int totalRevisions = fileRevisions.length;
			int totalMatches = 0;
			boolean notFound = true;
			for (int i = 0; i < totalRevisions; i++) {
				// If a new find event was generated, ends the current thread.
				if (toolbar.getDisplay().isDisposed()
						|| currentThreadIx < globalThreadIx) {
					return;
				}

				// Updates the toolbar with in process info.
				if (System.currentTimeMillis() - lastUIUpdate > 500) {
					final int percentage = (int) (((i + 1F) / totalRevisions) * 100);
					toolbar.getDisplay().asyncExec(new Runnable() {
						public void run() {
							if (toolbar.isDisposed()) {
								return;
							}
							toolbar.progressUpdate(percentage);
						}
					});
					lastUIUpdate = System.currentTimeMillis();
				}

				// Finds for the pattern in the revision history.
				notFound = true;
				SWTCommit revision = fileRevisions[i];
				try {
					revision.parseBody();
				} catch (IOException e) {
					Activator.error("Error parsing body", e); //$NON-NLS-1$
					continue;
				}

				if (findInCommitId) {
					String contentId = revision.getId().name();
					if (contentId != null) {
						if (ignoreCase) {
							contentId = contentId.toLowerCase();
						}
						if (contentId.indexOf(findPattern) != -1) {
							totalMatches++;
							findResults.add(i, revision);
							notFound = false;
						}
					}
				}

				if (findInComments && notFound) {
					String comment = revision.getFullMessage();
					if (comment != null) {
						if (ignoreCase) {
							comment = comment.toLowerCase();
						}
						if (comment.indexOf(findPattern) != -1) {
							totalMatches++;
							findResults.add(i, revision);
							notFound = false;
						}
					}
				}

				if (findInAuthor && notFound) {
					String author = revision.getAuthorIdent().getName();
					if (author != null) {
						if (ignoreCase) {
							author = author.toLowerCase();
						}
						if (author.indexOf(findPattern) != -1) {
							totalMatches++;
							findResults.add(i, revision);
							notFound = false;
						}
					}
					if (notFound) {
						String email = revision.getAuthorIdent()
								.getEmailAddress();
						if (email != null) {
							if (ignoreCase) {
								email = email.toLowerCase();
							}
							if (email.indexOf(findPattern) != -1) {
								totalMatches++;
								findResults.add(i, revision);
								notFound = false;
							}
						}
					}
				}

				if (findInCommitter && notFound) {
					String committer = revision.getCommitterIdent().getName();
					if (committer != null) {
						if (ignoreCase) {
							committer = committer.toLowerCase();
						}
						if (committer.indexOf(findPattern) != -1) {
							totalMatches++;
							findResults.add(i, revision);
							notFound = false;
						}
					}
					if (notFound) {
						String email = revision.getCommitterIdent()
								.getEmailAddress();
						if (email != null) {
							if (ignoreCase) {
								email = email.toLowerCase();
							}
							if (email.indexOf(findPattern) != -1) {
								totalMatches++;
								findResults.add(i, revision);
								notFound = false;
							}
						}
					}
				}

				if (findInReference && notFound) {
					if (revision.getRefCount() > 0) {
						for (int j = 0; j < revision.getRefCount(); j++) {
							Ref ref = revision.getRef(j);
							String refName = ref.getName();
							refName = Repository.shortenRefName(refName);
							if (ignoreCase)
								refName = refName.toLowerCase();
							if (refName.indexOf(findPattern) != -1) {
								totalMatches++;
								findResults.add(i, revision);
								notFound = false;
							}
						}
					}
				}

				if (totalMatches == MAX_RESULTS) {
					maxResultsOverflow = true;
					break;
				}
			}

		}

		// Updates the toolbar with the result find info.
		final boolean overflow = maxResultsOverflow;
		toolbar.getDisplay().syncExec(new Runnable() {
			public void run() {
				if (toolbar.isDisposed()) {
					return;
				}
				toolbar.findCompletionUpdate(pattern, overflow);
			}
		});
	}

	static void updateGlobalThreadIx() {
		++globalThreadIx;
	}
}
