/*
 * 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.
 */
package org.apache.geode.internal.cache.tier.sockets;

import java.util.OptionalLong;
import java.util.Random;
import java.util.function.Consumer;

/**
 * Generates sequences of unique subject IDs based on a {@code Random} and an initializer function.
 * No two IDs generated within a single sequence have the same value.
 *
 * <p>
 * <strong>Sequences.</strong> Before generating the first ID, the generator prepares a sequence of
 * IDs by applying an initializer function to the given {@code Random}. If the generator exhausts
 * all IDs in its current sequence, it prepares a new sequence before generating the next ID.
 * <p>
 * <strong>Uniqueness.</strong> The IDs generated by this generator are unique only within a single
 * sequence. The IDs generated in one sequence may repeat the IDs generated in earlier sequences.
 * <p>
 * <strong>ID Exhaustion.</strong>After the generator prepares a sequence, it remembers the first
 * ID that it generates in that sequence. If {@code nextLong()} returns that value again, the
 * generator returns an empty {@code OptionalLong} to indicate that it has generated all possible
 * unique IDs in the current sequence. It also arranges to re-initialize the {@code Random} to
 * prepare a new sequence before generating the next ID.
 * <p>
 * If {@code generateId()} returns an empty {@code OptionalLong}, the caller should handle the ID
 * exhaustion before requesting the next ID. For example, the caller might invalidate all uses of
 * previously generated IDs.
 * <p>
 */
public class RandomSubjectIdGenerator implements SubjectIdGenerator {
  private final Random random;
  private final Consumer<Random> initializer;
  private long firstIdInCurrentSequence;
  private boolean mustStartNewSequence = true;

  /**
   * Constructs an ID generator that uses {@code random} to generate IDs and {@code
   * randomInitializer} to set the state of the {@code random}. An initializer should attempt to
   * leave {@code random} in a state distinct from that produced by any other invocation of the
   * initializer.
   */
  public RandomSubjectIdGenerator(Random random, Consumer<Random> randomInitializer) {
    this.random = random;
    initializer = randomInitializer;
  }

  @Override
  public OptionalLong generateId() {
    if (mustStartNewSequence) {
      initializer.accept(random);
    }
    long id = random.nextLong();
    if (id == firstIdInCurrentSequence) { // The current sequence is exhausted.
      mustStartNewSequence = true; // Remember to start a new sequence on the next call.
      return OptionalLong.empty(); // Alert the caller that this sequence is exhausted.
    }
    if (mustStartNewSequence) {
      firstIdInCurrentSequence = id;
      mustStartNewSequence = false;
    }
    return OptionalLong.of(id);
  }
}
