/*******************************************************************************
 * Copyright (C) 2013, Laurent Goubet <laurent.goubet@obeo.fr> 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
 *******************************************************************************/
package org.eclipse.egit.core.synchronize;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.File;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.RemoteResourceMappingContext;
import org.eclipse.core.resources.mapping.ResourceTraversal;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet;
import org.eclipse.egit.core.test.GitTestCase;
import org.eclipse.egit.core.test.TestRepository;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;

public class GitSubscriberResourceMappingContextTest extends GitTestCase {

	private static final String MASTER = Constants.R_HEADS + Constants.MASTER;

	private static final String BRANCH = Constants.R_HEADS + "branch";

	private Repository repo;

	private IProject iProject;

	private TestRepository testRepo;

	@Before
	public void setUp() throws Exception {
		super.setUp();

		iProject = project.project;
		testRepo = new TestRepository(gitDir);
		testRepo.connect(iProject);
		repo = RepositoryMapping.getMapping(iProject).getRepository();

		// make initial commit
		new Git(repo).commit().setAuthor("JUnit", "junit@jgit.org")
				.setMessage("Initial commit").call();
	}

	@Test
	public void hasLocalChange() throws Exception {
		File file1 = testRepo.createFile(iProject, "a.txt");
		File file2 = testRepo.createFile(iProject, "b.txt");
		testRepo.appendContentAndCommit(iProject, file1, "content a",
				"commit a");
		testRepo.appendContentAndCommit(iProject, file2, "content b",
				"commit b");

		IFile iFile1 = testRepo.getIFile(iProject, file1);
		IFile iFile2 = testRepo.getIFile(iProject, file2);

		RemoteResourceMappingContext context = prepareContext(MASTER, MASTER);
		assertFalse(context.hasLocalChange(iFile1, new NullProgressMonitor()));
		assertFalse(context.hasLocalChange(iFile2, new NullProgressMonitor()));

		JGitTestUtil.write(file1, "changed content a");
		JGitTestUtil.write(file2, "changed content b");

		refresh(context, iFile1, iFile2);
		assertTrue(context.hasLocalChange(iFile1, new NullProgressMonitor()));
		assertTrue(context.hasLocalChange(iFile2, new NullProgressMonitor()));

		JGitTestUtil.write(file2, "content b");

		refresh(context, iFile2);
		assertTrue(context.hasLocalChange(iFile1, new NullProgressMonitor()));
		assertFalse(context.hasLocalChange(iFile2, new NullProgressMonitor()));
	}

	@Test
	public void hasLocalChangeWithFileRemoval() throws Exception {
		File file1 = testRepo.createFile(iProject, "a.txt");
		File file2 = testRepo.createFile(iProject, "b.txt");
		File file3 = testRepo.createFile(iProject, "c.txt");

		IFile iFile1 = testRepo.getIFile(iProject, file1);
		IFile iFile2 = testRepo.getIFile(iProject, file2);
		IFile iFile3 = testRepo.getIFile(iProject, file3);

		RemoteResourceMappingContext context = prepareContext(MASTER, MASTER);
		assertTrue(context.hasLocalChange(iFile1, new NullProgressMonitor()));
		assertTrue(context.hasLocalChange(iFile2, new NullProgressMonitor()));
		assertTrue(context.hasLocalChange(iFile3, new NullProgressMonitor()));

		iFile1.delete(false, null);
		refresh(context, iFile1, iFile2, iFile3);
		assertTrue(context.hasLocalChange(iFile2, new NullProgressMonitor()));
		assertTrue(context.hasLocalChange(iFile3, new NullProgressMonitor()));
	}

	@Test
	public void hasLocalChangeInNewFolder() throws Exception {
		iProject.getFolder("folder").create(false, true, null);
		RemoteResourceMappingContext context = prepareContext(MASTER, MASTER);
		// Folder is now known, but not yet file in it

		File file = testRepo.createFile(iProject, "folder/b.txt");
		IFile iFile = testRepo.getIFile(iProject, file);
		refresh(context, iFile);
		assertTrue(context.hasLocalChange(iFile, new NullProgressMonitor()));

		testRepo.addToIndex(iProject, file);
		refresh(context, iFile);
		assertTrue(context.hasLocalChange(iFile, new NullProgressMonitor()));

		JGitTestUtil.write(file, "changed content b");
		refresh(context, iFile);
		assertTrue(context.hasLocalChange(iFile, new NullProgressMonitor()));
	}

