package gp;

import java.util.ArrayList;
import java.util.Random;

/**
 * A collection of static functions concerned with pseudo random numbers.
 * 
 */
public class RandomNumberGenerator extends Random {
  private static Random random;
  private static long randomSeed;
  /**
   *
   */
  static {
    randomSeed=System.currentTimeMillis();
    random=new Random(randomSeed);
  }
  /**
  *
  */
 public static void setRandomSeed(long new_seed){
    //counter++;
   randomSeed=new_seed;
   if (randomSeed == 0) setRandomSeed();
   else random.setSeed(randomSeed);
 }
 
 /**
  * Set the random seed without replacing zero with current system time.
  */
 public static void setRandomSeedStrict(long new_seed){
	 randomSeed=new_seed;
	 random.setSeed(randomSeed);
 }
  /**
   *
   */
  public static void setRandomSeed() {
    randomSeed=System.currentTimeMillis();
    random=new Random(randomSeed);
  }
  /**
   *
   */
  public static void setRandom(Random base_random) {
    random=base_random;
  }
  /**
   *
   */
  public static long getRandomSeed() {
    return randomSeed;
  }
  
  /**
   * Returns 0 or 1 evenly distributed.
   */
  public static int randomInt() {
    return randomInt(0,1);
  }

  /**
  * Returns an evenly distributes int value between zero and
  * upperLim-1.
  * @param upperLim upper exclusive limit of the random int
  */
 public static int randomInt(int upperLim) {
   return randomInt(0,upperLim-1);
 }
 
  /** This method returns a evenly distributed int value.
   * The boundarys are included.
   * @param lo         Lower bound.
   * @param hi         Upper bound.
   * @return int
   */
  public static int randomInt(int lo,int hi) {
	  if (hi<lo) {
		  System.err.println("Invalid boundary values! Returning zero.");
		  return -1;
	  }
      int result = (Math.abs(random.nextInt())%(hi-lo+1))+lo;
      if ((result < lo) || (result > hi)) {
    	  System.err.println("Error in RNG.randomInt!");
    	  result = Math.abs(random.nextInt()%(hi-lo+1))+lo;
      }
      return result;
  }

  /**
   *
   */
  public static long randomLong() {
    return random.nextLong();
  }
  /**
   *
   */
  public static long randomLong(long lo,long hi) {
    return (Math.abs(random.nextLong())%(hi-lo+1))+lo;
  }
  
  /**
   * This method returns a random permutation of n int values
   * @param length        The number of int values
   * @return The permutation [0-length-1]
   */
  public static int[] randomPerm(int length) {
  	ArrayList<Integer> intList = new ArrayList<Integer>(length);
      int[]       result      = new int[length];
      for (int i = 0; i < length; i++) {
          intList.add(new Integer(i));
      }
      for (int i = 0; i < length-1; i++) {
      	int index = randomInt(intList.size());
          result[i] = intList.get(index);
          intList.remove(index);
          
      }
      if (intList.size()>1) System.err.println("Error in randomPerm!");
      result[length-1] = intList.get(0);
      return result;
  }
  
  /**
   *
   */
  public static float randomFloat() {
    return random.nextFloat();
  }
  /**
   *
   */
  public static float randomFloat(float lo,float hi) {
    return (hi-lo)*random.nextFloat()+lo;
  }
  /**
   * A random double value between 0 and 1.
   */
  public static double randomDouble() {
    return random.nextDouble();
  }
  /**
   *
   */
  public static double randomDouble(double lo,double hi) {
    return (hi-lo)*random.nextDouble()+lo;
  }
  
  /**
   * Create a uniform random vector within the given bounds.
   */
  public static double[] randomDoubleArray(double[] lo,double[] hi) {
    double[] xin = new double[lo.length];
    for (int i=0;i<lo.length;i++)
      xin[i] = (hi[i]-lo[i])*random.nextDouble()+lo[i];
    return xin;
  }
  
  /**
   * Create a uniform random vector within the given bounds.
   */
  public static double[] randomDoubleArray(double[][] range) {
	  double[] xin = new double[range.length];
	  for (int i=0;i<xin.length;i++)
		  xin[i] = (range[i][1]-range[i][0])*random.nextDouble()+range[i][0];
	  return xin;
  }

  /**
   * Create a uniform random double vector within the given bounds (inclusive) in every dimension.
   * 
   * @param lower
   * @param upper
   * @param size
   * @return
   */
  public static double[] randomDoubleArray(double lower, double upper, int size) {
	  double[] result = new double[size];
	  for (int i = 0; i < result.length; i++) {
		  result[i] = RandomNumberGenerator.randomDouble(lower, upper);
	  }
	  return result;
  }

  /**
   *
   */
  public static double[] randomDoubleArray(double[] lo,double[] hi,double[] xin) {
     //counter++;
     for (int i=0;i<lo.length;i++)
      xin[i] = (hi[i]-lo[i])*random.nextDouble()+lo[i];
    return xin;
  }
  
  /**
   * Create a uniform random integer vector within the given bounds (inclusive) in every dimension.
   * 
   * @param n
   * @param lower
   * @param upper
   * @return
   */
  public static int[] randomIntArray(int lower, int upper, int size) {
	  int[] result = new int[size];
	  for (int i = 0; i < result.length; i++) {
		  result[i] = RandomNumberGenerator.randomInt(lower, upper);
	  }
	  return result;
  }
  
  /**
   *
   */
  public static boolean randomBoolean() {
     //counter++;
     return (randomInt()==1);
  }
  
  /**
   *
   */
  public static int randomBit() {
     //counter++;
     return randomInt();
  }
  
  /**
   *
   */
  public static boolean flipCoin(double p) {
     //counter++;
     return (randomDouble()<p ? true : false);
  }
  
  /**
   *
   */
  public static float gaussianFloat(float dev) {
     //counter++;
     return (float)random.nextGaussian()*dev;
  }
  
  /**
   * Return a Gaussian double with mean 0 and deviation dev.
   * 
   * @param dev the deviation of the distribution.
   * @return a Gaussian double with mean 0 and given deviation.
   */
  public static double gaussianDouble(double dev) {
     //counter++;
     return random.nextGaussian()*dev;
  }
  /**
   *
   */
  public static float exponentialFloat(float mean) {
     //counter++;
     return (float)(-mean*Math.log(randomDouble()));
  }
  /**
   *
   */
  public static double exponentialDouble(double mean) {
     //counter++;
     return -mean*Math.log(randomDouble());
  }
  
  /**
   * Adds Gaussian noise to a double vector
   * @param v 	the double vector
   * @param dev	the Gaussian deviation
   */
  public static void addNoise(double[] v, double dev) {
	  for (int i=0; i<v.length; i++) {
		  // add noise to the value
		  v[i] += gaussianDouble(dev);
	  }
  } 
}

