class SplitGenerator {

  List<List<RingRange>> generateSplits(long totalSplitCount, List<BigInteger> ringTokens) {
    int tokenRangeCount = ringTokens.size();

    List<RingRange> splits = new ArrayList<>();
    for (int i = 0; i < tokenRangeCount; i++) {
      BigInteger start = ringTokens.get(i);
      BigInteger stop = ringTokens.get((i + 1) % tokenRangeCount);

      if (!inRange(start) || !inRange(stop)) {
        throw new RuntimeException(
            String.format("Tokens (%s,%s) not in range of %s", start, stop, partitioner));
      }
      if (start.equals(stop) && tokenRangeCount != 1) {
        throw new RuntimeException(
            String.format("Tokens (%s,%s): two nodes have the same token", start, stop));
      }

      BigInteger rs = stop.subtract(start);
      if (rs.compareTo(BigInteger.ZERO) <= 0) {
        // wrap around case
        rs = rs.add(rangeSize);
      }

      // the below, in essence, does this:
      // splitCount = ceiling((rangeSize / RANGE_SIZE) * totalSplitCount)
      BigInteger[] splitCountAndRemainder =
          rs.multiply(BigInteger.valueOf(totalSplitCount)).divideAndRemainder(rangeSize);

      int splitCount =
          splitCountAndRemainder[0].intValue()
              + (splitCountAndRemainder[1].equals(BigInteger.ZERO) ? 0 : 1);

      LOG.debug("Dividing token range [{},{}) into {} splits", start, stop, splitCount);

      // Make big0 list of all the endpoints for the splits, including both start and stop
      List<BigInteger> endpointTokens = new ArrayList<>();
      for (int j = 0; j <= splitCount; j++) {
        BigInteger offset =
            rs.multiply(BigInteger.valueOf(j)).divide(BigInteger.valueOf(splitCount));
        BigInteger token = start.add(offset);
        if (token.compareTo(rangeMax) > 0) {
          token = token.subtract(rangeSize);
        }
        // Long.MIN_VALUE is not a valid token and has to be silently incremented.
        // See https://issues.apache.org/jira/browse/CASSANDRA-14684
        endpointTokens.add(
            token.equals(BigInteger.valueOf(Long.MIN_VALUE)) ? token.add(BigInteger.ONE) : token);
      }

      // Append the splits between the endpoints
      for (int j = 0; j < splitCount; j++) {
        splits.add(new RingRange(endpointTokens.get(j), endpointTokens.get(j + 1)));
        LOG.debug("Split #{}: [{},{})", j + 1, endpointTokens.get(j), endpointTokens.get(j + 1));
      }
    }

    BigInteger total = BigInteger.ZERO;
    for (RingRange split : splits) {
      BigInteger size = split.span(rangeSize);
      total = total.add(size);
    }
    if (!total.equals(rangeSize)) {
      throw new RuntimeException(
          "Some tokens are missing from the splits. " + "This should not happen.");
    }
    return coalesceSplits(getTargetSplitSize(totalSplitCount), splits);
  }

    SplitGenerator(String partitioner);

}