	@Test
	public void hasRemoteChanges() throws Exception {
		File file1 = testRepo.createFile(iProject, "file1.sample");
		File file2 = testRepo.createFile(iProject, "file2.sample");

		testRepo.appendContentAndCommit(iProject, file1,
				"initial content - file 1",
				"first file - initial commit MASTER");
		testRepo.appendContentAndCommit(iProject, file2,
				"initial content - file 2",
				"second file - initial commit MASTER");

		IFile iFile1 = testRepo.getIFile(iProject, file1);
		IFile iFile2 = testRepo.getIFile(iProject, file2);

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);

		setContentsAndCommit(iFile1, "change in branch - file 1",
				"branch commit - file1");
		setContentsAndCommit(iFile2, "change in branch - file 2",
				"branch commit - file2");

		testRepo.checkoutBranch(MASTER);

		RemoteResourceMappingContext context = prepareContext(MASTER, BRANCH);
		assertFalse(context.hasLocalChange(iFile1, new NullProgressMonitor()));
		assertTrue(context.hasRemoteChange(iFile1, new NullProgressMonitor()));
		assertFalse(context.hasLocalChange(iFile2, new NullProgressMonitor()));
		assertTrue(context.hasRemoteChange(iFile2, new NullProgressMonitor()));

		setContents(iFile1, "change in master - file 1");
		refresh(context, iFile1);
		assertTrue(context.hasLocalChange(iFile1, new NullProgressMonitor()));
		assertTrue(context.hasRemoteChange(iFile1, new NullProgressMonitor()));

		setContents(iFile2, "change in branch - file 2");
		refresh(context, iFile2);
		assertTrue(context.hasLocalChange(iFile2, new NullProgressMonitor()));
		assertTrue(context.hasRemoteChange(iFile2, new NullProgressMonitor()));

