package ru.yandex.qe.http.retries;

import java.io.IOException;
import java.util.function.Supplier;

import com.google.common.base.Preconditions;
import org.apache.http.HttpResponse;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Established by terry
 * on 26.01.16.
 */
public class NTimesBackoffRetryStrategy implements RetryStrategy {
    private static final Logger LOG = LoggerFactory.getLogger(NTimesBackoffRetryStrategy.class);

    protected final ServiceUnavailableRetryStrategy unavailableRetryStrategy;
    protected final int attemptCount;
    private final BackoffStrategy backoffStrategy;

    public NTimesBackoffRetryStrategy(int attemptCount, BackoffStrategy backoffStrategy) {
        this(attemptCount, backoffStrategy, new DefaultServiceUnavailableRetryStrategy(attemptCount, 1000));
    }

    public NTimesBackoffRetryStrategy(int attemptCount, BackoffStrategy backoffStrategy,
                                      ServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy) {
        this.backoffStrategy = backoffStrategy;
        Preconditions.checkArgument(attemptCount > 0, "attempt must be positive: %s", attemptCount);
        this.attemptCount = attemptCount;
        unavailableRetryStrategy = serviceUnavailableRetryStrategy;
    }

    @Override
    public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
        if (executionCount < attemptCount) {
            sleep(() -> backoffStrategy.retryIntervalMs(exception, executionCount, context));
            return true;
        }
        return false;
    }

    @Override
    public boolean retryRequest(HttpResponse response, int executionCount, HttpContext context) {
        if (unavailableRetryStrategy.retryRequest(response, executionCount, context)) {
            sleep(() -> backoffStrategy.retryIntervalMs(response, executionCount, context));
            return true;
        }
        return false;
    }

    private void sleep(Supplier<Long> sleepTimeMs) {
        try {
            final long millis = sleepTimeMs.get();
            if (millis > 0) {
                Thread.sleep(millis);
            }
        } catch (InterruptedException e) {
            LOG.debug("retry back off was interrupted");
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public long getRetryInterval() {
        return 0; //will sleep in check method
    }
}
