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

import com.atlassian.bitbucket.mesh.git.GitProcessCompletionHandler;
import com.atlassian.bitbucket.mesh.process.ProcessCompletionHandler;
import com.atlassian.bitbucket.mesh.process.ProcessStdioHandler;
import com.atlassian.bitbucket.mesh.process.handler.LoggingStdioHandler;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcGitTimeouts;
import com.atlassian.bitbucket.mesh.throttle.ThrottledResource;
import com.atlassian.bitbucket.mesh.util.Version;
import com.google.common.collect.Iterables;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

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

    protected final GitProcessBuilder builder;

    private boolean useDefaultCompletionHandler;

    protected AbstractGitProcessBuilder(GitProcessBuilder builder) {
        this.builder = builder;

        useDefaultCompletionHandler = true;
    }

    @Nonnull
    @Override
    public B alternate(@Nonnull Repository alternate) {
        builder.alternate(alternate);

        return self();
    }

    @Nonnull
    @Override
    public B alternate(@Nonnull Path alternateObjectsDir) {
        builder.alternate(alternateObjectsDir);

        return self();
    }

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

        return self();
    }

    @Nonnull
    @Override
    public B alternates(@Nonnull Repository alternate, @Nonnull Repository... alternates) {
        builder.alternates(alternate, alternates);

        return self();
    }

    /**
     * @param displayName the author's name
     * @param email       the author's e-mail address
     * @return {@link #self() this}
     */
    @Nonnull
    @Override
    public B author(@Nonnull String displayName, @Nonnull String email) {
        builder.author(displayName, email);

        return self();
    }

    @Nonnull
    @Override
    public <T> GitProcess<T> build(@Nonnull ProcessStdioHandler<T> stdioHandler) {
        requireNonNull(stdioHandler, "stdioHandler");

        //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();

        if (useDefaultCompletionHandler && !(stdioHandler instanceof ProcessCompletionHandler)) {
            //If the default completion handler should be used, and the ProcessStdioHandler is not a completion
            //handler, create the default handler and set it on the builder
            builder.completionHandler(createDefaultCompletionHandler());
        }

        return builder.build(stdioHandler);
    }

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

        return self();
    }

    /**
     * @param displayName the committer's name
     * @param email       the committer's e-mail address
     * @return {@link #self() this}
     */
    @Nonnull
    @Override
    public B committer(@Nonnull String displayName, @Nonnull String email) {
        builder.committer(displayName, email);

        return self();
    }

    @Nonnull
    @Override
    public B completionHandler(@Nonnull ProcessCompletionHandler value) {
        useDefaultCompletionHandler = false;
        builder.completionHandler(value);

        return self();
    }

    @Nonnull
    @Override
    public B defaultCompletionHandler() {
        useDefaultCompletionHandler = true;
        builder.defaultCompletionHandler();

        return self();
    }

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

        return self();
    }

    @Override
    public Repository getRepository() {
        return builder.getRepository();
    }

    @Override
    public boolean isGitVersionAtLeast(@Nonnull Version version) {
        return builder.isGitVersionAtLeast(version);
    }

    @Nonnull
    @Override
    public B removeEnvironment(@Nonnull String name) {
        builder.removeEnvironment(name);

        return self();
    }

    @Override
    public B throttled(ThrottledResource resource) {
        builder.throttled(resource);

        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 withDeadline(Instant value) {
        builder.withDeadline(value);

        return self();
    }

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

        return self();
    }

    @Nonnull
    @Override
    public B withExecutionInterval(@Nonnull Duration value) {
        builder.withExecutionInterval(value);

        return self();
    }

    @Nonnull
    @Override
    public B withIdleInterval(@Nonnull Duration value) {
        builder.withIdleInterval(value);

        return self();
    }

    @Nonnull
    @Override
    public B withTimeout(@Nonnull Duration duration) {
        builder.withTimeout(duration);

        return self();
    }

    @Nonnull
    @Override
    public B withTimeouts(RpcGitTimeouts timeouts) {
        builder.withTimeouts(timeouts);

        return self();
    }

    @Nonnull
    @Override
    public B workDir(@Nonnull Path value) {
        builder.workDir(value);

        return 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));
        }
    }

    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 process which uses a {@link LoggingStdioHandler} to log 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 GitProcess&lt;Void&gt; build()}
     * method which, when implemented, delegates to this method.
     *
     * @return the built process
     */
    protected GitProcess<Void> buildWithLoggingHandler() {
        return build(new LoggingStdioHandler());
    }

    /**
     * Creates the default {@link ProcessCompletionHandler} for use with this builder's process. If subclasses don't
     * override this, {@link GitProcessCompletionHandler} will be used.
     *
     * @return the process's default completion handler
     */
    protected ProcessCompletionHandler createDefaultCompletionHandler() {
        return new GitProcessCompletionHandler();
    }

    /**
     * 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 requireNonBlank(String argument, String name) {
        argument = requireNonNull(argument, name).trim();
        checkArgument(!argument.isEmpty(), "A non-blank %s is required", StringUtils.defaultString(name, "value"));

        return argument;
    }

    @Nonnull
    protected abstract B self();
}
