/******************************************************************************
 *  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.core.pr;

import java.io.IOException;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.egit.github.core.Comment;
import org.eclipse.egit.github.core.IRepositoryIdProvider;
import org.eclipse.egit.github.core.PullRequest;
import org.eclipse.egit.github.core.Repository;
import org.eclipse.egit.github.core.RepositoryId;
import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.client.GsonUtils;
import org.eclipse.egit.github.core.client.IGitHubConstants;
import org.eclipse.egit.github.core.service.IssueService;
import org.eclipse.egit.github.core.service.PullRequestService;
import org.eclipse.mylyn.commons.net.AuthenticationCredentials;
import org.eclipse.mylyn.commons.net.AuthenticationType;
import org.eclipse.mylyn.internal.github.core.GitHub;
import org.eclipse.mylyn.internal.github.core.QueryUtils;
import org.eclipse.mylyn.internal.github.core.RepositoryConnector;
import org.eclipse.mylyn.internal.github.core.issue.IssueConnector;
import org.eclipse.mylyn.internal.tasks.core.IRepositoryConstants;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskDataCollector;
import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession;

/**
 * GitHub pull request connector.
 */
public class PullRequestConnector extends RepositoryConnector {

	/**
	 * SEGMENT_PULL
	 */
	public static final String SEGMENT_PULL = "/pull"; //$NON-NLS-1$

	/**
	 * GitHub kind.
	 */
	public static final String KIND = "githubPullRequests"; //$NON-NLS-1$

	/**
	 * Get repository label for id provider.
	 * 
	 * @param repo
	 * @return label
	 */
	public static String getRepositoryLabel(IRepositoryIdProvider repo) {
		return repo.generateId()
				+ Messages.PullRequestConnector_LabelPullRequests;
	}

	/**
	 * Create pull request task repository
	 * 
	 * @param repo
	 * @param username
	 * @param password
	 * @return task repository
	 */
	public static TaskRepository createTaskRepository(Repository repo,
			String username, String password) {
		String url = PullRequestConnector.appendPulls(GitHub.createGitHubUrl(
				repo.getOwner().getLogin(), repo.getName()));
		TaskRepository repository = new TaskRepository(KIND, url);
		repository.setProperty(IRepositoryConstants.PROPERTY_LABEL,
				getRepositoryLabel(repo));
		if (username != null && password != null)
			repository.setCredentials(AuthenticationType.REPOSITORY,
					new AuthenticationCredentials(username, password), true);
		repository.setProperty(IRepositoryConstants.PROPERTY_CATEGORY,
				IRepositoryConstants.CATEGORY_REVIEW);
		return repository;
	}

	/**
	 * Appends a trailing '/pull's segment to the given url
	 * 
	 * @param repoUrl
	 * @return appended string
	 */
	public static String appendPulls(final String repoUrl) {
		return repoUrl + IGitHubConstants.SEGMENT_PULLS;
	}

	/**
	 * Strip trailing '/pulls' segment from string if it ends with it.
	 * 
	 * @param repoUrl
	 * @return stripped string
	 */
	public static String stripPulls(String repoUrl) {
		if (repoUrl.endsWith(IGitHubConstants.SEGMENT_PULLS))
			repoUrl = repoUrl.substring(0, repoUrl.length()
					- IGitHubConstants.SEGMENT_PULLS.length());
		return repoUrl;
	}

	/**
	 * Get pull request from task data
	 * 
	 * @param data
	 * @return pull request
	 */
	public static PullRequestComposite getPullRequest(TaskData data) {
		if (data == null)
			return null;
		String value = PullRequestAttribute.MODEL.getMetadata().getValue(data);
		if (value.length() == 0)
			return null;
		return GsonUtils.fromJson(value, PullRequestComposite.class);
	}

	/**
	 * Get repository id from pull request task repository url
	 * 
	 * @param prRepoUrl
	 * @return repository id
	 */
	public static RepositoryId getRepository(String prRepoUrl) {
		return GitHub.getRepository(stripPulls(prRepoUrl));
	}

