/*******************************************************************************
 *  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.egit.github.core.service;

import static org.eclipse.egit.github.core.client.IGitHubConstants.CHARSET_UTF8;
import static org.eclipse.egit.github.core.client.IGitHubConstants.PARAM_LANGUAGE;
import static org.eclipse.egit.github.core.client.IGitHubConstants.PARAM_START_PAGE;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_BRANCHES;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_CONTRIBUTORS;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_FORKS;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_HOOKS;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_LANGUAGES;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_LEGACY;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_ORGS;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_REPOS;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_REPOSITORIES;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_SEARCH;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_TAGS;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_TEST;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_USER;
import static org.eclipse.egit.github.core.client.IGitHubConstants.SEGMENT_USERS;
import static org.eclipse.egit.github.core.client.PagedRequest.PAGE_FIRST;
import static org.eclipse.egit.github.core.client.PagedRequest.PAGE_SIZE;

import com.google.gson.reflect.TypeToken;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.egit.github.core.Contributor;
import org.eclipse.egit.github.core.IRepositoryIdProvider;
import org.eclipse.egit.github.core.IResourceProvider;
import org.eclipse.egit.github.core.Repository;
import org.eclipse.egit.github.core.RepositoryBranch;
import org.eclipse.egit.github.core.RepositoryHook;
import org.eclipse.egit.github.core.RepositoryId;
import org.eclipse.egit.github.core.RepositoryTag;
import org.eclipse.egit.github.core.SearchRepository;
import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.client.GitHubRequest;
import org.eclipse.egit.github.core.client.PageIterator;
import org.eclipse.egit.github.core.client.PagedRequest;

/**
 * Repository service class.
 *
 * @see <a href="http://developer.github.com/v3/repos">GitHub repository API
 *      documentation</a>
 * @see <a href="http://developer.github.com/v3/repos/forks">GitHub forks API
 *      documentation</a>
 */
public class RepositoryService extends GitHubService {

	/**
	 * FIELD_NAME
	 */
	public static final String FIELD_NAME = "name"; //$NON-NLS-1$

	/**
	 * FIELD_DESCRIPTION
	 */
	public static final String FIELD_DESCRIPTION = "description"; //$NON-NLS-1$

	/**
	 * FIELD_HOMEPAGE
	 */
	public static final String FIELD_HOMEPAGE = "homepage"; //$NON-NLS-1$

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

	/**
	 * Type filter key
	 */
	public static final String FILTER_TYPE = "type"; //$NON-NLS-1$

	/**
	 * Public repository filter type
	 */
	public static final String TYPE_PUBLIC = "public"; //$NON-NLS-1$

	/**
	 * Private repository filter type
	 */
	public static final String TYPE_PRIVATE = "private"; //$NON-NLS-1$

	/**
	 * Member repository filter type
	 */
	public static final String TYPE_MEMBER = "member"; //$NON-NLS-1$

	/**
	 * All repositories filter type
	 */
	public static final String TYPE_ALL = "all"; //$NON-NLS-1$

	private static class RepositoryContainer implements
			IResourceProvider<SearchRepository> {

		private List<SearchRepository> repositories;

		/**
		 * @see org.eclipse.egit.github.core.IResourceProvider#getResources()
		 */
		public List<SearchRepository> getResources() {
			return repositories;
		}
	}

	/**
	 * Create repository service
	 */
	public RepositoryService() {
		super();
	}

	/**
	 * Create repository service
	 *
	 * @param client
	 *            cannot be null
	 */
	public RepositoryService(GitHubClient client) {
		super(client);
	}

