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

import com.atlassian.bitbucket.mesh.process.ProcessCompletionHandler;
import com.atlassian.bitbucket.mesh.process.ProcessStdioHandler;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcGitTimeouts;
import com.atlassian.bitbucket.mesh.throttle.ThrottleService;
import com.atlassian.bitbucket.mesh.throttle.ThrottledResource;
import com.atlassian.bitbucket.mesh.util.Person;
import com.atlassian.bitbucket.mesh.util.Version;

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

import static java.util.Objects.requireNonNull;

/**
 * Serves as the super-interface describing functionality that is required to be provided by all <i>type-safe</i>
 * {@code git} command builders.
 */
public interface GitProcessBuilderSupport<B extends GitProcessBuilderSupport<B>> {

    /**
     * Adds the specified repository as an alternate, giving the command access to all of the repository's objects.
     *
     * @param alternate the alternate repository to add
     * @return {@code this}
     */
    @Nonnull
    B alternate(@Nonnull Repository alternate);

    /**
     * Adds the specified directory as an alternate objects directory, giving the command access to all of its contents.
     *
     * @param alternateObjectsDir the alternate objects directory
     * @return {@code this}
     */
    @Nonnull
    B alternate(@Nonnull Path alternateObjectsDir);

    /**
     * Adds all of the specified repositories as alternates, giving the command access to all of the repositories'
     * objects.
     *
     * @param alternates a collection of repositories to add as alternates
     * @return {@code this}
     */
    @Nonnull
    B alternates(@Nonnull Iterable<Repository> alternates);

    /**
     * Adds all of the specified repositories as alternates, giving the command access to all of the repositories'
     * objects.
     *
     * @param alternate  an alternate repository to add
     * @param alternates zero or more additional alternate repositories to add
     * @return {@code this}
     */
    @Nonnull
    B alternates(@Nonnull Repository alternate, @Nonnull Repository... alternates);

    /**
     * @param displayName the author's name
     * @param email       the author's e-mail address
     * @return {@code this}
     */
    @Nonnull
    B author(@Nonnull String displayName, @Nonnull String email);

    /**
     * @param author an author for git commit/branch/tag/etc
     * @return {@code this}
     */
    @Nonnull
    default B author(@Nonnull Person author) {
        return author(requireNonNull(author, "author").getName(), author.getEmail());
    }

    /**
     * Builds a {@link GitProcess} which will use the provided {@link ProcessStdioHandler handler} for processing.
     *
     * @param stdioHandler the process handler
     * @param <T>          the type of object the handler will create from the process's output
     * @return the {@code git} process
     * @throws NullPointerException if the provided {@code handler} is {@code null}
     */
    @Nonnull
    <T> GitProcess<T> build(@Nonnull ProcessStdioHandler<T> stdioHandler);

    /**
     * Clears any environment variables which have been set.
     *
     * @return {@code this}
     */
    @Nonnull
    B clearEnvironment();

    /**
     * @param displayName the committer's name
     * @param email       the committer's e-mail address
     * @return {@code this}
     */
    @Nonnull
    B committer(@Nonnull String displayName, @Nonnull String email);

    /**
     * @param committer the person to record as the committer
     * @return {@code this}
     */
    @Nonnull
    default B committer(@Nonnull Person committer) {
        return committer(requireNonNull(committer, "committer").getName(), committer.getEmail());
    }

    /**
     * Sets the {@link ProcessCompletionHandler} which should be invoked after the process completes. This
     * handler receives:
     * <ul>
     *     <li>a flag indicating whether the process was canceled</li>
     *     <li>the exit code from the process</li>
     *     <li>any {@code Throwable} thrown during processing</li>
     *     <li>any buffered {@code stderr} (if the {@link ProcessStdioHandler} didn't process it)</li>
     * </ul>
     * Whether the process executes successfully or fails in some way (either at the Os level or in processing
     * performed by handlers attached to it), the completion handler is <i>guaranteed</i> to be invoked.
     *
     * @param value the handler which should be invoked when the process completes
     * @return {@code this}
     * @throws NullPointerException if the provided {@code value} is {@code null}
     */
    @Nonnull
    B completionHandler(@Nonnull ProcessCompletionHandler value);

    /**
     * Applies the default {@link ProcessCompletionHandler}. The default completion handler used is based on the
     * builder in play; each builder is free to define its own default completion handler.
     * <p>
     * Note: If an explicit {@link #completionHandler completion handler is set}, it will override the default.
     * If the {@link ProcessStdioHandler} provided when the process is {@link #build built} implements
     * {@link ProcessCompletionHandler}, that is treated as an explicit completion handler and likewise overrides
     * the default--or any explicitly set completion handler. It is not possible to build a process without a
     * completion handler.
     *
     * @return {@code this}
     */
    @Nonnull
    B defaultCompletionHandler();

    /**
     * Applies the default working directory, which is the working directory for the current Java process. This may be
     * useful for commands that <i>create</i> directories, such as SCM clone commands.
     *
     * @return {@code this}
     */
    @Nonnull
    B defaultWorkDir();

    /**
     * @return the {@link Repository} this git process targets, if any
     */
    @Nullable
    Repository getRepository();

    /**
     * @param version the version to compare the current git version to
     * @return {@code true} if the current version of the current git binary is equal to or higher than the provided
     *         {@code version}
     */
    boolean isGitVersionAtLeast(@Nonnull Version version);

    /**
     * Removes the environment variable with the provided {@code name} if it has been set
     *
     * @param name the name of the environment variable to clear
     * @return {@code this}
     * @throws IllegalArgumentException if {@code name} is empty or blank
     * @throws NullPointerException     if {@code name} is {@code null}
     */
    @Nonnull
    B removeEnvironment(@Nonnull String name);

