package com.atlassian.stash.internal.rest.commit;

import com.atlassian.bitbucket.rest.content.RestChange;
import com.atlassian.bitbucket.rest.RestErrors;
import com.atlassian.bitbucket.rest.util.*;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import com.atlassian.bitbucket.commit.CommitService;
import com.atlassian.bitbucket.content.ChangesRequest;
import com.atlassian.stash.internal.rest.content.JsonChangeCallback;
import com.atlassian.bitbucket.nav.NavBuilder;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.util.PageRequest;
import com.sun.jersey.spi.resource.Singleton;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;

import static com.atlassian.bitbucket.rest.util.CachePolicies.getCacheControlForRange;
import static com.atlassian.bitbucket.rest.util.CachePolicies.noCache;

/**
 * @since 2.11
 */
@AnonymousAllowed
@Consumes({MediaType.APPLICATION_JSON})
@Path(ResourcePatterns.COMMIT_URI + "/changes")
@Produces({RestUtils.APPLICATION_JSON_UTF8})
@Singleton
public class CommitChangeResource {

    private final CommitService commitService;
    private final NavBuilder navBuilder;

    public CommitChangeResource(CommitService commitService, NavBuilder navBuilder) {
        this.commitService = commitService;
        this.navBuilder = navBuilder;
    }

    /**
     * Retrieve a page of changes made in a specified commit.
     * <p>
     * <strong>Note:</strong> The implementation will apply a hard cap ({@code page.max.changes}) and it is not
     * possible to request subsequent content when that cap is exceeded.
     * <p>
     * The authenticated user must have <strong>REPO_READ</strong> permission for the specified repository to call this
     * resource.
     *
     * @param commitId      the commit to retrieve changes for
     * @param sinceId       the commit to which <code>until</code> should be compared to produce a page of changes.
     *                      If not specified the commit's first parent is assumed (if one exists)
     * @param pullRequestId when comments counts should be returned as well ({@code withComments == true}),
     *                      a pull request ID can be provided to only count comments that were made in the context
     *                      of a pull request. If no pull request ID is provided, only comments made outside
     *                      the context of any pull request will be counted.
     * @param withComments  {@code true} to apply comment counts in the changes (the default); otherwise, {@code false}
     *                      to stream changes without comment counts
     *
     * @response.representation.200.mediaType application/json
     * @response.representation.200.qname page
     * @response.representation.200.example {@link RestChange#PAGE_EXAMPLE}
     * @response.representation.200.doc A page of changes
     *
     * @response.representation.400.mediaType application/json
     * @response.representation.400.qname errors
     * @response.representation.400.example {@link RestErrors#EXAMPLE}
     * @response.representation.400.doc The until parameter was not supplied
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc The currently authenticated user has insufficient permissions to view
     *                                  the repository.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The repository or the since or until parameters supplied does not exist.
     */
    @GET
    public Response getChanges(@Context Repository repository,
                               @PathParam(ResourcePatterns.COMMIT_ID) String commitId,
                               @QueryParam("since") String sinceId,
                               @QueryParam("withComments") @DefaultValue("true") boolean withComments,
                               @QueryParam("pullRequestId") Long pullRequestId,
                               @Context final PageRequest pageRequest) {
        ChangesRequest request = new ChangesRequest.Builder(repository, commitId)
                .pullRequestId(pullRequestId)
                .sinceId(sinceId)
                .withComments(withComments)
                .build();
        NavBuilder.Commit commitNav = navBuilder.repo(repository).commit(commitId);

        return ResponseFactory.ok(new JsonStreamingOutput() {

            @Override
            public void write(StatefulJsonWriter writer) throws IOException, WebApplicationException {
                commitService.streamChanges(request, new JsonChangeCallback(writer, pageRequest, commitNav));
            }
        }, withComments ? noCache() : getCacheControlForRange(commitId, sinceId)).build();
    }
}
