package com.atlassian.stash.internal.scm.git.pull;

import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.util.FileUtils;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import java.io.File;
import java.util.Collections;
import java.util.List;

import static com.atlassian.stash.internal.scm.git.InternalGitConstants.*;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * A reusable builder for crafting pull request refs, either dark or public. The builder allows the various pieces,
 * like the {@link PullRequest pull request} {@link PullRequest#getId() ID} and the {@link PullRequestRefType type},
 * to be assembled (and replaced), and then converted <i>either</i> into a {@link #toRef() fully-qualified ref}, or
 * to a {@link #toFile(File) file} as needed.
 *
 * @since 2.9
 */
public class PullRequestRefBuilder {

    private static final String[] PATH_DARK_REFS = new String[] {PATH_STASH_REFS, PATH_PULL_REQUESTS};
    private static final String[] PATH_PUBLIC_REFS = new String[] {PATH_REFS, PATH_PULL_REQUESTS};

    private final boolean dark;

    private Long id;
    private String name;

    private PullRequestRefBuilder(boolean dark) {
        this.dark = dark;
    }

    /**
     * Constructs a new builder for creating dark refs, used for pull request auto-merges. These refs will not be
     * included in ref advertisements; they will only be available locally.
     *
     * @return a builder for constructing dark refs
     */
    @Nonnull
    public static PullRequestRefBuilder darkRef() {
        return new PullRequestRefBuilder(true);
    }

    /**
     * Constructs a new builder for creating public refs, which will be included in ref advertisements, to allow pull
     * request commits to be fetched.
     *
     * @return a builder for constructing public refs
     */
    @Nonnull
    public static PullRequestRefBuilder publicRef() {
        return new PullRequestRefBuilder(false);
    }

    @Nonnull
    public PullRequestRefBuilder id(@Nonnull PullRequest pullRequest) {
        return id(checkNotNull(pullRequest, "pullRequest").getId());
    }

    @Nonnull
    public PullRequestRefBuilder id(@Nonnull Long value) {
        id = checkNotNull(value, "id");

        return this;
    }

    @Nonnull
    public PullRequestRefBuilder name(@Nonnull String value) {
        name = checkNotNull(value, "name");

        return this;
    }

    @Nonnull
    public PullRequestRefBuilder type(@Nonnull PullRequestRefType value) {
        return name(checkNotNull(value, "type").getRefName());
    }

    /**
     * Builds a {@code File} which can be used to access the ref on disk.
     *
     * @param repositoryDir the repository directory, used as the base of the returned {@code File}
     * @return a file for the pull request ref
     */
    @Nonnull
    public File toFile(@Nonnull File repositoryDir) {
        return FileUtils.construct(checkNotNull(repositoryDir, "repositoryDir"), toArray());
    }

    /**
     * Builds a <i>fully-qualified</i> ref from the provided values.
     *
     * @return a fully-qualified pull request ref
     */
    @Nonnull
    public String toRef() {
        return StringUtils.join(toList(), "/");
    }

    /**
     * Appends the pull request's {@link #id(PullRequest) ID} and its version as the {@link #name(String) name}.
     *
     * @param pullRequest the pull request
     * @return {@code this}
     * @since 3.4
     */
    public PullRequestRefBuilder version(@Nonnull PullRequest pullRequest) {
        return id(pullRequest)
                .name("v" + pullRequest.getVersion());
    }

    @Nonnull
    private String[] toArray() {
        List<String> list = toList();

        return list.toArray(new String[list.size()]);
    }

    private List<String> toList() {
        List<String> path = Lists.newArrayListWithCapacity(id == null ? 2 : name == null ? 3 : 4);
        Collections.addAll(path, dark ? PATH_DARK_REFS : PATH_PUBLIC_REFS);
        if (id != null) {
            path.add(id.toString());
            if (name != null) {
                path.add(name);
            }
        }

        return path;
    }
}