	/**
	 * Get repositories for the currently authenticated user
	 *
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<Repository> getRepositories() throws IOException {
		return getRepositories((Map<String, String>) null);
	}

	/**
	 * Get repositories for the currently authenticated user
	 *
	 * @param filterData
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<Repository> getRepositories(Map<String, String> filterData)
			throws IOException {
		return getAll(pageRepositories(filterData));
	}

	/**
	 * Page repositories for currently authenticated user
	 *
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageRepositories() {
		return pageRepositories(PAGE_SIZE);
	}

	/**
	 * Page repositories for currently authenticated user
	 *
	 * @param size
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageRepositories(int size) {
		return pageRepositories(PAGE_FIRST, size);
	}

	/**
	 * Page repositories for currently authenticated user
	 *
	 * @param start
	 * @param size
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageRepositories(int start, int size) {
		return pageRepositories((Map<String, String>) null, start, size);
	}

	/**
	 * Page repositories for currently authenticated user
	 *
	 * @param filterData
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageRepositories(
			Map<String, String> filterData) {
		return pageRepositories(filterData, PAGE_SIZE);
	}

	/**
	 * Page repositories for currently authenticated user
	 *
	 * @param filterData
	 * @param size
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageRepositories(
			Map<String, String> filterData, int size) {
		return pageRepositories(filterData, PAGE_FIRST, size);
	}

	/**
	 * Page repositories for currently authenticated user
	 *
	 * @param filterData
	 * @param start
	 * @param size
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageRepositories(
			Map<String, String> filterData, int start, int size) {
		PagedRequest<Repository> request = createPagedRequest(start, size);
		request.setUri(SEGMENT_USER + SEGMENT_REPOS);
		request.setParams(filterData);
		request.setType(new TypeToken<List<Repository>>() {
		}.getType());
		return createPageIterator(request);
	}

	/**
	 * Page all repositories
	 *
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageAllRepositories() {
		return pageAllRepositories(-1);
	}

	/**
	 * Page all repositories
	 *
	 * @param since
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageAllRepositories(final long since) {
		PagedRequest<Repository> request = createPagedRequest();
		request.setUri(SEGMENT_REPOSITORIES);
		if (since > 0)
			request.setParams(Collections.singletonMap("since",
					Long.toString(since)));
		request.setType(new TypeToken<List<Repository>>() {
		}.getType());
		return createPageIterator(request);
	}

	/**
	 * Get repositories for the given user
	 *
	 * @param user
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<Repository> getRepositories(String user) throws IOException {
		return getAll(pageRepositories(user));
	}

	/**
	 * Page repositories for given user
	 *
	 * @param user
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageRepositories(String user) {
		return pageRepositories(user, PAGE_SIZE);
	}

	/**
	 * Page repositories for given user
	 *
	 * @param user
	 * @param size
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageRepositories(String user, int size) {
		return pageRepositories(user, PAGE_FIRST, size);
	}

	/**
	 * Page repositories for given user
	 *
	 * @param user
	 * @param start
	 * @param size
	 * @return iterator over repository page
	 */
	public PageIterator<Repository> pageRepositories(String user, int start,
			int size) {
		if (user == null)
			throw new IllegalArgumentException("User cannot be null"); //$NON-NLS-1$
		if (user.length() == 0)
			throw new IllegalArgumentException("User cannot be empty"); //$NON-NLS-1$

		StringBuilder uri = new StringBuilder(SEGMENT_USERS);
		uri.append('/').append(user);
		uri.append(SEGMENT_REPOS);
		PagedRequest<Repository> request = createPagedRequest(start, size);
		request.setUri(uri);
		request.setType(new TypeToken<List<Repository>>() {
		}.getType());
		return createPageIterator(request);
	}