	/**
	 * GitHub specific {@link AbstractTaskDataHandler}.
	 */
	private final PullRequestTaskDataHandler taskDataHandler;

	/**
	 * Create GitHub issue repository connector
	 */
	public PullRequestConnector() {
		taskDataHandler = new PullRequestTaskDataHandler(this);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see #KIND
	 */
	@Override
	public String getConnectorKind() {
		return KIND;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getLabel() {
		return Messages.PullRequestConnector_Label;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public AbstractTaskDataHandler getTaskDataHandler() {
		return taskDataHandler;
	}

	@Override
	public IStatus performQuery(TaskRepository repository,
			IRepositoryQuery query, TaskDataCollector collector,
			ISynchronizationSession session, IProgressMonitor monitor) {
		IStatus result = Status.OK_STATUS;
		List<String> statuses = QueryUtils.getAttributes(
				IssueService.FILTER_STATE, query);

		monitor.beginTask(Messages.PullRequestConnector_TaskFetching,
				statuses.size());
		try {
			RepositoryId repo = getRepository(repository.getRepositoryUrl());

			GitHubClient client = IssueConnector.createClient(repository);
			PullRequestService service = new PullRequestService(client);
			IssueService commentService = new IssueService(client);

			for (String status : statuses) {
				List<PullRequest> pulls = service.getPullRequests(repo, status);

				// collect task data
				for (PullRequest pr : pulls) {
					pr = service.getPullRequest(repo, pr.getNumber());
					PullRequestComposite prComp = new PullRequestComposite();
					prComp.setRequest(pr);
					List<Comment> comments = null;
					if (pr.getComments() > 0)
						comments = commentService.getComments(repo.getOwner(),
								repo.getName(),
								Integer.toString(pr.getNumber()));
					if (pr.getCommits() > 0)
						prComp.setCommits(service.getCommits(repo,
								pr.getNumber()));
					TaskData taskData = taskDataHandler.createTaskData(
							repository, monitor, repo, prComp, comments);
					collector.accept(taskData);
				}
				monitor.worked(1);
			}
		} catch (IOException e) {
			result = GitHub.createWrappedStatus(e);
		}

		monitor.done();
		return result;
	}

	@Override
	public TaskData getTaskData(TaskRepository repository, String taskId,
			IProgressMonitor monitor) throws CoreException {
		RepositoryId repo = getRepository(repository.getRepositoryUrl());

		try {
			GitHubClient client = IssueConnector.createClient(repository);
			PullRequestService service = new PullRequestService(client);
			PullRequest pr = service.getPullRequest(repo,
					Integer.parseInt(taskId));
			PullRequestComposite prComp = new PullRequestComposite();
			prComp.setRequest(pr);
			IssueService commentService = new IssueService(client);
			List<Comment> comments = null;
			if (pr.getComments() > 0)
				comments = commentService.getComments(repo.getOwner(),
						repo.getName(), taskId);
			if (pr.getCommits() > 0)
				prComp.setCommits(service.getCommits(repo, pr.getNumber()));
			return taskDataHandler.createTaskData(repository, monitor, repo,
					prComp, comments);
		} catch (IOException e) {
			throw new CoreException(GitHub.createWrappedStatus(e));
		}
	}

	@Override
	public String getRepositoryUrlFromTaskUrl(String taskFullUrl) {
		int lastPull = taskFullUrl.lastIndexOf(SEGMENT_PULL);
		if (lastPull != -1)
			return taskFullUrl.substring(0, lastPull)
					+ IGitHubConstants.SEGMENT_PULLS;
		return null;
	}

	@Override
	public String getTaskUrl(String repositoryUrl, String taskId) {
		return stripPulls(repositoryUrl) + SEGMENT_PULL + "/" + taskId; //$NON-NLS-1$
	}
}