		setContentsAndCommit(iFile1, "change in branch - file 1",
				"change in master (same as in branch) - file 2");
		refresh(context, iFile1);
		assertTrue(context.hasLocalChange(iFile1, new NullProgressMonitor()));
		assertTrue(context.hasRemoteChange(iFile1, new NullProgressMonitor()));
	}

	@Test
	public void hasRemoteChangeInNewFile() throws Exception {
		File file1 = testRepo.createFile(iProject, "file1.sample");
		String initialContent1 = "some content for the first file";
		testRepo.appendContentAndCommit(iProject, file1, initialContent1,
				"first file - initial commit");
		IFile iFile1 = testRepo.getIFile(iProject, file1);

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);

		File file2 = testRepo.createFile(iProject, "file2.sample");
		String initialContent2 = "some content for the second file";
		testRepo.appendContentAndCommit(iProject, file2, initialContent2,
				"second file - initial commit");
		IFile iFile2 = testRepo.getIFile(iProject, file2);

		testRepo.checkoutBranch(MASTER);

		RemoteResourceMappingContext context = prepareContext(MASTER, BRANCH);
		assertFalse(context.hasRemoteChange(iFile1, new NullProgressMonitor()));
		assertTrue(context.hasRemoteChange(iFile2, new NullProgressMonitor()));
	}

	@Test
	public void hasRemoteChangeInNewFolder() throws Exception {
		File file1 = testRepo.createFile(iProject, "file1.sample");
		String initialContent1 = "some content for the first file";
		testRepo.appendContentAndCommit(iProject, file1, initialContent1,
				"first file - initial commit");
		IFile iFile1 = testRepo.getIFile(iProject, file1);

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);

		iProject.getFolder("folder").create(true, true,
				new NullProgressMonitor());
		File file2 = testRepo.createFile(iProject, "folder/file2.sample");
		String initialContent2 = "some content for the second file";
		testRepo.appendContentAndCommit(iProject, file2, initialContent2,
				"second file - initial commit");
		IFile iFile2 = testRepo.getIFile(iProject, file2);

		testRepo.checkoutBranch(MASTER);

		RemoteResourceMappingContext context = prepareContext(MASTER, BRANCH);
		assertFalse(context.hasRemoteChange(iFile1, new NullProgressMonitor()));
		assertTrue(context.hasRemoteChange(iFile2, new NullProgressMonitor()));
	}

	@Test
	public void hasLocalAndRemoteChange() throws Exception {
		File file1 = testRepo.createFile(iProject, "file1.sample");
		testRepo.appendContentAndCommit(iProject, file1, "initial content",
				"first commit in master");
		IFile iFile1 = testRepo.getIFile(iProject, file1);

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);
		setContentsAndCommit(iFile1, "changed content in branch",
				"first commit in BRANCH");

		testRepo.checkoutBranch(MASTER);
		setContentsAndCommit(iFile1, "changed content in master",
				"second commit in MASTER");

		RemoteResourceMappingContext context = prepareContext(MASTER, BRANCH);
		assertTrue(context.hasRemoteChange(iFile1, new NullProgressMonitor()));
		assertTrue(context.hasLocalChange(iFile1, new NullProgressMonitor()));
	}

	@Test
	public void hasLocalAndRemoteChangeInSubFolder() throws Exception {
		File file1 = testRepo.createFile(iProject, "folder/file1.sample");
		testRepo.appendContentAndCommit(iProject, file1, "initial content",
				"first commit in master");
		IFile iFile1 = testRepo.getIFile(iProject, file1);

		testRepo.createAndCheckoutBranch(MASTER, BRANCH);
		setContentsAndCommit(iFile1, "changed content in branch",
				"first commit in BRANCH");

		testRepo.checkoutBranch(MASTER);
		setContentsAndCommit(iFile1, "changed content in master",
				"second commit in MASTER");

		RemoteResourceMappingContext context = prepareContext(MASTER, BRANCH);
		assertTrue(context.hasRemoteChange(iFile1, new NullProgressMonitor()));
		assertTrue(context.hasLocalChange(iFile1, new NullProgressMonitor()));
	}

	@Test
	public void hasLocalChangeWhenRefreshingParentFolder() throws Exception {
		IFolder folder = iProject.getFolder("newfolder");
		folder.create(false, true, null);

		IFile file = folder.getFile("a.txt");
		file.create(new ByteArrayInputStream("a".getBytes()), false, null);

		RemoteResourceMappingContext context = prepareContext(MASTER, MASTER);
		refresh(context, file);

		assertTrue(context.hasLocalChange(file, new NullProgressMonitor()));

		file.delete(false, null);

		// Refresh of folder, not file directly
		refresh(context, folder);

		assertFalse(context.hasLocalChange(file, new NullProgressMonitor()));
	}

	private RevCommit setContentsAndCommit(IFile targetFile,
			String newContents, String commitMessage)
			throws Exception {
		setContents(targetFile, newContents);
		return addAndCommit(targetFile, commitMessage);
	}

	private RevCommit addAndCommit(IFile targetFile, String commitMessage) throws Exception {
		testRepo.addToIndex(targetFile);
		return testRepo.commit(commitMessage);
	}

	private void setContents(IFile targetFile, String newContents)
			throws CoreException {
		targetFile.setContents(
				new ByteArrayInputStream(newContents.getBytes()),
				IResource.FORCE, new NullProgressMonitor());
	}

	private RemoteResourceMappingContext prepareContext(String srcRev,
			String dstRev) throws Exception {
		GitSynchronizeData gsd = new GitSynchronizeData(repo, srcRev, dstRev,
				true);
		GitSynchronizeDataSet gsds = new GitSynchronizeDataSet(gsd);
		GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(
				gsds);
		subscriber.init(new NullProgressMonitor());

		return new GitSubscriberResourceMappingContext(subscriber, gsds);
	}

	private void refresh(RemoteResourceMappingContext context,
			IResource... resources) throws Exception {
		context.refresh(new ResourceTraversal[] { new ResourceTraversal(
				resources, IResource.DEPTH_INFINITE, 0) }, 0,
				new NullProgressMonitor());
	}
}
