/*******************************************************************************
 * Copyright (c) 2010, 2013 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *     Dariusz Luksza <dariusz@luksza.org>
 *     Laurent Goubet <laurent.goubet@obeo.fr> - 403363
 *******************************************************************************/
package org.eclipse.egit.core.synchronize;

import static org.eclipse.egit.core.internal.util.ResourceUtil.isNonWorkspace;
import static org.eclipse.jgit.lib.ObjectId.zeroId;
import static org.eclipse.jgit.lib.Repository.stripWorkDir;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.egit.core.internal.CoreText;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.variants.IResourceVariant;
import org.eclipse.team.core.variants.ResourceVariantByteStore;
import org.eclipse.team.core.variants.ResourceVariantTree;

abstract class GitResourceVariantTree extends ResourceVariantTree {

	private final GitSyncCache gitCache;

	private final Map<IResource, IResourceVariant> cache = Collections
			.synchronizedMap(new WeakHashMap<IResource, IResourceVariant>());

	protected final GitSynchronizeDataSet gsds;

	GitResourceVariantTree(ResourceVariantByteStore store,
			GitSyncCache gitCache, GitSynchronizeDataSet gsds) {
		super(store);
		this.gsds = gsds;
		this.gitCache = gitCache;
	}

	public IResource[] roots() {
		Set<IResource> roots = new HashSet<IResource>();
		for (GitSynchronizeData gsd : gsds)
			if (gsd.getPathFilter() == null)
				roots.addAll(gsd.getProjects());
			else
				for (IResource resource : gsd.getIncludedResources())
					roots.add(resource.getProject());

		return roots.toArray(new IResource[roots.size()]);
	}

	/**
	 * Disposes all nested resources
	 */
	public void dispose() {
		if (gsds != null)
			gsds.dispose();

		cache.clear();
	}

	@Override
	protected IResourceVariant fetchVariant(IResource resource, int depth,
			IProgressMonitor monitor) throws TeamException {
		SubMonitor subMonitor = SubMonitor.convert(monitor);
		if (resource == null || isNonWorkspace(resource)) {
			subMonitor.done();
			return null;
		}

		subMonitor.beginTask(NLS.bind(
				CoreText.GitResourceVariantTree_fetchingVariant,
				resource.getName()), IProgressMonitor.UNKNOWN);
		try {
			return fetchVariant(resource);
		} finally {
			subMonitor.done();
		}
	}

	private IResourceVariant fetchVariant(IResource resource) {
		if (gitCache == null)
			return null;

		IResourceVariant cachedVariant = cache.get(resource);
		if (cachedVariant != null)
			return cachedVariant;

		GitSynchronizeData gsd = gsds.getData(resource.getProject());
		if (gsd == null)
			return null;

		Repository repo = gsd.getRepository();
		String path = getPath(resource, repo);

		GitSyncObjectCache syncCache = gitCache.get(repo);
		GitSyncObjectCache cachedData = syncCache.get(path);
		if (cachedData == null)
			return null;

		IResourceVariant variant = null;
		ObjectId objectId = getObjectId(cachedData.getDiffEntry());
		if (!objectId.equals(zeroId())) {
			if (resource.getType() == IResource.FILE)
				variant = new GitRemoteFile(repo, getCommitId(gsd), objectId,
						path);
			else
				variant = new GitRemoteFolder(repo, cachedData,
						getCommitId(gsd), objectId, path);

			cache.put(resource, variant);
		}

		return variant;
	}

	protected abstract ObjectId getObjectId(ThreeWayDiffEntry diffEntry);

	protected abstract RevCommit getCommitId(GitSynchronizeData gsd);

	@Override
	protected IResourceVariant[] fetchMembers(IResourceVariant variant,
			IProgressMonitor progress) throws TeamException {
		if (variant == null || !(variant instanceof GitRemoteFolder))
			return new IResourceVariant[0];

		GitRemoteFolder gitVariant = (GitRemoteFolder) variant;

		try {
			return gitVariant.members(progress);
		} finally {
			progress.done();
		}
	}

	public IResourceVariant getResourceVariant(final IResource resource)
			throws TeamException {
		return fetchVariant(resource, 0, null);
	}

	private String getPath(final IResource resource, Repository repo) {
		return stripWorkDir(repo.getWorkTree(), resource.getLocation().toFile());
	}

}
