/*******************************************************************************
 * Copyright (C) 2014, Maik Schreiber <blizzy@blizzy.de>
 *
 * 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:
 *    Maik Schreiber - initial implementation
 *******************************************************************************/
package org.eclipse.egit.core;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.StopWalkException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;

/**
 * Utility class for working with commits.
 */
public class CommitUtil {

	private CommitUtil() {
		// non instanciable utility class
	}

	/**
	 * Sorts commits in parent-first order.
	 *
	 * @param commits
	 *            the commits to sort
	 * @return a new list containing the sorted commits
	 */
	public static List<RevCommit> sortCommits(Collection<RevCommit> commits) {
		Map<RevCommit, RevCommit> parentToChild = new HashMap<RevCommit, RevCommit>();
		RevCommit firstCommit = null;
		for (RevCommit commit : commits) {
			RevCommit parentCommit = commit.getParent(0);
			parentToChild.put(parentCommit, commit);
			if (!commits.contains(parentCommit))
				firstCommit = commit;
		}

		List<RevCommit> sortedCommits = new ArrayList<RevCommit>();
		sortedCommits.add(firstCommit);
		RevCommit parentCommit = firstCommit;
		for (;;) {
			RevCommit childCommit = parentToChild.get(parentCommit);
			if (childCommit == null)
				break;
			sortedCommits.add(childCommit);
			parentCommit = childCommit;
		}

		return sortedCommits;
	}

	/**
	 * Returns whether a commit is on the current branch, ie. if it is reachable
	 * from the current HEAD.
	 *
	 * @param commit
	 *            the commit to check
	 * @param repository
	 *            the repository
	 * @return true if the commit is reachable from HEAD
	 * @throws IOException
	 *             if there is an I/O error
	 */
	public static boolean isCommitInCurrentBranch(RevCommit commit,
			Repository repository) throws IOException {
		return areCommitsInCurrentBranch(Collections.singleton(commit),
				repository);
	}

	/**
	 * Returns whether the commits are on the current branch, ie. if they are
	 * reachable from the current HEAD.
	 *
	 * @param commits
	 *            the commits to check
	 * @param repository
	 *            the repository
	 * @return true if the commits are reachable from HEAD
	 * @throws IOException
	 *             if there is an I/O error
	 */
	public static boolean areCommitsInCurrentBranch(
			Collection<RevCommit> commits, Repository repository)
			throws IOException {
		RevWalk walk = new RevWalk(repository);
		ObjectId headCommitId = repository.resolve(Constants.HEAD);
		RevCommit headCommit = walk.parseCommit(headCommitId);

		for (final RevCommit commit : commits) {
			walk.reset();
			walk.markStart(headCommit);

			RevFilter revFilter = new RevFilter() {
				@Override
				public boolean include(RevWalk walker, RevCommit cmit)
						throws StopWalkException, MissingObjectException,
						IncorrectObjectTypeException, IOException {

					return cmit.equals(commit);
				}

				@Override
				public RevFilter clone() {
					return null;
				}
			};
			walk.setRevFilter(revFilter);

			if (walk.next() == null)
				return false;
		}
		return true;
	}
}
