package com.atlassian.bitbucket.mesh.git;

import com.atlassian.bitbucket.mesh.git.builder.GitProcess;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderSupport;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcGitTimeouts;
import com.atlassian.bitbucket.mesh.util.BuilderSupport;

import javax.annotation.Nonnull;
import java.time.Duration;

import static java.util.Objects.requireNonNull;

/**
 * A base class for constructing parameter types which include {@link #getExecutionTimeout() execution} and
 * {@link #getIdleTimeout() idle} timeouts to apply to processes.
 */
public abstract class AbstractParameters {

    private final long executionTimeout;
    private final long idleTimeout;

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

    /**
     * Applies timeouts to the provided builder.
     *
     * @param builder the builder to configure
     * @param <T> the builder type
     * @return the builder
     */
    public <T extends GitProcessBuilderSupport<T>> T applyTimeouts(@Nonnull T builder) {
        requireNonNull(builder, "builder");

        if (getIdleTimeout() != 0L) {
            builder.withIdleInterval(Duration.ofSeconds(getIdleTimeout()));
        }
        if (getExecutionTimeout() != 0L) {
            builder.withExecutionInterval(Duration.ofSeconds(getExecutionTimeout()));
        }
        return builder;
    }

    /**
     * Applies timeouts to the provided {@link GitProcess process} and then {@link GitProcess#run() runs} it.
     *
     * @param process the process to configure and run
     * @param <T> type of the result returned by the process
     * @return the process's output
     */
    public <T> T configureAndRun(@Nonnull GitProcess<T> process) {
        process.setExecutionInterval(Duration.ofSeconds(executionTimeout));
        process.setIdleInterval(Duration.ofSeconds(idleTimeout));

        return process.run();
    }

    /**
     * @return the configured execution timeout to apply to processes
     */
    public long getExecutionTimeout() {
        return executionTimeout;
    }

    /**
     * @return the configured idle timeout to apply to processes
     */
    public long getIdleTimeout() {
        return idleTimeout;
    }

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

        private long executionTimeout;
        private long idleTimeout;

        protected AbstractBuilder() {
        }

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

        protected AbstractBuilder(@Nonnull RpcGitTimeouts timeouts) {
            executionTimeout = requireNonNull(timeouts, "timeouts").getExecution();
            idleTimeout = timeouts.getIdle();
        }

        @Nonnull
        public abstract R build();

        /**
         * Sets the execution timeout.
         *
         * @param value the desired execution timeout
         * @return {@code this}
         */
        @Nonnull
        public B executionTimeout(long value) {
            executionTimeout = value;

            return self();
        }

        /**
         * Sets the idle timeout.
         *
         * @param value the desired idle timeout
         * @return {@code this}
         */
        @Nonnull
        public B idleTimeout(long value) {
            idleTimeout = value;

            return self();
        }

        /**
         * Sets both the {@link #executionTimeout} and {@link #idleTimeout} to the same value.
         *
         * @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();
        }

        /**
         * Sets the {@link #executionTimeout} and {@link #idleTimeout} from the provided {@link RpcGitTimeouts}.
         *
         * @param value the RPC-provided timeouts to apply
         * @return {@code this}
         */
        @Nonnull
        public B timeouts(@Nonnull RpcGitTimeouts value) {
            executionTimeout = requireNonNull(value, "timeouts").getExecution();
            idleTimeout = value.getIdle();

            return self();
        }

        @Nonnull
        protected abstract B self();
    }
}
