class SegmentGenerator {

  List<Segment> generateSegments(
      int totalSegmentCount,
      List<BigInteger> ringTokens,
      Boolean incrementalRepair,
      Map<List<String>, List<RingRange>> replicasToRange,
      String cassandraVersion)
      throws ReaperException {

    List<Segment> repairSegments = Lists.newArrayList();
    int tokenRangeCount = ringTokens.size();

    if (tokenRangeCount < totalSegmentCount || !supportsSegmentCoalescing(cassandraVersion)) {
      // We want more segments than there are token ranges.
      // Token ranges will be subdivided to match the requirements.
      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 ReaperException(
              String.format("Tokens (%s,%s) not in range of %s", start, stop, partitioner));
        }
        if (start.equals(stop) && tokenRangeCount != 1) {
          throw new ReaperException(
              String.format("Tokens (%s,%s): two nodes have the same token", start, stop));
        }

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

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

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

        LOG.info("Dividing token range [{},{}) into {} segments", start, stop, segmentCount);

        // Make big0 list of all the endpoints for the repair segments, including both start and stop
        List<BigInteger> endpointTokens = Lists.newArrayList();
        for (int j = 0; j <= segmentCount; j++) {
          BigInteger offset = rs.multiply(BigInteger.valueOf(j)).divide(BigInteger.valueOf(segmentCount));
          BigInteger reaperToken = start.add(offset);
          if (greaterThan(reaperToken, rangeMax)) {
            reaperToken = reaperToken.subtract(rangeSize);
          }
          endpointTokens.add(reaperToken);
        }

        // Append the segments between the endpoints
        for (int j = 0; j < segmentCount; j++) {
          repairSegments.add(
              Segment.builder()
                  .withTokenRanges(
                      Arrays.asList(
                          new RingRange(endpointTokens.get(j), endpointTokens.get(j + 1))))
                  .build());
          LOG.debug(
              "Segment #{}: [{},{})", j + 1, endpointTokens.get(j), endpointTokens.get(j + 1));
        }
      }

      // verify that the whole range is repaired
      BigInteger total = BigInteger.ZERO;
      for (Segment segment : repairSegments) {
        for (RingRange range : segment.getTokenRanges()) {
          BigInteger size = range.span(rangeSize);
          total = total.add(size);
        }
      }
      if (!total.equals(rangeSize) && !incrementalRepair) {
        throw new ReaperException("Not entire ring would get repaired");
      }
    } else {
      // We want less segments than there are token ranges.
      // Token ranges will be grouped to match the requirements.
      LOG.info("Less segments required than there are vnode. Coalescing eligible token ranges...");
      repairSegments = coalesceTokenRanges(getTargetSegmentSize(totalSegmentCount), replicasToRange);
    }

    return repairSegments;
  }

    SegmentGenerator(String partitioner);
    SegmentGenerator(BigInteger rangeMin, BigInteger rangeMax);

}