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

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;
import com.atlassian.bitbucket.scm.git.command.merge.GitMerge;
import com.atlassian.bitbucket.scm.git.command.merge.GitMergeBuilderSupport;
import com.atlassian.bitbucket.scm.git.command.merge.GitMergeFastForward;
import com.atlassian.stash.internal.scm.git.command.merge.AbstractGitMergeBuilder;

import java.io.File;

/**
 * @since 2.9
 */
public abstract class AbstractMergeStrategy<B extends GitMergeBuilderSupport<B>> implements MergeStrategy {

    protected final I18nService i18nService;

    private final GitCommandBuilderFactory builderFactory;
    private final GitMergeFastForward fastForward;

    protected AbstractMergeStrategy(GitCommandBuilderFactory builderFactory, I18nService i18nService,
                                    GitMergeFastForward fastForward) {
        this.builderFactory = builderFactory;
        this.fastForward = fastForward;
        this.i18nService = i18nService;
    }

    /**
     * Creates a {@link GitScmCommandBuilder builder} with no associated {@link Repository repository} which will
     * build commands that run in the specified working directory.
     *
     * @param workingDir the working directory for commands built with the returned builder
     * @return a new builder with its working directory set
     */
    protected GitScmCommandBuilder builder(File workingDir) {
        return builderFactory.builder().workingDirectory(workingDir);
    }

    /**
     * Creates a {@link GitMergeBuilderSupport merge builder} which will run in the specified working directory,
     * and applies common flags based on the {@link MergeRequest merge request}.
     *
     * @param workingDir the working directory for the {@code git merge} command
     * @param request    a request describing the merge being performed
     * @return a new merge builder with its working directory and common flags set
     */
    protected B mergeBuilder(File workingDir, MergeRequest request) {
        GitMerge merge = builder(workingDir).merge();

        B builder = chooseMode(merge)
                .author(request.getAuthor())
                .commit(request.getFrom().getLatestCommit())
                .ff(fastForward);
        //--allow-unrelated-histories was introduced in Git 2.9, and is necessary to continue allowing branches
        //which do not share a common ancestor to be merged. Since this has been possible in the product since
        //1.3, the goal is to ensure it continues to work.
        //
        //However, since this flag is not supported on Git 2.8 and older, it shouldn't be added to the builder
        //API; the implementation for it wouldn't work consistently. That means we need to pass the argument
        //directly to the builder
        ((AbstractGitMergeBuilder<?>) builder).allowUnrelatedHistories(true);

        return builder;
    }

    /**
     * Implemented in derived strategies to choose the appropriate mode from the {@link GitMerge merge selector} and
     * return it.
     *
     * @param merge the merge selector, used to create a mode-appropriate builder
     * @return the mode-appropriate builder for this strategy
     */
    protected abstract B chooseMode(GitMerge merge);
}
