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

import com.atlassian.bitbucket.mesh.git.exception.UnsupportedMergeStrategyException;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcMergeStrategy;

import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Optional;

import static java.util.Objects.requireNonNull;
import static java.util.Optional.of;
import static java.util.stream.Collectors.toList;

/**
 * Defines the possible merge strategies for pull requests.
 */
public enum GitMergeStrategy {

    /**
     * Merge the pull request without creating a merge commit if the source commit is fast-forward from the target.
     */
    FF("ff", "--ff"),
    /**
     * Merge the pull request <i>only</i> if the source commit is fast-forward from the target. If a merge commit
     * would be created, fail.
     */
    FF_ONLY("ff-only", "--ff-only"),
    /**
     * Merge the pull request and <i>always</i> create a merge commit, even if the source commit is fast-forward
     * from the target.
     * <p>
     * Note: This is the default merge strategy.
     */
    NO_FF("no-ff", "--no-ff", true),
    /**
     * Rebase the source branch onto the target branch and apply the now-fast-forward commits to the target branch,
     * without producing a new merge commit. This produces a linear, Subversion-like history, without merge commits.
     */
    REBASE_FF_ONLY("rebase-ff-only", "rebase + merge --ff-only"),
    /**
     * Rebase the source branch onto the target branch and <i>merge</i> the now-fast-forward commits to the target
     * branch. The merge commit, while unnecessary, serves as a record of where the changes where introduced. This
     * produces a "railroad tracks"-style history.
     */
    REBASE_NO_FF("rebase-no-ff", "rebase + merge --no-ff"),
    /**
     * Merges the pull request by squashing all of incoming commits into a single commit on the target. This
     * strategy will <i>never</i> create a merge commit.
     */
    SQUASH("squash", "--squash"),
    /**
     * Merges the pull request by squashing all of incoming commits into a single commit on the target <i>only</i>.
     * if the source commit is fast-forward from the target. This strategy will <i>never</i> create a merge commit.
     */
    SQUASH_FF_ONLY("squash-ff-only", "--squash --ff-only");

    private final String descriptionKey;
    private final String flag;
    private final String id;
    private final boolean isDefault;
    private final String nameKey;

    GitMergeStrategy(String id, String flag) {
        this(id, flag, false);
    }

    GitMergeStrategy(String id, String flag, boolean isDefault) {
        this.flag = flag;
        this.id = id;
        this.isDefault = isDefault;

        nameKey = "git.mergestrategy." + id;
        descriptionKey = nameKey + ".description";
    }

    @Nonnull
    public static Optional<GitMergeStrategy> fromId(@Nonnull String id) {
        requireNonNull(id, "id");

        return Arrays.stream(values())
                .filter(strategy -> id.equals(strategy.id))
                .findFirst();
    }

    @Nonnull
    public static GitMergeStrategy fromRpc(@Nonnull RpcMergeStrategy rpc) {
        switch (requireNonNull(rpc, "rpc")) {
            case MERGE_STRATEGY_NO_FF:
                return NO_FF;
            case MERGE_STRATEGY_FF:
                return FF;
            case MERGE_STRATEGY_FF_ONLY:
                return FF_ONLY;
            case MERGE_STRATEGY_REBASE_FF_ONLY:
                return REBASE_FF_ONLY;
            case MERGE_STRATEGY_REBASE_NO_FF:
                return REBASE_NO_FF;
            case MERGE_STRATEGY_SQUASH:
                return SQUASH;
            case MERGE_STRATEGY_SQUASH_FF_ONLY:
                return SQUASH_FF_ONLY;
            default:
                throw new UnsupportedMergeStrategyException(rpc.name(),
                        Arrays.stream(values())
                                .map(GitMergeStrategy::getId)
                                .collect(toList()));
        }
    }

    @Nonnull
    public String getDescriptionKey() {
        return descriptionKey;
    }

    @Nonnull
    public Optional<String> getFlag() {
        return of(flag);
    }

    @Nonnull
    public String getId() {
        return id;
    }

    @Nonnull
    public String getNameKey() {
        return nameKey;
    }

    public boolean isDefault() {
        return isDefault;
    }
}
