/*
 * COPIED FROM APACHE LUCENE 4.7.2
 *
 * Git URL: git@github.com:apache/lucene.git, tag: releases/lucene-solr/4.7.2, path: lucene/core/src/java
 *
 * (see https://issues.apache.org/jira/browse/OAK-10786 for details)
 */

package org.apache.lucene.store;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.
 */

import org.apache.lucene.util.ThreadInterruptedException;

/** Abstract base class to rate limit IO.  Typically implementations are
 *  shared across multiple IndexInputs or IndexOutputs (for example
 *  those involved all merging).  Those IndexInputs and
 *  IndexOutputs would call {@link #pause} whenever they
 *  want to read bytes or write bytes. */
public abstract class RateLimiter {

  /**
   * Sets an updated mb per second rate limit.
   */
  public abstract void setMbPerSec(double mbPerSec);
  /**
   * The current mb per second rate limit.
   */
  public abstract double getMbPerSec();
  
  /** Pauses, if necessary, to keep the instantaneous IO
   *  rate at or below the target. 
   *  <p>
   *  Note: the implementation is thread-safe
   *  </p>
   *  @return the pause time in nano seconds 
   * */
  public abstract long pause(long bytes);
  
  /**
   * Simple class to rate limit IO.
   */
  public static class SimpleRateLimiter extends RateLimiter {
    private volatile double mbPerSec;
    private volatile double nsPerByte;
    private volatile long lastNS;

    // TODO: we could also allow eg a sub class to dynamically
    // determine the allowed rate, eg if an app wants to
    // change the allowed rate over time or something

    /** mbPerSec is the MB/sec max IO rate */
    public SimpleRateLimiter(double mbPerSec) {
      setMbPerSec(mbPerSec);
    }

    /**
     * Sets an updated mb per second rate limit.
     */
    @Override
    public void setMbPerSec(double mbPerSec) {
      this.mbPerSec = mbPerSec;
      nsPerByte = 1000000000. / (1024*1024*mbPerSec);
      
    }

    /**
     * The current mb per second rate limit.
     */
    @Override
    public double getMbPerSec() {
      return this.mbPerSec;
    }
    
    /** Pauses, if necessary, to keep the instantaneous IO
     *  rate at or below the target. NOTE: multiple threads
     *  may safely use this, however the implementation is
     *  not perfectly thread safe but likely in practice this
     *  is harmless (just means in some rare cases the rate
     *  might exceed the target).  It's best to call this
     *  with a biggish count, not one byte at a time.
     *  @return the pause time in nano seconds 
     * */
    @Override
    public long pause(long bytes) {
      if (bytes == 1) {
        return 0;
      }

      // TODO: this is purely instantaneous rate; maybe we
      // should also offer decayed recent history one?
      final long targetNS = lastNS = lastNS + ((long) (bytes * nsPerByte));
      final long startNS;
      long curNS = startNS = System.nanoTime();
      if (lastNS < curNS) {
        lastNS = curNS;
      }

      // While loop because Thread.sleep doesn't always sleep
      // enough:
      while(true) {
        final long pauseNS = targetNS - curNS;
        if (pauseNS > 0) {
          try {
            Thread.sleep((int) (pauseNS/1000000), (int) (pauseNS % 1000000));
          } catch (InterruptedException ie) {
            throw new ThreadInterruptedException(ie);
          }
          curNS = System.nanoTime();
          continue;
        }
        break;
      }
      return curNS - startNS;
    }
  }
}
