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

import com.atlassian.bitbucket.scm.Command;
import com.atlassian.bitbucket.util.BuilderSupport;

import javax.annotation.Nonnull;

import static java.util.Objects.requireNonNull;

/**
 * A base class for constructing request types which include {@link #getExecutionTimeout() execution} and
 * {@link #getIdleTimeout() idle} timeouts to apply to the commands run to service them.
 * <p>
 * Unlike most request objects, the timeout values provided by this base class (and therefore inherited by
 * all derived types) are <i>mutable</i>. This is something of a compromise, allowing the SCM to apply its
 * own default timeouts while still allowing {@link Command#call callers} to specify more tailored timeouts
 * where appropriate.
 *
 * @since 2.9
 */
public abstract class AbstractTimedRequest {

    private long executionTimeout;
    private long idleTimeout;

    protected AbstractTimedRequest(AbstractBuilder<?, ?> builder) {
        executionTimeout = builder.executionTimeout;
        idleTimeout = builder.idleTimeout;
    }

    /**
     * Applies timeouts to the provided {@link Command command} and then {@link Command#call() calls} it.
     *
     * @param command the command to configure and call
     * @return the output of the {@link Command#call() called} command
     */
    public <T> T configureAndCall(Command<T> command) {
        command.setExecutionTimeout(executionTimeout);
        command.setIdleTimeout(idleTimeout);

        return command.call();
    }

    /**
     * @return the configured execution timeout to apply to commands run to service this request
     * @since 4.6
     */
    public long getExecutionTimeout() {
        return executionTimeout;
    }

    /**
     * @return the configured idle timeout to apply to commands run to service this request
     * @since 4.6
     */
    public long getIdleTimeout() {
        return idleTimeout;
    }

    /**
     * @param executionTimeout the execution timeout to apply to commands run to service this request
     * @since 4.6
     */
    public void setExecutionTimeout(long executionTimeout) {
        this.executionTimeout = executionTimeout;
    }

    /**
     * @param idleTimeout the idle timeout to apply to commands run to service this request
     * @since 4.6
     */
    public void setIdleTimeout(long idleTimeout) {
        this.idleTimeout = idleTimeout;
    }

    public abstract static class AbstractBuilder<B extends AbstractBuilder<B, R>, R extends AbstractTimedRequest>
            extends BuilderSupport {

        private long executionTimeout;
        private long idleTimeout;

        protected AbstractBuilder() {
        }

        protected AbstractBuilder(@Nonnull AbstractTimedRequest request) {
            executionTimeout = requireNonNull(request, "request").getExecutionTimeout();
            idleTimeout = request.getIdleTimeout();
        }

        @Nonnull
        public abstract R build();

        /**
         * Sets the execution timeout.
         * <p>
         * Timeouts set via the builder should be considered <i>default values</i>. After the request is
         * {@link #build built}, its timeouts can be updated.
         *
         * @param value the desired execution timeout
         * @return {@code this}
         * @since 4.6
         */
        @Nonnull
        public B executionTimeout(long value) {
            executionTimeout = value;

            return self();
        }

        /**
         * Sets the idle timeout.
         * <p>
         * Timeouts set via the builder should be considered <i>default values</i>. After the request is
         * {@link #build built}, its timeouts can be updated.
         *
         * @param value the desired idle timeout
         * @return {@code this}
         * @since 4.6
         */
        @Nonnull
        public B idleTimeout(long value) {
            idleTimeout = value;

            return self();
        }

        /**
         * Sets both the {@link #executionTimeout} and {@link #idleTimeout} to the same value.
         * <p>
         * Timeouts set via the builder should be considered <i>default values</i>. After the request is
         * {@link #build built}, its timeouts can be updated.
         *
         * @param value the timeout to use for both execution and idle
         * @return {@code this}
         */
        @Nonnull
        public B timeout(long value) {
            executionTimeout = idleTimeout = value;

            return self();
        }

        @Nonnull
        protected abstract B self();
    }
}
