/*******************************************************************************
 * Copyright (C) 2011, 2013 Tasktop Technologies Inc. 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:
 *    Benjamin Muskalla (benjamin.muskalla@tasktop.com) - initial implementation
 *******************************************************************************/
package org.eclipse.egit.ui.operations;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;

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

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.mapping.ModelProvider;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.resources.mapping.ResourceMappingContext;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.JobFamilies;
import org.eclipse.egit.ui.common.LocalRepositoryTestCase;
import org.eclipse.egit.ui.internal.operations.GitScopeOperation;
import org.eclipse.egit.ui.internal.operations.GitScopeOperationFactory;
import org.eclipse.egit.ui.internal.operations.GitScopeUtil;
import org.eclipse.egit.ui.test.Eclipse;
import org.eclipse.egit.ui.test.TestUtil;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
import org.eclipse.swtbot.swt.finder.results.VoidResult;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
import org.eclipse.team.core.subscribers.SubscriberScopeManager;
import org.eclipse.ui.IWorkbenchPart;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;

public class GitScopeUtilTest extends LocalRepositoryTestCase {

	public static final String MODEL_FILE = "base.model";

	public static final String MODEL_EXTENSIONS_FILE = "base.model.extension";

	private IWorkbenchPart part;

	private File repositoryFile;

	@Before
	public void setup() throws Exception {
		SWTBotView view = TestUtil.showExplorerView();
		part = view.getViewReference().getPart(false);

		repositoryFile = createProjectAndCommitToRepository();

		GitScopeOperationFactory.setFactory(new GitScopeOperationFactory() {
			@Override
			public GitScopeOperation createGitScopeOperation(
					IWorkbenchPart workbenchPart, SubscriberScopeManager manager) {
				return new GitScopeOperation(workbenchPart, manager) {
					@Override
					protected boolean promptForInputChange(
							String requestPreviewMessage,
							IProgressMonitor monitor) {
						// we will avoid the confirmation prompt in the tests
						return false;
					}
				};
			}
		});
	}

	@After
	public void tearDown() throws Exception {
		deleteAllProjects();
		shutDownRepositories();
		FileUtils.delete(repositoryFile.getParentFile(), FileUtils.RECURSIVE
				| FileUtils.RETRY);
	}

	@AfterClass
	public static void afterClassBase() throws Exception {
		// close all editors/dialogs
		new Eclipse().reset();
		// cleanup
		IProject modelProject = ResourcesPlugin.getWorkspace().getRoot()
				.getProject(PROJ1);
		modelProject.delete(false, false, null);

		GitScopeOperationFactory.setFactory(new GitScopeOperationFactory());
	}

	@Test
	// model provider already available via fragment.xml
	public void modelProviderWithExtensionFiles() throws Exception {
		IFile modelFile = createModelFiles();

		IResource[] selectedResources = new IResource[] { modelFile };
		IResource[] relatedChanges = getRelatedChangesInUIThread(selectedResources);
		assertEquals(2, relatedChanges.length);

		assertContainsResourceByName(relatedChanges, MODEL_FILE);
		assertContainsResourceByName(relatedChanges, MODEL_EXTENSIONS_FILE);
	}

	@Test
	public void relatedChangesWithNullResources() throws Exception {
		IResource[] resources = GitScopeUtil.getRelatedChanges(part, null);
		assertNotNull(resources);
		assertEquals(0, resources.length);
	}

	@Test
	public void relatedChangesWithEmptyResources() throws Exception {
		IResource[] res = new IResource[0];
		IResource[] resources = getRelatedChangesInUIThread(res);
		assertNotNull(resources);
		assertEquals(0, resources.length);
	}

	@Test(expected = IllegalArgumentException.class)
	public void relatedChangesWithInvalidParams() throws Exception {
		IResource[] res = new IResource[0];
		GitScopeUtil.getRelatedChanges(null, res);
	}

	@Test
	public void relatedChangesWithPrompt() throws Exception {
		GitScopeOperationFactory.setFactory(new GitScopeOperationFactory());

		final IFile modelFile = createModelFiles();

		Repository repository = lookupRepository(repositoryFile);
		Activator.getDefault().getIndexDiffCache()
				.getIndexDiffCacheEntry(repository);
		TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE);

		final IResource[] selectedResources = new IResource[] { modelFile };
		UIThreadRunnable.asyncExec(new VoidResult() {
			public void run() {
				try {
					GitScopeUtil.getRelatedChanges(part, selectedResources);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		});

		// Prompt because the files are untracked
		SWTBotShell dialog = bot.shell("Selection Adjustment Required");
		dialog.bot().button(IDialogConstants.OK_LABEL).click();

		IFile modelExtensionsFile = modelFile.getProject().getFile(
				MODEL_EXTENSIONS_FILE);
		addAndCommit(modelExtensionsFile, "add model extensions file");
		addAndCommit(modelFile, "add model file");
		TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE);

		// Both files are committed, should no longer prompt now
		IResource[] relatedChanges = getRelatedChangesInUIThread(selectedResources);
		assertEquals(2, relatedChanges.length);

		assertContainsResourceByName(relatedChanges, MODEL_FILE);
		assertContainsResourceByName(relatedChanges, MODEL_EXTENSIONS_FILE);
	}

	private IResource[] getRelatedChangesInUIThread(
			final IResource[] selectedResources) {
		final IResource[][] relatedChanges = new IResource[1][];
		UIThreadRunnable.syncExec(new VoidResult() {
			public void run() {
				try {
					relatedChanges[0] = GitScopeUtil.getRelatedChanges(part,
							selectedResources);
				} catch (Exception e) {
					e.printStackTrace();
					fail(e.getMessage());
				}
			}
		});
		return relatedChanges[0];
	}

	private IFile createModelFiles() throws CoreException,
			UnsupportedEncodingException {
		IProject project = ResourcesPlugin.getWorkspace().getRoot()
				.getProject(PROJ1);

		IFile modelFile = project.getFile(MODEL_FILE);
		modelFile.create(
				new ByteArrayInputStream("This is the base model"
						.getBytes(project.getDefaultCharset())), false,
				null);
		IFile modelExtensionFile = project.getFile(MODEL_EXTENSIONS_FILE);
		modelExtensionFile.create(
				new ByteArrayInputStream("Some more content"
						.getBytes(project.getDefaultCharset())), false,
				null);

		return modelFile;
	}

	private void assertContainsResourceByName(IResource[] relatedChanges,
			String fileName) {
		for (IResource resource : relatedChanges)
			if (resource.getName().equals(fileName))
				return;
		fail("Resource " + fileName + " not found.");
	}

	/**
	 * Model provider that requires for all given {@link IResource}s an
	 * additional {@link IResource}s with an appended extension ".extension"
	 *
	 */
	public static class MockModelProvider extends ModelProvider {

		@Override
		public ResourceMapping[] getMappings(IResource resource,
				ResourceMappingContext context, IProgressMonitor monitor)
				throws CoreException {
			ResourceMapping[] mappings = new ResourceMapping[2];
			mappings[0] = getMappingAdapter(resource);

			IPath resourcePath = resource.getFullPath();
			IPath extensionFilePath = resourcePath
					.addFileExtension("extension");
			IWorkspaceRoot workspace = resource.getWorkspace().getRoot();
			IFile extensionFile = workspace.getFile(extensionFilePath);
			mappings[1] = getMappingAdapter(extensionFile);
			return mappings;
		}

		private ResourceMapping getMappingAdapter(IResource resource) {
			return (ResourceMapping) resource.getAdapter(ResourceMapping.class);
		}
	}
}
