/*******************************************************************************
 * Copyright (c) 2010, 2013 SAP AG 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:
 *    Mathias Kinzler (SAP AG) - initial implementation
 *    Daniel Megert <daniel_megert@ch.ibm.com> - Delete empty working directory
 *    Laurent Goubet <laurent.goubet@obeo.fr - 404121
 *******************************************************************************/
package org.eclipse.egit.ui.internal.repository.tree.command;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.RepositoryCache;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.JobFamilies;
import org.eclipse.egit.ui.internal.CommonUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.repository.tree.RepositoryNode;
import org.eclipse.egit.ui.internal.repository.tree.RepositoryTreeNodeType;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.submodule.SubmoduleWalk;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchSite;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;

/**
 * "Removes" one or several nodes
 */
public class RemoveCommand extends
		RepositoriesViewCommandHandler<RepositoryNode> {
	public Object execute(final ExecutionEvent event) throws ExecutionException {
		removeRepository(event, false);
		return null;
	}

	/**
	 * Remove or delete the repository
	 *
	 * @param event
	 * @param delete
	 *            if <code>true</code>, the repository will be deleted from disk
	 */
	protected void removeRepository(final ExecutionEvent event,
			final boolean delete) {
		IWorkbenchSite activeSite = HandlerUtil.getActiveSite(event);
		IWorkbenchSiteProgressService service = CommonUtils.getService(activeSite, IWorkbenchSiteProgressService.class);

		// get selected nodes
		final List<RepositoryNode> selectedNodes;
		try {
			selectedNodes = getSelectedNodes(event);
		} catch (ExecutionException e) {
			Activator.handleError(e.getMessage(), e, true);
			return;
		}
		boolean deleteWorkingDir = false;
		boolean removeProjects = false;
		final List<IProject> projectsToDelete = findProjectsToDelete(selectedNodes);
		if (delete) {
			if (selectedNodes.size() > 1) {
				return;
			} else if (selectedNodes.size() == 1) {
				Repository repository = selectedNodes.get(0).getObject();
				if (repository.isBare()) {
					// simple confirm dialog
					String title = UIText.RemoveCommand_ConfirmDeleteBareRepositoryTitle;
					String message = NLS
							.bind(
									UIText.RemoveCommand_ConfirmDeleteBareRepositoryMessage,
									repository.getDirectory().getPath());
					if (!MessageDialog.openConfirm(getShell(event), title,
							message))
						return;
				} else {
					// confirm dialog with check box
					// "delete also working directory"
					DeleteRepositoryConfirmDialog dlg = new DeleteRepositoryConfirmDialog(
							getShell(event), repository, projectsToDelete.size());
					if (dlg.open() != Window.OK)
						return;
					deleteWorkingDir = dlg.shouldDeleteWorkingDir();
					removeProjects = dlg.shouldRemoveProjects();
				}
			}
		}
		else {
			if (!projectsToDelete.isEmpty()) {
				final boolean[] confirmedCanceled = new boolean[] { false,
						false };
				Display.getDefault().syncExec(new Runnable() {

					public void run() {
						try {
							confirmedCanceled[0] = confirmProjectDeletion(
									projectsToDelete, event);
						} catch (OperationCanceledException e) {
							confirmedCanceled[1] = true;
						}
					}
				});
				if (confirmedCanceled[1])
					return;
				removeProjects = confirmedCanceled[0];
			}
		}

		final boolean deleteWorkDir = deleteWorkingDir;
		final boolean removeProj = removeProjects;

		Job job = new WorkspaceJob(UIText.RemoveCommand_RemoveRepositoriesJob) {

			@Override
			public IStatus runInWorkspace(IProgressMonitor monitor) {

				monitor.setTaskName(UIText.RepositoriesView_DeleteRepoDeterminProjectsMessage);

				if (removeProj) {
					// confirmed deletion
					deleteProjects(delete, projectsToDelete,
							monitor);
				}
				for (RepositoryNode node : selectedNodes) {
					util.removeDir(node.getRepository().getDirectory());
				}

				if (delete) {
					try {
						deleteRepositoryContent(selectedNodes, deleteWorkDir);
					} catch (IOException e) {
						return Activator.createErrorStatus(e.getMessage(), e);
					}
				}
				return Status.OK_STATUS;
			}

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

		service.schedule(job);
	}

	private void deleteProjects(
			final boolean delete,
			final List<IProject> projectsToDelete,
			IProgressMonitor monitor) {
		IWorkspaceRunnable wsr = new IWorkspaceRunnable() {

			public void run(IProgressMonitor actMonitor)
			throws CoreException {

				for (IProject prj : projectsToDelete)
					prj.delete(delete, false, actMonitor);
			}
		};

		try {
			ResourcesPlugin.getWorkspace().run(wsr,
					ResourcesPlugin.getWorkspace().getRoot(),
					IWorkspace.AVOID_UPDATE, monitor);
		} catch (CoreException e1) {
			Activator.logError(e1.getMessage(), e1);
		}
	}

	private void deleteRepositoryContent(
			final List<RepositoryNode> selectedNodes,
			final boolean deleteWorkDir) throws IOException {
		for (RepositoryNode node : selectedNodes) {
			Repository repo = node.getRepository();
			File workTree = deleteWorkDir && !repo.isBare() ? repo.getWorkTree() : null;
			if (workTree != null) {
				File[] files = workTree.listFiles();
				if (files != null)
					for (File file : files) {
						if (isTracked(file, repo))
							FileUtils.delete(file,
									FileUtils.RECURSIVE | FileUtils.RETRY);
					}
			}
			repo.close();

			if (!repo.isBare())
				closeSubmoduleRepositories(repo);

			FileUtils.delete(repo.getDirectory(),
					FileUtils.RECURSIVE | FileUtils.RETRY
							| FileUtils.SKIP_MISSING);

			if (workTree != null) {
				// Delete working directory if a submodule repository and refresh
				// parent repository
				if (node.getParent() != null
						&& node.getParent().getType() == RepositoryTreeNodeType.SUBMODULES) {
					FileUtils.delete(workTree, FileUtils.RECURSIVE
							| FileUtils.RETRY | FileUtils.SKIP_MISSING);
					node.getParent().getRepository().notifyIndexChanged();
				}
				// Delete if empty working directory
				String[] files = workTree.list();
				boolean isWorkingDirEmpty = files != null && files.length == 0;
				if (isWorkingDirEmpty)
					FileUtils.delete(workTree, FileUtils.RETRY | FileUtils.SKIP_MISSING);
			}
		}
	}

	private static void closeSubmoduleRepositories(Repository repo)
			throws IOException {
		SubmoduleWalk walk = SubmoduleWalk.forIndex(repo);
		try {
			while (walk.next()) {
				Repository subRepo = walk.getRepository();
				if (subRepo != null) {
					RepositoryCache cache = null;
					try {
						cache = org.eclipse.egit.core.Activator.getDefault()
								.getRepositoryCache();
					} finally {
						if (cache != null)
							cache.lookupRepository(subRepo.getDirectory())
									.close();
						subRepo.close();
					}
				}
			}
		} finally {
			walk.release();
		}
	}

	private boolean isTracked(File file, Repository repo) throws IOException {
		ObjectId objectId = repo.resolve(Constants.HEAD);
		RevTree tree;
		if (objectId != null)
			tree = new RevWalk(repo).parseTree(objectId);
		else
			tree = null;

		TreeWalk treeWalk = new TreeWalk(repo);
		treeWalk.setRecursive(true);
		if (tree != null)
			treeWalk.addTree(tree);
		else
			treeWalk.addTree(new EmptyTreeIterator());
		treeWalk.addTree(new DirCacheIterator(repo.readDirCache()));
		treeWalk.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(
				Repository.stripWorkDir(repo.getWorkTree(), file))));
		return treeWalk.next();

	}

	private List<IProject> findProjectsToDelete(final List<RepositoryNode> selectedNodes) {
		final List<IProject> projectsToDelete = new ArrayList<IProject>();
		for (RepositoryNode node : selectedNodes) {
			if (node.getRepository().isBare())
				continue;
			File workDir = node.getRepository().getWorkTree();
			final IPath wdPath = new Path(workDir.getAbsolutePath());
			for (IProject prj : ResourcesPlugin.getWorkspace()
					.getRoot().getProjects()) {
				IPath location = prj.getLocation();
				if (location != null && wdPath.isPrefixOf(location)) {
					projectsToDelete.add(prj);
				}
			}
		}
		return projectsToDelete;
	}

	@SuppressWarnings("boxing")
	private boolean confirmProjectDeletion(List<IProject> projectsToDelete,
			ExecutionEvent event) throws OperationCanceledException {

		String message = NLS.bind(
				UIText.RepositoriesView_ConfirmProjectDeletion_Question,
				projectsToDelete.size());
		MessageDialog dlg = new MessageDialog(getShell(event),
				UIText.RepositoriesView_ConfirmProjectDeletion_WindowTitle,
				null, message, MessageDialog.INFORMATION, new String[] {
						IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL,
						IDialogConstants.CANCEL_LABEL }, 0);
		int index = dlg.open();
		// Return true if 'Yes' was selected
		if (index == 0)
			return true;
		// Return false if 'No' was selected
		if (index == 1)
			return false;
		// Cancel operation in all other cases
		throw new OperationCanceledException();
	}
}
