package com.atlassian.bitbucket.mesh.git.pull;

import com.google.common.collect.ImmutableList;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import static java.util.Optional.ofNullable;

/**
 * The outcome of the {@link BatchPullRequestRescopeAnalyzer analysis} of a {@link PullRequestRescope rescope}
 * operation that describes how a pull request is affected by a change to its from and/or to branch.
 */
public class PullRequestRescopeAnalysisResult {

    private final List<String> addedCommits;
    private final int addedCommitsCount;
    private final String mergeCommit;
    private final PullRequestRescopeOutcome outcome;
    private final List<String> removedCommits;
    private final int removedCommitsCount;
    private final PullRequestRescope rescope;

    private PullRequestRescopeAnalysisResult(Builder builder) {
        addedCommits = builder.addedCommits == null ? Collections.emptyList() : builder.addedCommits.build();
        addedCommitsCount = builder.addedCommitsCount;
        mergeCommit = builder.mergeCommit;
        outcome = builder.outcome;
        removedCommits = builder.removedCommits == null ? Collections.emptyList() : builder.removedCommits.build();
        removedCommitsCount = builder.removedCommitsCount;
        rescope = builder.rescope;
    }

    /**
     * Returns the IDs of commits that were added to the pull request as a result of the {@link #getRescope() rescope}.
     * The list is capped to at most {@link Builder#maxCommitIds}. Use {@link #getAddedCommitsCount()} to get the total
     * number of commits that were added.
     *
     * @return IDs of commits that were added to the pull request
     */
    @Nonnull
    public List<String> getAddedCommits() {
        return addedCommits;
    }

    /**
     * @return the total number of commits that were added to the pull request
     */
    public int getAddedCommitsCount() {
        return addedCommitsCount;
    }

    /**
     * @return the merge commit, if available. Will be {@link Optional#empty() empty} if the pull request was
     *         not merged or if the merge commit could not be determined
     */
    @Nonnull
    public Optional<String> getMergeCommit() {
        return ofNullable(mergeCommit);
    }

    /**
     * @return the outcome of the pull request rescope analysis
     */
    @Nonnull
    public PullRequestRescopeOutcome getOutcome() {
        return outcome;
    }

    @Nonnull
    public PullRequestRescope getRescope() {
        return rescope;
    }

    /**
     * Returns the IDs of commits that were removed from the pull request as a result of the
     * {@link #getRescope() rescope}. The list is capped to at most {@link Builder#maxCommitIds}. Use
     * {@link #getRemovedCommitsCount()} to get the total number of commits that were removed.
     *
     * @return IDs of commits that were removed from the pull request
     */
    @Nonnull
    public List<String> getRemovedCommits() {
        return removedCommits;
    }

    /**
     * @return the total number of commits that were removed from the pull request
     */
    public int getRemovedCommitsCount() {
        return removedCommitsCount;
    }

    public static class Builder {

        private final int maxCommitIds;
        private final PullRequestRescope rescope;

        private ImmutableList.Builder<String> addedCommits;
        private int addedCommitsCount;
        private String mergeCommit;
        private PullRequestRescopeOutcome outcome;
        private ImmutableList.Builder<String> removedCommits;
        private int removedCommitsCount;

        public Builder(PullRequestRescope rescope, int maxCommitIds) {
            this.maxCommitIds = maxCommitIds;
            this.rescope = rescope;

            outcome = PullRequestRescopeOutcome.UNKNOWN;
        }

        /**
         * @param commitId ID of commit that was added to the pull request
         * @return the builder
         */
        @Nonnull
        public Builder addedCommit(@Nonnull String commitId) {
            if (++addedCommitsCount <= maxCommitIds) {
                if (addedCommits == null) {
                    addedCommits = ImmutableList.builder();
                }
                addedCommits.add(commitId);
            }
            return this;
        }

        /**
         * Builds the result
         *
         * @return the result
         */
        @Nonnull
        public PullRequestRescopeAnalysisResult build() {
            return new PullRequestRescopeAnalysisResult(this);
        }

        /**
         * @param value the merge commit
         * @return the builder
         */
        @Nonnull
        public Builder mergeCommit(@Nullable String value) {
            mergeCommit = value;
            return this;
        }

        /**
         * @param value the outcome of the analysis
         * @return the builder
         */
        @Nonnull
        public Builder outcome(@Nullable PullRequestRescopeOutcome value) {
            outcome = value == null ? PullRequestRescopeOutcome.UNKNOWN : value;
            return this;
        }

        /**
         * @param commitId ID of commit that was removed from the pull request
         * @return the builder
         */
        @Nonnull
        public Builder removedCommit(@Nonnull String commitId) {
            if (++removedCommitsCount <= maxCommitIds) {
                if (removedCommits == null) {
                    removedCommits = ImmutableList.builder();
                }
                removedCommits.add(commitId);
            }
            return this;
        }
    }
}
