package com.atlassian.bitbucket.scm;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;

/**
 * Provides a fluent interface for creating free-form commands using arbitrary {@link #argument(String) arguments}.
 * <p>
 * As this builder is free-form, it is left to the caller to ensure the validity of the command being built. This
 * includes, among other things:
 * <ul>
 *     <li>the arguments provided</li>
 *     <li>the order the arguments are provided in</li>
 *     <li>the environment variables defined</li>
 *     <li>whether an {@link CommandInputHandler input handler} is supported</li>
 * </ul>
 * Implementors are <i>encouraged</i> not to validate the free-form commands, or to validate them only minimally. This
 * allows callers the most flexibility to use the process or processes backing the commands to their fullest. However,
 * the contract for this builder interface does not <i>prohibit</i> such validation.
 * <p>
 * Methods for removing property values have two possible prefixes, where each property will only provide <i>one</i>:
 * <ul>
 *     <li><i>clear</i>: Removes any set value, for properties that are not required</li>
 *     <li><i>default</i>: Replaces any set value with an appropriate default, for properties that are required</li>
 * </ul>
 * <p>
 * <b>About arguments:</b> <i>Each call</i> to a method accepting an argument is expected to be provided a <i>single
 * argument</i>. Providing multiple arguments separated by a space will cause the executed command to <i>fail</i>.
 * Consider the following example:
 * <pre><code>
 *     CommandBuilder&lt;Void&gt; builder = ...;
 *     builder.command("someCommand").argument("a b").build(someOutputHandler).call();
 * </code></pre>
 * The argument array for the executed command will contain a <i>single entry</i>, {@code "a b"}, not two entries
 * {@code "a"} and {@code "b"}. This is likely to result in {@link CommandUsageException
 * usage exceptions}. This applies to:
 * <ul>
 *     <li>{@link #argument(String)}</li>
 *     <li>{@link #argumentAfter(String, String)}</li>
 *     <li>{@link #argumentAt(int, String)}</li>
 *     <li>{@link #argumentBefore(String, String)}</li>
 * </ul>
 * <p>
 * <b>Warning:</b> Command builders are <i>not thread-safe</i>. Each builder should be used on a single thread. If
 * multiple threads require builders, each thread should construct its own. <u>Chained builders returned from any
 * method on builder interfaces are attached to their originating build and <i>share</i> threading semantics.</u>
 * This means calling builder methods and passing the returned objects to different threads is also not supported.
 */
@NotThreadSafe
public interface CommandBuilder<B extends CommandBuilder<B>> extends CommandBuilderSupport<B> {

    /**
     * Appends the provided argument.
     * <p>
     * Note: Arguments must be provided <i>one per invocation</i>. There is no need to quote strings which include
     * spaces; spaces in arguments <i>are not interpreted</i>. They are passed <i>as-is</i> to the command.
     *
     * @param argument the argument to append to the command
     * @return {@code this}
     * @throws IllegalArgumentException if the provided {@code argument} is empty or blank
     * @throws NullPointerException if the provided {@code argument} is {@code null}
     */
    @Nonnull
    B argument(String argument);

    /**
     * Inserts the provided argument immediately after the specified anchor, if the anchor can be found. Otherwise, the
     * argument is appended at the end.
     * <p>
     * Note: Arguments must be provided <i>one per invocation</i>. There is no need to quote strings which include
     * spaces; spaces in arguments <i>are not interpreted</i>. They are passed <i>as-is</i> to the command.
     *
     * @param anchor   the argument serving as the anchor for the new argument
     * @param argument the argument to add immediately after the specified anchor
     * @return {@code this}
     * @throws IllegalArgumentException if the provided {@code argument} is empty or blank
     * @throws NullPointerException if the provided {@code argument} is {@code null}
     */
    @Nonnull
    B argumentAfter(String anchor, String argument);

    /**
     * Inserts the provided argument at the specified index.
     * <p>
     * Note: Arguments must be provided <i>one per invocation</i>. There is no need to quote strings which include
     * spaces; spaces in arguments <i>are not interpreted</i>. They are passed <i>as-is</i> to the command.
     *
     * @param index    the index at which the argument should be inserted
     * @param argument the argument to insert at the specified index
     * @return {@code this}
     * @throws IllegalArgumentException if the provided {@code argument} is empty or blank
     * @throws IndexOutOfBoundsException if the provided {@code index} is out of bounds
     * @throws NullPointerException if the provided {@code argument} is {@code null}
     */
    @Nonnull
    B argumentAt(int index, String argument);

    /**
     * Inserts the provided argument immediately before the specified anchor, if the anchor can be found. Otherwise, the
     * argument is appended at the end.
     * <p>
     * Note: Arguments must be provided <i>one per invocation</i>. There is no need to quote strings which include
     * spaces; spaces in arguments <i>are not interpreted</i>. They are passed <i>as-is</i> to the command.
     *
     * @param anchor   the argument serving as the anchor for the new argument
     * @param argument the argument to add immediately before the specified anchor
     * @return {@code this}
     * @throws IllegalArgumentException if the provided {@code argument} is empty or blank
     * @throws NullPointerException if the provided {@code argument} is {@code null}
     */
    @Nonnull
    B argumentBefore(String anchor, String argument);

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

    /**
     * Clears the {@link CommandInputHandler input handler}, if one has been set. Without an input handler, no data
     * will be provided on the command's input stream.
     * <p>
     * Some commands always read data from their input stream, rather than, or in addition to, considering arguments
     * provided on the command line. For such commands, the caller must provide an explicit
     * {@link #inputHandler(CommandInputHandler) input handler}; there is no automatic default.
     *
     * @return {@code this}
     */
    @Nonnull
    B clearInputHandler();

    /**
     * Applies the default {@link CommandErrorHandler error handler}, which will collect any output written to the
     * error stream and provide that output to the {@link CommandExitHandler exit handler}.
     *
     * @return {@code this}
     */
    @Nonnull
    B defaultErrorHandler();

    /**
     * Sets the {@link CommandErrorHandler error handler} which will be used to process any output written to the
     * error stream as the command runs.
     * <p>
     * When using a custom error handler, the {@link CommandExitHandler exit handler} will <i>not</i> receive error
     * stream output; it is assumed that the error handler will have received and processed the output already. If
     * the error handler throws an exception in response to error stream output, the exit handler will receive that
     * exception.
     *
     * @param value the handler to receive and process the command's error stream
     * @return {@code this}
     * @throws NullPointerException if the provided {@code value} is {@code null}
     */
    @Nonnull
    B errorHandler(@Nonnull CommandErrorHandler value);

    /**
     * Sets the {@link CommandInputHandler input handler} which will be used to feed data to the command's input
     * stream.
     * <p>
     * Some commands require specific arguments to trigger processing data provided via an input stream. For such
     * commands, the caller must also provide those arguments in addition to setting the input handler; the builder
     * <i>does not</i> add them automatically.
     *
     * @param value the handler to receive and process the command's input stream
     * @return {@code this}
     * @throws NullPointerException if the provided {@code value} is {@code null}
     */
    @Nonnull
    B inputHandler(@Nonnull CommandInputHandler value);
}