	/**
	 * Get organization repositories for the given organization
	 *
	 * @param organization
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<Repository> getOrgRepositories(String organization)
			throws IOException {
		return getOrgRepositories(organization, null);
	}

	/**
	 * Page repositories for the given organization
	 *
	 * @param organization
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageOrgRepositories(String organization) {
		return pageOrgRepositories(organization, PAGE_SIZE);
	}

	/**
	 * Page repositories for the given organization
	 *
	 * @param organization
	 * @param size
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageOrgRepositories(String organization,
			int size) {
		return pageOrgRepositories(organization, PAGE_FIRST, size);
	}

	/**
	 * Page repositories for the given organization
	 *
	 * @param organization
	 * @param start
	 * @param size
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageOrgRepositories(String organization,
			int start, int size) {
		return pageOrgRepositories(organization, null, start, size);
	}

	/**
	 * Get organization repositories for the given organization
	 *
	 * @param organization
	 * @param filterData
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<Repository> getOrgRepositories(String organization,
			Map<String, String> filterData) throws IOException {
		return getAll(pageOrgRepositories(organization, filterData));
	}

	/**
	 * Page repositories for the given organization
	 *
	 * @param organization
	 * @param filterData
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageOrgRepositories(String organization,
			Map<String, String> filterData) {
		return pageOrgRepositories(organization, filterData, PAGE_SIZE);
	}

	/**
	 * Page repositories for the given organization
	 *
	 * @param organization
	 * @param filterData
	 * @param size
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageOrgRepositories(String organization,
			Map<String, String> filterData, int size) {
		return pageOrgRepositories(organization, filterData, PAGE_FIRST, size);
	}

	/**
	 * Page repositories for the given organization
	 *
	 * @param organization
	 * @param filterData
	 * @param start
	 * @param size
	 * @return iterator over pages of repositories
	 */
	public PageIterator<Repository> pageOrgRepositories(String organization,
			Map<String, String> filterData, int start, int size) {
		if (organization == null)
			throw new IllegalArgumentException("Organization cannot be null"); //$NON-NLS-1$
		if (organization.length() == 0)
			throw new IllegalArgumentException("Organization cannot be empty"); //$NON-NLS-1$

		StringBuilder uri = new StringBuilder(SEGMENT_ORGS);
		uri.append('/').append(organization);
		uri.append(SEGMENT_REPOS);
		PagedRequest<Repository> request = createPagedRequest(start, size);
		request.setParams(filterData);
		request.setUri(uri);
		request.setType(new TypeToken<List<Repository>>() {
		}.getType());
		return createPageIterator(request);
	}

	/**
	 * Search for repositories matching query.
	 *
	 * @param query
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<SearchRepository> searchRepositories(final String query)
			throws IOException {
		return searchRepositories(query, -1);
	}

	/**
	 * Search for repositories matching query.
	 *
	 * @param query
	 * @param startPage
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<SearchRepository> searchRepositories(final String query,
			final int startPage) throws IOException {
		return searchRepositories(query, null, startPage);
	}

	/**
	 * Search for repositories matching language and query.
	 *
	 * @param query
	 * @param language
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<SearchRepository> searchRepositories(final String query,
			final String language) throws IOException {
		return searchRepositories(query, language, -1);
	}

	/**
	 * Search for repositories matching language and query.
	 *
	 * @param query
	 * @param language
	 * @param startPage
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<SearchRepository> searchRepositories(final String query,
			final String language, final int startPage) throws IOException {
		if (query == null)
			throw new IllegalArgumentException("Query cannot be null"); //$NON-NLS-1$
		if (query.length() == 0)
			throw new IllegalArgumentException("Query cannot be empty"); //$NON-NLS-1$

		StringBuilder uri = new StringBuilder(SEGMENT_LEGACY + SEGMENT_REPOS
				+ SEGMENT_SEARCH);
		final String encodedQuery = URLEncoder.encode(query, CHARSET_UTF8)
				.replace("+", "%20") //$NON-NLS-1$ //$NON-NLS-2$
				.replace(".", "%2E"); //$NON-NLS-1$ //$NON-NLS-2$
		uri.append('/').append(encodedQuery);

		PagedRequest<SearchRepository> request = createPagedRequest();

		Map<String, String> params = new HashMap<String, String>(2, 1);
		if (language != null && language.length() > 0)
			params.put(PARAM_LANGUAGE, language);
		if (startPage > 0)
			params.put(PARAM_START_PAGE, Integer.toString(startPage));
		if (!params.isEmpty())
			request.setParams(params);

		request.setUri(uri);
		request.setType(RepositoryContainer.class);
		return getAll(request);
	}

	/**
	 * Search for repositories matching search parameters.
	 *
	 * @param params
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<SearchRepository> searchRepositories(
			final Map<String, String> params) throws IOException {
		return searchRepositories(params, -1);
	}

	/**
	 * Search for repositories matching search parameters.
	 *
	 * @param queryParams
	 * @param startPage
	 * @return list of repositories
	 * @throws IOException
	 */
	public List<SearchRepository> searchRepositories(
			final Map<String, String> queryParams, final int startPage)
			throws IOException {
		if (queryParams == null)
			throw new IllegalArgumentException("Params cannot be null"); //$NON-NLS-1$
		if (queryParams.isEmpty())
			throw new IllegalArgumentException("Params cannot be empty"); //$NON-NLS-1$

		StringBuilder query = new StringBuilder();
		for (Entry<String, String> param : queryParams.entrySet())
			query.append(param.getKey()).append(':').append(param.getValue())
					.append(' ');
		return searchRepositories(query.toString(), startPage);
	}

