class RetryHandlerBuilder {

    public Func1<Observable<? extends Throwable>, Observable<?>> buildExponentialBackoff() {
        Preconditions.checkState(retryCount > 0, "Retry count not defined");
        Preconditions.checkState(retryDelayMs > 0, "Retry delay not defined");

        return failedAttempts -> failedAttempts
                .doOnNext(error -> onErrorHook.call(error))
                .zipWith(Observable.range(0, retryCount + 1), RetryItem::new)
                .flatMap(retryItem -> {
                    if (retryWhenCondition != null && !retryWhenCondition.get()) {
                        String errorMessage = String.format(
                                "Retry condition not met for %s. Last error: %s. Returning an error to the caller",
                                title, retryItem.cause.getMessage()
                        );
                        return Observable.error(new IOException(errorMessage, retryItem.cause));
                    }

                    if (retryOnThrowableCondition != null && !retryOnThrowableCondition.test(retryItem.cause)) {
                        String errorMessage = String.format(
                                "Retry condition for the last error not met for %s. Last error: %s. Returning an error to the caller",
                                title, retryItem.cause.getMessage()
                        );
                        return Observable.error(new IOException(errorMessage, retryItem.cause));
                    }

                    if (retryItem.retry == retryCount) {
                        String errorMessage = String.format(
                                "Retry limit reached for %s. Last error: %s. Returning an error to the caller",
                                title, retryItem.cause.getMessage()
                        );
                        return Observable.error(new IOException(errorMessage, retryItem.cause));
                    }
                    long expDelay = buildDelay(retryItem.retry);
                    if (retryItem.cause instanceof TimeoutException) {
                        logger.info("Delaying timed-out {} retry by {}[ms]", title, expDelay);
                    } else {
                        logger.info("Delaying failed {} retry by {}[ms]: {}", title, expDelay, ExceptionExt.toMessageChain(retryItem.cause));
                        logger.debug("Exception", retryItem.cause);
                    }
                    return Observable.timer(expDelay, TimeUnit.MILLISECONDS, scheduler);
                });
    }

    private  RetryHandlerBuilder();

}