    /**
     * Indicates that the process requires a throttling ticket for the duration of its execution. When configured,
     * the ticket will be {@link ThrottleService#acquireTicket(ThrottledResource) acquired} before the process is
     * started and released when the process completes.
     * <p>
     * If {@code resource} is {@code null} the throttling configuration is cleared.
     *
     * @param resource the resource for which the ticket should be acquired
     * @return {@code this}
     */
    @Nonnull
    B throttled(@Nullable ThrottledResource resource);

    /**
     * A convenience shorthand for {@link #withConfiguration(String, boolean) withConfiguration(key, true)}. This
     * emulates the behavior of {@code git -c key}, where no {@code =value} is specified.
     *
     * @param key the configuration key to set, with sub-keys separated by dots (e.g. {@code "gc.auto"}
     * @return {@code this}
     */
    @Nonnull
    B withConfiguration(@Nonnull String key);

    /**
     * Configures the specified key to either {@code true} or {@code false}.
     *
     * @param key   the configuration key to set, with sub-keys separated by dots (e.g. {@code "gc.auto"}
     * @param value {@code true} or {@code false}
     * @return {@code this}
     */
    @Nonnull
    B withConfiguration(@Nonnull String key, boolean value);

    /**
     * Configures the specified key to the provided numeric value.
     *
     * @param key   the configuration key to set, with sub-keys separated by dots (e.g. {@code "gc.auto"}
     * @param value the numeric value to set, which will be converted to a {@code String} for the command line
     * @return {@code this}
     */
    @Nonnull
    B withConfiguration(@Nonnull String key, long value);

    /**
     * Configures the specified key with the provided value. If the provided value is {@code null} or {@code ""}
     * (empty), the key will be set to the empty string. This emulates the behavior of {@code git config -c key=},
     * where no {@code value} is provided after the {@code =} sign.
     * <p>
     * Unlike {@link GitProcessBuilder#config git config}, it is not possible to explicitly declare the type of
     * value being set when using {@code git -c}. The values provided here need to be coercible to whatever type
     * {@code git} expects or the command will fail.
     * <p>
     * {@link #withConfiguration(String, boolean)} and {@link #withConfiguration(String, long)} are both convenience
     * wrappers around this method which will perform the {@code String} conversion. To the {@link #build command}
     * there is no difference between providing values to those methods or this one.
     * <p>
     * Note that the behavior of {@link #withConfiguration(String)} is <i>not</i> possible using this method. To use its
     * implicit {@code true} semantics, you must call that method.
     *
     * @param key   the configuration key to set, with sub-keys separated by dots (e.g. {@code "gc.auto"}
     * @param value the value to set, which may be {@code null} or {@code ""} (empty)
     * @return {@code this}
     */
    @Nonnull
    B withConfiguration(@Nonnull String key, @Nullable String value);

    /**
     * Sets a deadline by which the process must have terminated. If the deadline expires earlier than the
     * {@link #withExecutionInterval(Duration) execution interval} would allow, the maximum execution interval will
     * be reduced.
     *
     * @param value the instant at which the process must have terminated
     * @return {@code this}
     */
    @Nonnull
    B withDeadline(@Nullable Instant value);

    /**
     * Adds an environment variable with the specified {@code name} and {@code value}. Both parameters are required,
     * and an exception will be thrown if either is not provided.
     *
     * @param name  the name of the environment variable to set
     * @param value the value to set for the environment variable
     * @return {@code this}
     * @throws IllegalArgumentException if {@code name} or {@code value} is empty or blank
     * @throws NullPointerException     if {@code name} or {@code value} is {@code null}
     */
    @Nonnull
    B withEnvironment(@Nonnull String name, @Nonnull String value);

    /**
     * Sets the maximum interval the process is allowed to run. The process will be aborted automatically
     * if it hasn't completed in this time <i>even if it is still producing output or consuming input</i>.
     *
     * @param value the maximum interval the process is allowed to run
     * @return {@code this}
     */
    @Nonnull
    B withExecutionInterval(@Nonnull Duration value);

    /**
     * Sets the maximum interval the process is allowed to run without <i>either</i> producing output or
     * consuming input. Output may be produced on either {@code stdout} or {@code stderr}.
     *
     * @param value the maximum interval the process is allowed to be idle
     * @return {@code this}
     */
    @Nonnull
    B withIdleInterval(@Nonnull Duration value);

    /**
     * Sets the {@link #withExecutionInterval execution} and {@link #withIdleInterval idle} intervals to the specified
     * duration. This is useful for processes that do not produce any output until the desired task is complete.
     * Setting the idle and execution intervals to the same value effectively disables applying an idle timeout.
     *
     * @param duration the maximum duration the process is allowed to run or be idle
     * @return {@code this}
     */
    @Nonnull
    B withTimeout(@Nonnull Duration duration);

    /**
     * Sets the {@link #withExecutionInterval execution} and {@link #withIdleInterval idle} intervals to the specified
     * timeouts. This is useful for processes that do not produce any output until the desired task is complete.
     * Setting the idle and execution intervals to the same value effectively disables applying an idle timeout.
     *
     * @param timeouts the timeouts specified on an RPC request
     * @return {@code this}
     */
    @Nonnull
    B withTimeouts(@Nullable RpcGitTimeouts timeouts);

    /**
     * Sets the working directory which should be used when the command is run.
     *
     * @param value the desired working directory
     * @return {@code this}
     * @throws IllegalArgumentException if {@code value} references a nonexistent path, or a file
     * @throws NullPointerException     if the provided {@code value} is {@code null}
     */
    @Nonnull
    B workDir(@Nonnull Path value);
}