	/**
	 * Create a new repository
	 *
	 * @param repository
	 * @return created repository
	 * @throws IOException
	 */
	public Repository createRepository(Repository repository)
			throws IOException {
		if (repository == null)
			throw new IllegalArgumentException("Repository cannot be null"); //$NON-NLS-1$

		return client.post(SEGMENT_USER + SEGMENT_REPOS, repository,
				Repository.class);
	}

	/**
	 * Create a new repository
	 *
	 * @param organization
	 * @param repository
	 * @return created repository
	 * @throws IOException
	 */
	public Repository createRepository(String organization,
			Repository repository) throws IOException {
		if (organization == null)
			throw new IllegalArgumentException("Organization cannot be null"); //$NON-NLS-1$
		if (organization.length() == 0)
			throw new IllegalArgumentException("Organization cannot be empty"); //$NON-NLS-1$
		if (repository == null)
			throw new IllegalArgumentException("Repository cannot be null"); //$NON-NLS-1$

		StringBuilder uri = new StringBuilder(SEGMENT_ORGS);
		uri.append('/').append(organization);
		uri.append(SEGMENT_REPOS);
		return client.post(uri.toString(), repository, Repository.class);
	}

	/**
	 * Get repository
	 *
	 * @param owner
	 * @param name
	 * @return repository
	 * @throws IOException
	 */
	public Repository getRepository(final String owner, final String name)
			throws IOException {
		return getRepository(RepositoryId.create(owner, name));
	}

	/**
	 * Get repository
	 *
	 * @param provider
	 * @return repository
	 * @throws IOException
	 */
	public Repository getRepository(final IRepositoryIdProvider provider)
			throws IOException {
		final String id = getId(provider);
		GitHubRequest request = createRequest();
		request.setUri(SEGMENT_REPOS + '/' + id);
		request.setType(Repository.class);
		return (Repository) client.get(request).getBody();
	}

