package org.infinispan.stats.percentiles;

import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Keeps the sample for percentile calculations.
 * <p/>
 * Please check <a href="http://en.wikipedia.org/wiki/Reservoir_sampling for more details">this</a> for more details
 *
 * @author Roberto Palmieri
 * @author Diego Didona
 * @author Pedro Ruivo
 * @since 6.0
 */
public class ReservoirSampler {

   private static final int DEFAULT_NUM_SPOTS = 100;
   private final AtomicInteger index;
   private final Random random;
   private double[] reservoir;

   public ReservoirSampler() {
      this(DEFAULT_NUM_SPOTS);
   }

   public ReservoirSampler(int numSpots) {
      this.reservoir = createArray(numSpots);
      this.index = new AtomicInteger(0);
      random = new Random(System.nanoTime());

   }

   public synchronized final void insertSample(double sample) {
      int i = index.getAndIncrement();
      if (i < reservoir.length)
         reservoir[i] = sample;
      else {
         int randGenerated = random.nextInt(i + 2);//should be nextInt(index+1) but nextInt is exclusive
         if (randGenerated < reservoir.length) {
            reservoir[randGenerated] = sample;
         }
      }
   }

   /**
    * @param k the percentage of observations. Should be a value between 0 and 100 exclusively.
    * @return the percentile value for the k% observations.
    * @throws IllegalArgumentException if k is not between 0 and 100 exclusively.
    */
   public synchronized final double getKPercentile(int k) throws IllegalArgumentException {
      if (k < 0 || k > 100) {
         throw new IllegalArgumentException(k + " should be between 0 and 100 exclusive");
      }
      double[] copy = createArray(reservoir.length);
      System.arraycopy(this.reservoir, 0, copy, 0, reservoir.length);
      Arrays.sort(copy);
      return copy[this.getIndex(k)];
   }

   public synchronized final void reset() {
      this.index.set(0);
      this.reservoir = createArray(reservoir.length);
   }

   private int getIndex(int k) {
      //I solve the proportion k:100=x:NUM_SAMPLE
      //Every percentage is covered by NUM_SAMPLE / 100 buckets; I consider here only the first as representative
      //of a percentage
      return reservoir.length * (k - 1) / 100;
   }

   private double[] createArray(int size) {
      return new double[size];
   }
}
