import java.util.*;

g g = new g();

final static class g {
  //Loops after 48 bits of random numbers - which are sufficient for most cases. This is to increase performance
  Random rand = new Random();

  //Array shuffler, returns randomized array of arbitrary data-type
  //With this you have fair distribution of results, so you know exactly how long it takes to read the array - exactly as long as if it weren't random - when you read the shuffled array iteratively
  //(compared to reading random indices of a non-shuffled array)
  Object random(Object[] input) {
    Collections.shuffle(Arrays.asList(input));
    return input;
  }

  //random() functions as known from Processing
  float random(float floor, float ceiling) {
    return rand.nextFloat() * (ceiling - floor) + floor;
  }
  float random(float ceiling) {
    return rand.nextFloat() * ceiling;
  }

  //Returns a random integer and should not repeat the same number twice
  int lastResult = 0;
  int random(float floor, float ceiling, boolean norepetition) {
    float nextFloat = rand.nextFloat();
    int result = (int)(nextFloat * (ceiling - floor) + floor);
    if (norepetition) {
      if ((int)floor - (int)ceiling == 0) {
        return result;
      }
      while (lastResult == result) {
        nextFloat = rand.nextFloat();
        result = (int)(nextFloat * (ceiling - floor) + floor);
      }
      lastResult = (int)result;
    }
    return result;
  }
  int random(float ceiling, boolean norepetition) {
    return this.random(0.0f, ceiling, norepetition);
  }

  //Returns a random entry of an array of arbitrary data-type with probabilities
  Object random(Object[] input, float[] probabilities) {
    if (input.length != probabilities.length) {
      println("Probability amount doesn't match amount of objects to select from.");
      //Instead of return(null), runtimeException (IllegalArgumentException) might be better.
      return(null);
    }
    float probabilitySum = 0;
    for (int i = 0; i < probabilities.length; i++) {
      probabilitySum += probabilities[i];
    }
    if (probabilitySum != 1.0) {
      //We could also just throw an Exception here because it's resource hungry if the user doesn't supply probabilities that add up to exactly 1.0
      for (int i = 0; i < probabilities.length; i++) {
        probabilities[i] = map(probabilities[i], 0, probabilitySum, 0, 1);
      }
    }
    //Not an Alias algorithm so potentially slow on long arrays
    //Random r = new Random();
    float r = rand.nextFloat();
    float p = 0.0f;
    for (int i = 0; i < input.length; i++) {
      p += probabilities[i];
      if (r < p) {
        return(input[i]);
      }
    }
    //Should never be reached
    return(null);
  }

  //Returns a random float between 0 and 1, selection weighted by a given probability curve defined by vectors
  float random(float[][] probCurve) {
    //Imagine an x and y coordinate system reaching from (0,0) to (1,1).
    //There's a function curve that must have exactly one value per f(x).
    //This function curve defines the probability y per x.
    //This is how it works:
    //1. Generate 2 random values x & y
    //2. Check if y is in the probability curve field f(x) (beneath f(x))
    //3. If so, x is the random number we're looing for, else, repeat

    //NEEDS to have a y value for x = 0 and a y value for x = 1
    //NEEDS to have the x values in ascending order (in the array)



    boolean foundX = false;
    float x = 0.0;
    float y = 0.0;
    while (!foundX) {
      x = rand.nextFloat();
      y = rand.nextFloat();
      int pos = 0;
      for (int i = 0; i < probCurve.length; i++) {
        if (x <= probCurve[i][0]) {
          pos = i;
          break;
        }
      }
      if (lineIntersect(x, y, x, y+1, probCurve[pos-1][0], probCurve[pos-1][1], probCurve[pos][0], probCurve[pos][1])) {
        foundX = true;
      }
    }
    return x;
  }
  boolean lineIntersect(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
    float uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
    float uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
    if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
      return true;
    }
    return false;
  }
}


void setup() {
  /*
   //A dice rolling twice as many sixes
   int[] rolls = new int[6];
   for (int i = 0; i < 10000; i++) {
   int diceRoll = (int)g.random(new Object[]{(int)g.random(0, 5), 5}, new float[]{(float)5/7, (float)2/7});
   rolls[diceRoll] += 1;
   }
   println(rolls);
   */
}

void draw() {
  /*
   //Prints a random float between -20 and 5 (just the same as Processings random()-function and exactly as fast)
   println(g.random(-20, 5));
   */

  /*
  //Prints an integer between 15 and 20, should never repeat the same number twice in a row
   println(g.random(15,20, true));
   */

  /*
   //An array shuffler for arbitrary data types
   println(g.random(new Integer[]{1, 2, 3, 4, 5}));
   String StringArray[] = {"eins", "zwei", "drei", "vier", "fünf"};
   println(g.random(StringArray));
   Float FloatArray[] = {1.1, 1.2, 1.3, 1.4, 1.5};
   println(g.random(FloatArray));
   */

  /*
  //Prints array entries of arbitrary data type with a given probability in floats (it's recommended to let the probability floats add up to exactly 1.0, if they don't it's way more resource hungry)
   Object[] entries = {"rare", "common", "808"};
   float[] probabilities = {0.1, 0.85, 0.05};
   println(g.random(entries, probabilities));
   */

  /*
  //Prints random floats between 0 and 1 depending on a probability curve.
   //Never between 0 and 0.3. Probability between 0.3 and 0.5 ramps up, and goes down again between 0.5 and 0.6, diminishing to zero at 0.95, where it ramps up again, so values between 0.95 and 1.0 are again highly probable.
   println(g.random(new float[][]{{0.0, 0.0}, {0.3, 0.0}, {0.5, 1.0}, {0.6, 0.3}, {0.95,0.0}, {1.0, 1.0}}));
   */
}