	/**
	 * Create paged request for iterating over repositories forks
	 *
	 * @param repository
	 * @param start
	 * @param size
	 * @return paged request
	 */
	protected PagedRequest<Repository> createPagedForkRequest(
			IRepositoryIdProvider repository, int start, int size) {
		final String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_FORKS);
		PagedRequest<Repository> request = createPagedRequest(start, size);
		request.setUri(uri);
		request.setType(new TypeToken<List<Repository>>() {
		}.getType());
		return request;
	}

	/**
	 * Get all the forks of the given repository
	 *
	 * @param repository
	 * @return non-null but possibly empty list of repository
	 * @throws IOException
	 */
	public List<Repository> getForks(IRepositoryIdProvider repository)
			throws IOException {
		return getAll(pageForks(repository));
	}

	/**
	 * Page forks of given repository
	 *
	 * @param repository
	 * @return iterator over repositories
	 */
	public PageIterator<Repository> pageForks(IRepositoryIdProvider repository) {
		return pageForks(repository, PAGE_SIZE);
	}

	/**
	 * Page forks of given repository
	 *
	 * @param repository
	 * @param size
	 * @return iterator over repositories
	 */
	public PageIterator<Repository> pageForks(IRepositoryIdProvider repository,
			int size) {
		return pageForks(repository, PAGE_FIRST, size);
	}

	/**
	 * Page forks of given repository
	 *
	 * @param repository
	 * @param start
	 * @param size
	 * @return iterator over repositories
	 */
	public PageIterator<Repository> pageForks(IRepositoryIdProvider repository,
			int start, int size) {
		PagedRequest<Repository> request = createPagedForkRequest(repository,
				start, size);
		return createPageIterator(request);
	}

	/**
	 * Edit given repository
	 *
	 * @param repository
	 * @return edited repository
	 * @throws IOException
	 */
	public Repository editRepository(Repository repository) throws IOException {
		if (repository == null)
			throw new IllegalArgumentException("Repository cannot be null"); //$NON-NLS-1$

		final String repoId = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(repoId);
		return client.post(uri.toString(), repository, Repository.class);
	}

	/**
	 * Edit given fields in repository
	 * <p>
	 * Only values in the given fields map will be updated on the repository
	 *
	 * @param owner
	 * @param name
	 * @param fields
	 * @return edited repository
	 * @throws IOException
	 */
	public Repository editRepository(String owner, String name,
			Map<String, Object> fields) throws IOException {
		verifyRepository(owner, name);
		if (fields == null)
			throw new IllegalArgumentException("Fields cannot be null"); //$NON-NLS-1$

		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(owner).append('/').append(name);
		return client.post(uri.toString(), fields, Repository.class);
	}

	/**
	 * Edit given fields in repository
	 * <p>
	 * Only values in the given fields map will be updated on the repository
	 *
	 * @param provider
	 * @param fields
	 * @return edited repository
	 * @throws IOException
	 */
	public Repository editRepository(IRepositoryIdProvider provider,
			Map<String, Object> fields) throws IOException {
		String id = getId(provider);
		if (fields == null)
			throw new IllegalArgumentException("Fields cannot be null"); //$NON-NLS-1$

		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		return client.post(uri.toString(), fields, Repository.class);
	}

	/**
	 * Fork given repository into new repository under the currently
	 * authenticated user.
	 *
	 * @param repository
	 * @return forked repository
	 * @throws IOException
	 */
	public Repository forkRepository(IRepositoryIdProvider repository)
			throws IOException {
		return forkRepository(repository, null);
	}

	/**
	 * Fork given repository into new repository.
	 *
	 * The new repository will be under the given organization if non-null, else
	 * it will be under the currently authenticated user.
	 *
	 * @param repository
	 * @param organization
	 * @return forked repository
	 * @throws IOException
	 */
	public Repository forkRepository(IRepositoryIdProvider repository,
			String organization) throws IOException {
		final String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_FORKS);
		if (organization != null)
			uri.append("?org=").append(organization);
		return client.post(uri.toString(), null, Repository.class);
	}

	/**
	 * Get languages used in given repository
	 *
	 * @param repository
	 * @return map of language names mapped to line counts
	 * @throws IOException
	 */
	@SuppressWarnings({ "unchecked" })
	public Map<String, Long> getLanguages(IRepositoryIdProvider repository)
			throws IOException {
		String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_LANGUAGES);
		GitHubRequest request = createRequest();
		request.setUri(uri);
		request.setType(new TypeToken<Map<String, Long>>() {
		}.getType());
		return (Map<String, Long>) client.get(request).getBody();
	}

	/**
	 * Get branches in given repository
	 *
	 * @param repository
	 * @return list of branches
	 * @throws IOException
	 */
	public List<RepositoryBranch> getBranches(IRepositoryIdProvider repository)
			throws IOException {
		String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_BRANCHES);
		PagedRequest<RepositoryBranch> request = createPagedRequest();
		request.setUri(uri);
		request.setType(new TypeToken<List<RepositoryBranch>>() {
		}.getType());
		return getAll(request);
	}

	/**
	 * Get tags in given repository
	 *
	 * @param repository
	 * @return list of tags
	 * @throws IOException
	 */
	public List<RepositoryTag> getTags(IRepositoryIdProvider repository)
			throws IOException {
		String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_TAGS);
		PagedRequest<RepositoryTag> request = createPagedRequest();
		request.setUri(uri);
		request.setType(new TypeToken<List<RepositoryTag>>() {
		}.getType());
		return getAll(request);
	}

	/**
	 * Get contributors to repository
	 *
	 * @param repository
	 * @param includeAnonymous
	 * @return list of contributors
	 * @throws IOException
	 */
	public List<Contributor> getContributors(IRepositoryIdProvider repository,
			boolean includeAnonymous) throws IOException {
		String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_CONTRIBUTORS);
		PagedRequest<Contributor> request = createPagedRequest();
		request.setUri(uri);
		if (includeAnonymous)
			request.setParams(Collections.singletonMap("anon", "1")); //$NON-NLS-1$ //$NON-NLS-2$
		request.setType(new TypeToken<List<Contributor>>() {
		}.getType());
		return getAll(request);
	}

	/**
	 * Get hooks for given repository
	 *
	 * @param repository
	 * @return list of hooks
	 * @throws IOException
	 */
	public List<RepositoryHook> getHooks(IRepositoryIdProvider repository)
			throws IOException {
		String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_HOOKS);
		PagedRequest<RepositoryHook> request = createPagedRequest();
		request.setUri(uri);
		request.setType(new TypeToken<List<RepositoryHook>>() {
		}.getType());
		return getAll(request);
	}

	/**
	 * Get hook from repository with given id
	 *
	 * @param repository
	 * @param hookId
	 * @return repository hook
	 * @throws IOException
	 */
	public RepositoryHook getHook(IRepositoryIdProvider repository, int hookId)
			throws IOException {
		String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_HOOKS);
		uri.append('/').append(hookId);
		GitHubRequest request = createRequest();
		request.setType(RepositoryHook.class);
		request.setUri(uri);
		return (RepositoryHook) client.get(request).getBody();
	}

	/**
	 * Create hook in repository
	 *
	 * @param repository
	 * @param hook
	 * @return created repository hook
	 * @throws IOException
	 */
	public RepositoryHook createHook(IRepositoryIdProvider repository,
			RepositoryHook hook) throws IOException {
		String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_HOOKS);
		return client.post(uri.toString(), hook, RepositoryHook.class);
	}

	/**
	 * Edit hook in repository
	 *
	 * @param repository
	 * @param hook
	 * @return edited hook
	 * @throws IOException
	 */
	public RepositoryHook editHook(IRepositoryIdProvider repository,
			RepositoryHook hook) throws IOException {
		String id = getId(repository);
		if (hook == null)
			throw new IllegalArgumentException("Hook cannot be null"); //$NON-NLS-1$

		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_HOOKS);
		uri.append('/').append(hook.getId());
		return client.post(uri.toString(), hook, RepositoryHook.class);
	}

	/**
	 * Delete hook from repository
	 *
	 * @param repository
	 * @param hookId
	 * @throws IOException
	 */
	public void deleteHook(IRepositoryIdProvider repository, int hookId)
			throws IOException {
		String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_HOOKS);
		uri.append('/').append(hookId);
		client.delete(uri.toString());
	}

	/**
	 * Test hook in repository. This will trigger the hook to run for the latest
	 * push to the repository.
	 *
	 * @param repository
	 * @param hookId
	 * @throws IOException
	 */
	public void testHook(IRepositoryIdProvider repository, int hookId)
			throws IOException {
		String id = getId(repository);
		StringBuilder uri = new StringBuilder(SEGMENT_REPOS);
		uri.append('/').append(id);
		uri.append(SEGMENT_HOOKS);
		uri.append('/').append(hookId);
		uri.append(SEGMENT_TEST);
		client.post(uri.toString());
	}
}
