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

import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.CommandExitHandler;
import com.atlassian.bitbucket.scm.CommandOutputHandler;
import com.atlassian.bitbucket.scm.git.command.GitCommand;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderSupport;
import com.atlassian.bitbucket.scm.git.command.GitScmCommandBuilder;
import com.atlassian.bitbucket.scm.git.command.LoggingCommandOutputHandler;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.Person;
import com.google.common.collect.Iterables;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;

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

/**
 * A base class to simplify building classes which implement the {@link GitCommandBuilderSupport} interface.
 */
public abstract class AbstractGitCommandBuilder<B extends GitCommandBuilderSupport<B>>
        implements GitCommandBuilderSupport<B> {

    protected final GitScmCommandBuilder builder;

    protected AbstractGitCommandBuilder(GitScmCommandBuilder builder) {
        this.builder = builder;
    }

    @Nonnull
    @Override
    public B alternates(@Nonnull Iterable<Repository> alternates) {
        builder.alternates(alternates);

        return self();
    }

    @Nonnull
    @Override
    public B author(@Nonnull String displayName, @Nonnull String email) {
        builder.author(displayName, email);

        return self();
    }

    @Nonnull
    @Override
    public B author(@Nonnull Person author) {
        builder.author(author);

        return self();
    }

    @Nonnull
    @Override
    public B author(@Nonnull ApplicationUser author) {
        builder.author(author);

        return self();
    }

    @Nonnull
    @Override
    public <T> GitCommand<T> build(@Nonnull CommandOutputHandler<T> outputHandler) {
        checkNotNull(outputHandler, "outputHandler");

        //Clear out any previously-set arguments. This allows the builder to be reused with slightly different settings,
        //which is very useful for commands like git config.
        builder.clearArguments();
        //Apply the arguments which have been stored on the builder as discrete variables
        applyArguments();

        return builder.build(outputHandler);
    }

    @Nonnull
    @Override
    public B clearEnvironment() {
        builder.clearEnvironment();

        return self();
    }

    @Nonnull
    @Override
    public B committer(@Nonnull String displayName, @Nonnull String email) {
        builder.committer(displayName, email);

        return self();
    }

    @Nonnull
    @Override
    public B committer(@Nonnull ApplicationUser value) {
        builder.committer(value);

        return self();
    }

    @Nonnull
    @Override
    public B defaultExitHandler() {
        builder.defaultExitHandler();

        return self();
    }

    @Nonnull
    @Override
    public B defaultWorkingDirectory() {
        builder.defaultWorkingDirectory();

        return self();
    }

    @Deprecated
    @Nonnull
    @Override
    public B environment(@Nonnull String name, @Nonnull String value) {
        builder.withEnvironment(name, value);

        return self();
    }

    @Nonnull
    @Override
    public B exitHandler(@Nonnull CommandExitHandler value) {
        builder.exitHandler(value);

        return self();
    }

    @Nonnull
    @Override
    public B withConfiguration(@Nonnull String key) {
        builder.withConfiguration(key);

        return self();
    }

    @Nonnull
    @Override
    public B withConfiguration(@Nonnull String key, boolean value) {
        builder.withConfiguration(key, value);

        return self();
    }

    @Nonnull
    @Override
    public B withConfiguration(@Nonnull String key, long value) {
        builder.withConfiguration(key, value);

        return self();
    }

    @Nonnull
    @Override
    public B withConfiguration(@Nonnull String key, @Nullable String value) {
        builder.withConfiguration(key, value);

        return self();
    }

    @Nonnull
    @Override
    public B withEnvironment(@Nonnull String name, @Nonnull String value) {
        builder.withEnvironment(name, value);

        return self();
    }

    @Nonnull
    @Override
    public B workingDirectory(@Nonnull File value) {
        builder.workingDirectory(value);

        return self();
    }

    @Nonnull
    @Override
    public B workingDirectory(@Nonnull String value) {
        builder.workingDirectory(value);

        return self();
    }

    protected abstract void applyArguments();

    /**
     * For commands which are not intended to produce output, or which only produce trivial, unimportant output, this
     * utility method builds a command which uses a {@link LoggingCommandOutputHandler} to log the command's output,
     * if any, upon completion.
     * <p>
     * This method is provided on this base class to simplify derived builders. It is {@code protected} because for
     * most commands, such as {@code git diff}, using this doesn't make sense. As a result, it shouldn't be part of
     * any standard API. Instead, those builders which work with commands that produce trivial output may choose to
     * expose this method themselves. The preferred API for doing so is a {@code Command&lt;Void&gt; build()} method
     * which, when implemented, delegates to this method.
     *
     * @return the built command
     */
    protected GitCommand<Void> buildWithLoggingHandler() {
        LoggingCommandOutputHandler handler = new LoggingCommandOutputHandler();
        GitCommand<Void> command = build(handler);

        return handler.setCommand(command);
    }

    /**
     * Ensures the provided argument is not {@code null}, empty or blank. The returned argument is trimmed to remove
     * both leading and trailing whitespace.
     *
     * @param argument the argument to validate
     * @param name     the name of the argument being validated
     * @return the provided {@code argument}, trimmed
     * @throws IllegalArgumentException if the provided {@code argument} is empty or blank
     * @throws NullPointerException     if the provided {@code argument} is {@code null}
     */
    @Nonnull
    protected String checkNotBlank(String argument, String name) {
        checkNotNull(argument, name);

        argument = argument.trim();
        checkArgument(!argument.isEmpty(), "A non-blank %s is required", StringUtils.defaultString(name, "value"));

        return argument;
    }

    @Nonnull
    protected abstract B self();

    protected static boolean addIfNotBlank(Collection<String> collection, String value) {
        return StringUtils.isNotBlank(value) && collection.add(value);
    }

    protected static void addIfNotBlank(Collection<String> collection, String value, String... values) {
        addIfNotBlank(collection, value);
        if (values != null && values.length > 0) {
            addIfNotBlank(collection, Arrays.asList(values));
        }
    }

    protected static void addIfNotBlank(Collection<String> collection, Iterable<String> values) {
        if (values != null) {
            Iterables.addAll(collection, Iterables.filter(values, StringUtils::isNotBlank));
        }
    }
}
