/*******************************************************************************
 *  Copyright (c) 2011 GitHub Inc.
 *  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:
 *    Kevin Sawicki (GitHub Inc.) - initial API and implementation
 *******************************************************************************/
package org.eclipse.mylyn.internal.github.ui.gist;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.MessageFormat;

import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.HandlerEvent;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.core.op.CloneOperation;
import org.eclipse.egit.core.op.CloneOperation.PostCloneTask;
import org.eclipse.egit.core.op.ConnectProviderOperation;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIPreferences;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.mylyn.internal.github.core.gist.GistAttribute;
import org.eclipse.mylyn.internal.github.ui.TaskDataHandler;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.HandlerUtil;

/**
 * Clone Gist handler class.
 */
public class CloneGistHandler extends TaskDataHandler {

	/**
	 * Get gist name for task data used to create projects and Git repositories
	 * 
	 * @param data
	 * @return name
	 */
	public static String getGistName(TaskData data) {
		return "gist-" + data.getTaskId(); //$NON-NLS-1$
	}

	private static IWorkspaceRoot getWorkspaceRoot() {
		return ResourcesPlugin.getWorkspace().getRoot();
	}

	private static RepositoryUtil getRepoUtil() {
		return org.eclipse.egit.core.Activator.getDefault().getRepositoryUtil();
	}

	/**
	 * ID
	 */
	public static final String ID = "org.eclipse.mylyn.github.ui.command.cloneGist"; //$NON-NLS-1$

	@Override
	public boolean isEnabled(TaskData data) {
		String id = getGistName(data);
		return !getWorkspaceRoot().getProject(id).exists()
				&& !getRepoUtil().getConfiguredRepositories().contains(id);
	}

	private File getParentDirectory() {
		String destinationDir = Activator.getDefault().getPreferenceStore()
				.getString(UIPreferences.DEFAULT_REPO_DIR);
		File parentDir = new File(destinationDir);
		if (!parentDir.exists() || !parentDir.isDirectory())
			parentDir = ResourcesPlugin.getWorkspace().getRoot()
					.getRawLocation().toFile();
		return parentDir;
	}

	private void createProject(final File workDir, final String name,
			final Repository repository, IProgressMonitor monitor)
			throws CoreException {
		IProjectDescription description = null;
		String projectName = null;
		File projectFile = new File(workDir, ".project"); //$NON-NLS-1$
		if (projectFile.exists()) {
			description = ResourcesPlugin.getWorkspace()
					.loadProjectDescription(
							Path.fromOSString(projectFile.getAbsolutePath()));
			projectName = description.getName();
		} else {
			description = ResourcesPlugin.getWorkspace().newProjectDescription(
					name);
			description
					.setLocation(Path.fromOSString(workDir.getAbsolutePath()));
			projectName = name;
		}

		monitor.setTaskName(Messages.CloneGistHandler_TaskCreatingProject);
		IProject project = ResourcesPlugin.getWorkspace().getRoot()
				.getProject(projectName);
		project.create(description, monitor);
		project.open(IResource.BACKGROUND_REFRESH, monitor);

		monitor.setTaskName(Messages.CloneGistHandler_TaskConnectingProject);
		new ConnectProviderOperation(project, repository.getDirectory())
				.execute(monitor);
	}

	private CloneOperation createCloneOperation(TaskData data, String name)
			throws IOException, URISyntaxException {
		String pullUrl = data.getRoot()
				.getAttribute(GistAttribute.CLONE_URL.getMetadata().getId())
				.getValue();
		URIish uri = new URIish(pullUrl);
		int timeout = Activator.getDefault().getPreferenceStore()
				.getInt(UIPreferences.REMOTE_CONNECTION_TIMEOUT);
		final File workDir = new File(getParentDirectory(), name);

		if (getRepoUtil().getConfiguredRepositories().contains(
				new File(workDir, Constants.DOT_GIT).getAbsolutePath()))
			throw new IOException(MessageFormat.format(
					Messages.CloneGistHandler_ErrorRepoExists, name));

		return new CloneOperation(uri, true, null, workDir, Constants.R_HEADS
				+ Constants.MASTER, Constants.DEFAULT_REMOTE_NAME, timeout);
	}

	private Job createCloneJob(final ExecutionEvent event, final TaskData data) {
		Job job = new Job(Messages.CloneGistHandler_TaskCloning) {

			protected IStatus run(IProgressMonitor monitor) {
				try {
					final String name = getGistName(data);

					CloneOperation operation = createCloneOperation(data, name);

					operation.addPostCloneTask(new PostCloneTask() {

						public void execute(Repository repository,
								IProgressMonitor monitor) throws CoreException {
							if (monitor.isCanceled())
								return;
							monitor.setTaskName(Messages.CloneGistHandler_TaskRegisteringRepository);
							getRepoUtil().addConfiguredRepository(
									repository.getDirectory());
						}
					});

					operation.addPostCloneTask(new PostCloneTask() {

						public void execute(final Repository repository,
								IProgressMonitor monitor) throws CoreException {
							IWorkspaceRunnable runnable = new IWorkspaceRunnable() {

								public void run(IProgressMonitor monitor)
										throws CoreException {
									if (monitor.isCanceled())
										return;
									createProject(repository.getDirectory()
											.getParentFile(), name, repository,
											monitor);
								}
							};
							ResourcesPlugin.getWorkspace().run(runnable,
									monitor);
						}
					});

					operation.run(monitor);
				} catch (Exception e) {
					displayError(event, e);
					Activator.logError("Error cloning gist", e); //$NON-NLS-1$
				} finally {
					fireHandlerChanged(new HandlerEvent(CloneGistHandler.this,
							true, false));
				}
				return Status.OK_STATUS;
			}
		};
		return job;
	}

	private void displayError(final ExecutionEvent event, Exception exception) {
		final Throwable cause = exception.getCause() != null ? exception
				.getCause() : exception;
		PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {

			public void run() {
				ErrorDialog.openError(HandlerUtil.getActiveShell(event),
						Messages.CloneGistHandler_ErrorTitle,
						Messages.CloneGistHandler_ErrorMessage, Activator
								.createErrorStatus(cause.getLocalizedMessage(),
										cause));
			}
		});
	}

	/**
	 * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
	 */
	public Object execute(ExecutionEvent event) throws ExecutionException {
		TaskData data = getTaskData(event);
		if (data != null)
			schedule(createCloneJob(event, data), event);
		return null;
	}
}
