package com.atlassian.bitbucket.content;

import com.atlassian.bitbucket.commit.CommitService;
import com.atlassian.bitbucket.repository.Repository;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Request arguments for {@link CommitService#streamDiff(DiffRequest, DiffContentCallback)}.
 */
public class DiffRequest extends AbstractDiffRequest {

    private final boolean autoSrcPath;
    private final Repository repository;
    private final Long pullRequestId;
    private final String sinceId;
    private final String untilId;
    private final boolean withComments;

    private DiffRequest(Builder builder) {
        super(builder);
        autoSrcPath = builder.autoSrcPath;
        repository = builder.repository;
        pullRequestId = builder.pullRequestId;
        sinceId = builder.sinceId;
        untilId = builder.untilId;
        withComments = builder.withComments;

        if (autoSrcPath) {
            if (getPaths().isEmpty()) {
                throw new IllegalStateException("A path must be supplied when attempting to resolve the source path");
            }
            if (getPaths().size() > 1) {
                throw new IllegalStateException("Resolving the source path is only possible with a single path");
            }
        }
    }

    /**
     * @return the repository to use
     */
    @Nonnull
    public Repository getRepository() {
        return repository;
    }

    /**
     * When comments should be returned as well ({@link #isWithComments()} == {@code true}), a pull request ID can be
     * provided to only retrieve comments that were made in the context of that pull request. If no pull request ID is
     * provided, only comments made outside the context of any pull request will be retrieved.
     *
     * @return the ID for the pull request in which the diff will be displayed
     * @since 4.7
     */
    public Long getPullRequestId() {
        return pullRequestId;
    }

    /**
     * @return the "since" commit for the diff or {@code null} for the {@code untilId}'s parent
     */
    @Nullable
    public String getSinceId() {
        return sinceId;
    }

    /**
     * @return the "until" commit for the diff
     */
    @Nonnull
    public String getUntilId() {
        return untilId;
    }

    /**
     * @return {@code true} if attempting to resolve the source path in case of a rename, {@code false} otherise.
     * Only valid for a single path.
     * @since 4.5
     */
    public boolean isWithAutoSrcPath() {
        return autoSrcPath;
    }

    /**
     * @return {@code true} if the comment associated with the diff should be included in the response to this request.
     * {@code false} otherwise
     */
    public boolean isWithComments() {
        return withComments;
    }


    public static class Builder extends AbstractBuilder<Builder, DiffRequest> {

        private final Repository repository;
        private final String untilId;

        private boolean autoSrcPath;
        private Long pullRequestId;
        private String sinceId;
        private boolean withComments;

        public Builder(@Nonnull DiffRequest request) {
            super(request);
            autoSrcPath = request.isWithAutoSrcPath();
            repository = checkNotNull(request.getRepository(), "request.repository");
            sinceId = StringUtils.trimToNull(request.getSinceId());
            untilId = checkNotNull(request.getUntilId(), "request.untilId");
            withComments = request.isWithComments();
        }

        public Builder(@Nonnull Repository repository, @Nonnull String untilId) {
            checkNotNull(untilId, "untilId");
            checkArgument(StringUtils.isNotBlank(untilId), "An \"until\" ID is required to stream a diff");

            this.repository = checkNotNull(repository, "repository");
            this.untilId = untilId;
            this.withComments = true;
        }

        @Nonnull
        public Builder autoSrcPath(boolean value) {
            autoSrcPath = value;

            return self();
        }

        @Nonnull
        @Override
        public DiffRequest build() {
            return new DiffRequest(this);
        }

        @Nonnull
        @Override
        public Builder contextLines(int value) { // for binary compatibility
            return super.contextLines(value);
        }

        /**
         * Specifies the "since" commit for the diff. Generally this is an <i>ancestor</i> of the "until" commit,
         * but it is not <i>required</i> to be.
         * <p>
         * If the "since" and "until" commit are not directly related, the returned diff may not make sense. For
         * example, it may show files as added, removed or modified that have not actually been changed. When an
         * explicit "since" commit is provided, it is recommended to always use an ancestor of the "until" commit.
         *
         * @param value the "since" commit's ID, which will be trimmed to {@code null} if blank
         * @return {@code this}
         */
        @Nonnull
        public Builder sinceId(@Nullable String value) {
            sinceId = StringUtils.trimToNull(value);

            return self();
        }

        @Nonnull
        @Override
        public Builder path(@Nullable String value) { // for binary compatibility
            return super.path(value);
        }

        @Nonnull
        @Override
        public Builder paths(@Nullable Iterable<String> values) { // for binary compatibility
            return super.paths(values);
        }

        @Nonnull
        @Override
        public Builder paths(@Nullable String value, @Nullable String... values) { // for binary compatibility
            return super.paths(value, values);
        }

        @Nonnull
        public Builder pullRequest(@Nullable Long value) {
            pullRequestId = value;

            return self();
        }

        @Nonnull
        @Override
        public Builder whitespace(@Nonnull DiffWhitespace value) { // for binary compatibility
            return super.whitespace(value);
        }

        /**
         * Whether to include comments associated to the diff in the response to the request
         * @param value {@code true} if the comments should be included. {@code false} otherwise.
         * @return this builder
         */
        @Nonnull
        public Builder withComments(boolean value) {
            withComments = value;

            return self();
        }

        @Nonnull
        @Override
        protected Builder self() {
            return this;
        }
    }
}
