/*******************************************************************************
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
 * Copyright (C) 2012, Matthias Sohn <matthias.sohn@sap.com>
 *
 * 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 java.text.MessageFormat;

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.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.JobFamilies;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.trace.GitTraceLocation;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;

class GenerateHistoryJob extends Job {
	private static final int BATCH_SIZE = 256;

	private final GitHistoryPage page;

	private final SWTCommitList loadedCommits;

	private int itemToLoad = 1;

	private RevCommit commitToLoad;

	private RevCommit commitToShow;

	private int lastUpdateCnt;

	private boolean trace;

	private final RevWalk walk;

	private RevFlag highlightFlag;

	private int forcedRedrawsAfterListIsCompleted = 0;

	GenerateHistoryJob(final GitHistoryPage ghp, Control control, RevWalk walk,
			ResourceManager resources) {
		super(NLS.bind(UIText.HistoryPage_refreshJob, Activator.getDefault()
				.getRepositoryUtil().getRepositoryName(
						ghp.getInputInternal().getRepository())));
		page = ghp;
		this.walk = walk;
		highlightFlag = walk.newFlag("highlight"); //$NON-NLS-1$
		loadedCommits = new SWTCommitList(control, resources);
		loadedCommits.source(walk);
		trace = GitTraceLocation.HISTORYVIEW.isActive();
	}

	@Override
	protected IStatus run(final IProgressMonitor monitor) {
		IStatus status = Status.OK_STATUS;
		int maxCommits = Activator.getDefault().getPreferenceStore()
					.getInt(UIPreferences.HISTORY_MAX_NUM_COMMITS);
		boolean incomplete = false;
		boolean commitNotFound = false;
		try {
			if (trace)
				GitTraceLocation.getTrace().traceEntry(
						GitTraceLocation.HISTORYVIEW.getLocation());
			final boolean loadIncrementally = !Activator.getDefault()
					.getPreferenceStore()
					.getBoolean(UIPreferences.RESOURCEHISTORY_SHOW_FINDTOOLBAR);
			try {
				for (;;) {
					int oldsz = loadedCommits.size();
					if (trace)
						GitTraceLocation.getTrace().trace(
								GitTraceLocation.HISTORYVIEW.getLocation(),
								"Filling commit list"); //$NON-NLS-1$
					// ensure that filling (here) and reading (CommitGraphTable)
					// the commit list is thread safe
					synchronized (loadedCommits) {
						if (commitToLoad != null) {
							loadedCommits.fillTo(commitToLoad, maxCommits);
							commitToShow = commitToLoad;
							commitToLoad = null;
							boolean commitFound = false;
							for (RevCommit commit : loadedCommits) {
								if (commit.getId().equals(commitToShow.getId())) {
									commitFound = true;
									break;
								}
							}
							commitNotFound = !commitFound;
						} else {
							loadedCommits.fillTo(oldsz + BATCH_SIZE - 1);
							if (oldsz == loadedCommits.size()) {
								forcedRedrawsAfterListIsCompleted++;
								break;
							}
						}
					}
					if (monitor.isCanceled())
						return Status.CANCEL_STATUS;
					if (loadedCommits.size() > itemToLoad + (BATCH_SIZE / 2) + 1 && loadIncrementally)
						break;
					if (maxCommits > 0 && loadedCommits.size() > maxCommits) {
						incomplete = true;
						break;
					}
					if (loadedCommits.size() != 1)
						monitor.setTaskName(MessageFormat
								.format(UIText.GenerateHistoryJob_taskFoundMultipleCommits,
										Integer.valueOf(loadedCommits.size())));
					else
						monitor.setTaskName(UIText.GenerateHistoryJob_taskFoundSingleCommit);
				}
			} catch (IOException e) {
				status = new Status(IStatus.ERROR, Activator.getPluginId(),
						UIText.GenerateHistoryJob_errorComputingHistory, e);
			}
			if (trace)
				GitTraceLocation.getTrace().trace(
						GitTraceLocation.HISTORYVIEW.getLocation(),
						"Loaded " + loadedCommits.size() + " commits"); //$NON-NLS-1$ //$NON-NLS-2$
			if (commitNotFound) {
				if (forcedRedrawsAfterListIsCompleted < 1 && !loadIncrementally)
					page.setWarningTextInUIThread(this);
			}
			else
				updateUI(incomplete);
		} finally {
			monitor.done();
			if (trace)
				GitTraceLocation.getTrace().traceExit(
						GitTraceLocation.HISTORYVIEW.getLocation());
		}
		return status;
	}

	private void updateUI(boolean incomplete) {
		if (trace)
			GitTraceLocation.getTrace().traceEntry(
					GitTraceLocation.HISTORYVIEW.getLocation());
		try {
			if (!(forcedRedrawsAfterListIsCompleted == 1) && !incomplete
					&& loadedCommits.size() == lastUpdateCnt)
				return;
			if (forcedRedrawsAfterListIsCompleted == 1)
				forcedRedrawsAfterListIsCompleted++;
			final SWTCommit[] asArray = new SWTCommit[loadedCommits.size()];
			loadedCommits.toArray(asArray);
			page.showCommitList(this, loadedCommits, asArray, commitToShow, incomplete, highlightFlag);
			commitToShow = null;
			lastUpdateCnt = loadedCommits.size();
		} finally {
			if (trace)
				GitTraceLocation.getTrace().traceExit(
						GitTraceLocation.HISTORYVIEW.getLocation());
		}
	}

	void release() {
		if (getState() == Job.NONE)
			dispose();
		else
			addJobChangeListener(new JobChangeAdapter() {
				@Override
				public void done(final IJobChangeEvent event) {
					dispose();
				}
			});

	}

	private void dispose() {
		walk.release();
		Display.getDefault().asyncExec(new Runnable() {

			public void run() {
				loadedCommits.dispose();
			}
		});
	}

	@Override
	public boolean belongsTo(Object family) {
		if (JobFamilies.GENERATE_HISTORY.equals(family))
			return true;
		return super.belongsTo(family);
	}

	void setLoadHint(final int index) {
		itemToLoad = index;
	}

	void setLoadHint(final RevCommit c) {
		commitToLoad = c;
	}

	int loadMoreItemsThreshold() {
		return loadedCommits.size() - (BATCH_SIZE / 2);
	}
}
