/*
 * Licensed to Crate under one or more contributor license agreements.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.  Crate licenses this file
 * to you under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * However, if you have executed another commercial license agreement
 * with Crate these terms will supersede the license and you may use the
 * software solely pursuant to the terms of the relevant commercial
 * agreement.
 */

package io.crate.action;

import org.elasticsearch.action.bulk.BackoffPolicy;
import org.elasticsearch.common.unit.TimeValue;

import java.util.Iterator;
import java.util.NoSuchElementException;


public class LimitedExponentialBackoff extends BackoffPolicy {

    private final int startValue;
    private final int maxIterations;
    private final int limit;

    public LimitedExponentialBackoff(int startValue, int maxIterations, int limit) {
        assert startValue >= 0 : "startValue should be >= 0";
        assert maxIterations > 0 : "maxIterations should be > 0";
        assert limit > 0 : "limit should be > 0";
        this.startValue = startValue;
        this.maxIterations = maxIterations;
        this.limit = limit;
    }

    public static BackoffPolicy limitedExponential(int limit) {
        return new LimitedExponentialBackoff(0, Integer.MAX_VALUE, limit);
    }

    private static class LimitedExponentialBackoffIterator implements Iterator<TimeValue> {

        private static final float FACTOR = 1.8f;

        private final int startValue;
        private final int maxIterations;
        private final int limit;
        private int currentIterations;

        private LimitedExponentialBackoffIterator(int startValue, int maxIterations, int limit) {
            this.startValue = startValue;
            this.maxIterations = maxIterations;
            this.limit = limit;
        }

        private int calculate(int iteration) {
            return Math.min(limit, (int) Math.pow(iteration / Math.E, FACTOR));
        }

        @Override
        public boolean hasNext() {
            return currentIterations < maxIterations;
        }

        @Override
        public TimeValue next() {
            if (!hasNext()) {
                throw new NoSuchElementException(
                    "Reached maximum amount of backoff iterations. Only " + maxIterations + " iterations allowed.");
            }
            int result = startValue + calculate(currentIterations);
            currentIterations++;
            return TimeValue.timeValueMillis(result);
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("remove is not supported for " +
                                                    LimitedExponentialBackoff.class.getSimpleName());
        }

    }

    @Override
    public Iterator<TimeValue> iterator() {
        return new LimitedExponentialBackoffIterator(startValue, maxIterations, limit);
    }
}

@@ -1271,34 +1271,38 @@
 e final int 
-startValue
+firstDelayInMS
 ;%0A    privat
@@ -1350,21 +1350,28 @@
 nal int 
-limit
+maxDelayInMS
 ;%0A%0A    p
@@ -1402,34 +1402,38 @@
 Backoff(int 
-startValue
+firstDelayInMS
 , int maxIte
@@ -1437,37 +1437,44 @@
 Iterations, int 
-limit
+maxDelayInMS
 ) %7B%0A        asse
@@ -1480,37 +1480,45 @@
 ert 
-startValue %3E= 0 : %22startValue
+firstDelayInMS %3E= 0 : %22firstDelayInMS
  sho
@@ -1616,159 +1616,380 @@
 ert 
-limit %3E 0 : %22limit should be %3E 0%22;%0A        this.startValue = startValue;%0A        this.maxIterations = maxIterations;%0A        this.limit = limit;%0A    %7D%0A
+maxDelayInMS %3E 0 : %22maxDelayInMS should be %3E 0%22;%0A        this.firstDelayInMS = firstDelayInMS;%0A        this.maxIterations = maxIterations;%0A        this.maxDelayInMS = maxDelayInMS;%0A    %7D%0A%0A    /**%0A     * Creates a BackoffPolicy which will increase the delay each time it's used until %7B@code maxDelayInMS%7D%0A     * is reached, at that point the delay stops increasing.%0A     */
 %0A   
@@ -2036,29 +2036,36 @@
 nential(int 
-limit
+maxDelayInMS
 ) %7B%0A        
@@ -2101,16 +2101,17 @@
 Backoff(
+1
 0, Integ
@@ -2124,21 +2124,28 @@
 _VALUE, 
-limit
+maxDelayInMS
 );%0A    %7D
@@ -2316,26 +2316,30 @@
 nal int 
-startValue
+firstDelayInMS
 ;%0A      
@@ -2399,21 +2399,28 @@
 nal int 
-limit
+mayDelayInMS
 ;%0A      
@@ -2450,16 +2450,20 @@
 erations
+ = 0
 ;%0A%0A     
@@ -2511,26 +2511,30 @@
 tor(int 
-startValue
+firstDelayInMS
 , int ma
@@ -2550,21 +2550,28 @@
 ns, int 
-limit
+maxDelayInMs
 ) %7B%0A    
@@ -2587,31 +2587,39 @@
 his.
-startValue = startValue
+firstDelayInMS = firstDelayInMS
 ;%0A  
@@ -2685,21 +2685,35 @@
 his.
-limit = limit
+mayDelayInMS = maxDelayInMs
 ;%0A  
@@ -2796,21 +2796,28 @@
 ath.min(
-limit
+mayDelayInMS
 , (int) 
@@ -3272,26 +3272,30 @@
 esult = 
-startValue
+firstDelayInMS
  + calcu
@@ -3786,18 +3786,22 @@
 tor(
-startValue
+firstDelayInMS
 , ma
@@ -3813,21 +3813,28 @@
 ations, 
-limit
+maxDelayInMS
 );%0A    %7D

Increase initial delay from 0ms to 10ms in LimitedExponentialBackoff