// Deterministic shuffled list iterator using an XORSHIFT RNG
// Each step is O(1), iterator is OSPACE(1)
// This iterator doesn't have a seed parameter and is completely
// deterministic with respect to a given list size.
// If you want to pass a seed, use WeightlessShuffledIteratorWithSeed.
// Performance: Takes between 5 and 18 ns per nextIndex() on a laptop
//              and between 2 and 11 ns on a desktop CPU.
//              (these numbers are actually for WeightlessShuffledIteratorWithSeed,
//              WeightlessShuffledIterator itself is a tiny bit faster.)
sclass WeightlessShuffledIterator extends ItIt is IndexIterator {
  final L list;
  final int n;
  final int bits, cycleLength, a, b, c;
  
  int i; // step counter
  
  // now doing the TripletLSFR stuff directly in here
  int value;
  
  // initialize only with a length (i.e. list containing 0, 1, ...)
  // if you are going to be calling nextIndex() instead of next()
  *(int n) {
    this((L) iotaZeroList(n));
  }
  
  // initialize with a list to shuffle
  *(L *list) {
    n = l(list);
    if (n == 0) ret with a = b = c = bits = cycleLength = 0;
    bits = numberOfBitsNeededToRepresentNOptions(n+1);
    var lsfr = new TripletLSFR(bits);
    meta-for a also as b, c, value, cycleLength { a = lsfr.a; }
  }
  
  public bool hasNext() { ret i < n; }
  
  public A next() {
    ret list.get(nextIndex());
  }
  
  // you can call nextIndex() without having checked hasNext(),
  // then it will just return -1 when the iteration is complete.
  public int nextIndex() {
    if (i >= n) ret -1;
    ++i;
    
    int raw;
    do {
      int x = value;
      x ^= (x << a) & cycleLength;
      x ^= x >>> b;
      x ^= (x << c) & cycleLength;
      value = x;
      raw = postProcessLSFRValue(x-1);
    } while (raw >= n);
    
    ret raw;
  };
  
  int bits() { ret bits; }
  
  // overridable in subclasses
  int postProcessLSFRValue(int i) { ret i; }
}