package com.atlassian.bitbucket.pull;

import com.atlassian.bitbucket.content.ChangeType;
import com.atlassian.bitbucket.content.DiffContentCallback;
import com.atlassian.bitbucket.content.DiffContentFilter;
import com.atlassian.bitbucket.content.DiffWhitespace;
import org.apache.commons.lang3.StringUtils;

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

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

/**
 * Request arguments for {@link PullRequestService#streamDiff(PullRequestDiffRequest, DiffContentCallback)}.
 */
public class PullRequestDiffRequest extends AbstractPullRequestRequest {

    /**
     * Indicates the request should supply the default number of context lines for added and removed lines. The
     * default is configurable, so it may be changed by a system administrator. The shipped setting is 10 lines.
     */
    public static final int DEFAULT_CONTEXT_LINES = -1;

    private final int contextLines;
    private final String path;
    private final DiffContentFilter filter;
    private final String srcPath;
    private final DiffWhitespace whitespace;
    private final boolean withComments;

    private PullRequestDiffRequest(Builder builder) {
        super(builder);

        contextLines = builder.contextLines;
        filter = builder.filter;
        path = builder.path;
        srcPath = builder.srcPath;
        whitespace = builder.whitespace;
        withComments = builder.withComments;
    }

    /**
     * Retrieves the numbers of context lines to show around added and removed lines. If the value is {@code 0} context
     * will be omitted and only added and removed lines will be shown. For positive values, that number of lines will
     * be shown. For negative values, the <i>system-configured default</i> number will be shown.
     *
     * @return the number of context lines that should be included around added and removed lines in the diff
     */
    public int getContextLines() {
        return contextLines;
    }

    /**
     * @return an optional filter for this diff to filter out lines (and their surrounding file/hunk/segment)
     */
    @Nullable
    public DiffContentFilter getFilter() {
        return filter;
    }

    /**
     * @return the path within the repository to the file to diff
     */
    @Nullable
    public String getPath() {
        return path;
    }

    /**
     * @return the source path, required for {@link ChangeType#COPY copies} and
     *         {@link ChangeType#MOVE moves/renames} and {@code null} for other types
     */
    @Nullable
    public String getSrcPath() {
        return srcPath;
    }

    /**
     * @return the whitespace settings used in the diff
     */
    @Nonnull
    public DiffWhitespace getWhitespace() {
        return whitespace;
    }

    /**
     * Retrieves a flag indicating whether an explicit number of context lines has been requested. When this method
     * returns {@code true}, {@link #getContextLines()} will return a value greater than or equal to {@code 0}, When
     * it returns {@code false}, {@link #getContextLines()} will return {@link #DEFAULT_CONTEXT_LINES}.
     *
     * @return {@code true} if a non-{@link #DEFAULT_CONTEXT_LINES default} number of context lines has been requested;
     *         otherwise, {@code false} to use the default number of lines
     */
    public boolean hasContextLines() {
        return contextLines > DEFAULT_CONTEXT_LINES;
    }

    public boolean isWithComments() {
        return withComments;
    }

    public static class Builder extends AbstractBuilder<Builder> {

        private final String path;

        private int contextLines;
        private DiffContentFilter filter;
        private String srcPath;
        private DiffWhitespace whitespace;
        private boolean withComments;

        public Builder(@Nonnull PullRequestDiffRequest request) {
            super(checkNotNull(request, "request").getRepositoryId(), request.getPullRequestId());

            contextLines = request.getContextLines();
            filter = request.getFilter();
            path = request.getPath();
            srcPath = StringUtils.trimToNull(request.getSrcPath());
            whitespace = checkNotNull(request.getWhitespace(), "request.whitespace");
            withComments = request.isWithComments();
        }

        public Builder(@Nonnull PullRequest pullRequest, @Nullable String path) {
            this(checkNotNull(pullRequest, "pullRequest").getToRef().getRepository().getId(),
                    pullRequest.getId(), path);
        }

        public Builder(int repositoryId, long pullRequestId, @Nullable String path) {
            super(repositoryId, pullRequestId);

            this.path = path;

            contextLines = DEFAULT_CONTEXT_LINES;
            whitespace = DiffWhitespace.SHOW;
            withComments = true;
        }

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

        /**
         * Specifies the number of context lines to include around added/removed lines. {@code 0} and positive values
         * are treated as the number of lines to request. <i>Any negative value</i> is treated as a request for the
         * <i>system-configured default</i> number of lines.
         * <p>
         * When requesting the default number of context lines, it is encouraged to use {@link #DEFAULT_CONTEXT_LINES}
         * for clarity: {@code contextLines(DiffRequest.DEFAULT_CONTEXT_LINES)}.
         *
         * @param value the number of context lines to include around added and removed lines in the diff, which may
         *              be {@link #DEFAULT_CONTEXT_LINES} to use the default number
         * @return {@code this}
         */
        @Nonnull
        public Builder contextLines(int value) {
            contextLines = Math.max(DEFAULT_CONTEXT_LINES, value); //Normalize negative values to DEFAULT_CONTEXT_LINES

            return self();
        }

        @Nonnull
        public Builder filter(@Nullable DiffContentFilter value) {
            filter = value;

            return self();
        }

        @Nonnull
        public Builder srcPath(@Nullable String value) {
            srcPath = StringUtils.trimToNull(value);

            return self();
        }

        @Nonnull
        public Builder whitespace(@Nonnull DiffWhitespace value) {
            whitespace = checkNotNull(value, "whitespace");

            return self();
        }

        /**
         * Specifies whether comments should be provided to the callback when streaming the diff. When requested,
         * comments are {@link DiffContentCallback#offerAnchors(java.util.List) offered by anchor} to allow them
         * to be matched to paths and line numbers in the diff as it is streamed.
         *
         * @param value {@code true} to request comments (and their anchors) be provided to the callback when streaming
         *              the diff (the default); otherwise, {@code false} to omit comments
         * @return {@code this}
         */
        @Nonnull
        public Builder withComments(boolean value) {
            withComments = value;

            return self();
        }

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