code: package resa.migrate.plan;
  
  import java.util.AbstractMap;
  import java.util.Collections;
  import java.util.Map;
  import java.util.Objects;
  import java.util.stream.DoubleStream;
  import java.util.stream.Stream;
  
  /**
   * Created by ding on 14-6-11.
   */
  public abstract class PackCalculator {
      public static class Range {
          public final int start;
          public final int end;
  
          Range(int start, int end) {
              if (start > end) {
                  throw new IllegalArgumentException("start=" + start + ", end=" + end);
              }
              this.start = start;
              this.end = end;
          }
  
          boolean contains(int v) {
              return start <= v && v <= end;
          }
      }
  
      protected static class Pack {
          public int[] packing;
          public double gain;
  
          Pack(int[] packing, double gain) {
              this.packing = packing;
              this.gain = gain;
          }
      }
  
      protected static final Pack INFEASIBLE = new Pack(new int[0], Double.MIN_VALUE);
  
      protected double[] workloads;
      protected double[] normalizedWordloads;
      protected double[] dataSizes;
      protected Map.Entry<Range[], Double>[] srcPacks;
      protected int packSize;
      private float ratio = 1.3f;
      protected double loadUpperLimit;
  
      // cache result
      private Pack result = null;
  
      public int[] getPack() {
          Objects.requireNonNull(result, "Calc is not called");
          return result == INFEASIBLE ? null : result.packing;
      }
  
      public Double gain() {
          Objects.requireNonNull(result, "Calc is not called");
          return result == INFEASIBLE ? null : result.gain;
      }
  
      public PackCalculator setTargetPackSize(int packSize) {
          this.packSize = packSize;
          return this;
      }
  
      public PackCalculator setUpperLimitRatio(float ratio) {
          if (Float.compare(1f, ratio) >= 0) {
              throw new IllegalArgumentException("Bad ratio: " + ratio);
          }
          this.ratio = ratio;
          return this;
      }
  
      public PackCalculator setDataSizes(double[] dataSizes) {
          this.dataSizes = dataSizes;
          return this;
      }
  
      public PackCalculator setSrcPack(int[] pack) {
          return setSrcPacks(Collections.singletonMap(pack, 1.0));
      }
  
      public static Range[] convertPack(int[] pack) {
          Range[] ret = new Range[pack.length];
          int start = 0;
          for (int i = 0; i < pack.length; i++) {
              int end = start + pack[i];
              ret[i] = new Range(start, end - 1);
              start = end;
          }
          return ret;
      }
  
      protected static int[] convertPack(Range[] pack) {
          return Stream.of(pack).mapToInt(p -> p.end - p.start + 1).toArray();
      }
  
      public PackCalculator setSrcPacks(Map<int[], Double> packs) {
          this.srcPacks = packs.entrySet().stream()
                  .map(e -> new AbstractMap.SimpleEntry<>(convertPack(e.getKey()), e.getValue()))
                  .toArray(Map.Entry[]::new);
          return this;
      }
  
      public PackCalculator setWorkloads(double[] workloads) {
          this.workloads = workloads;
          return this;
      }
  
      private void checkAndInit() {
          if (workloads.length < packSize) {
              throw new IllegalArgumentException("packSize is larger than workload size");
          }
          if (Stream.of(srcPacks).anyMatch(e -> e.getKey().length == packSize)) {
              throw new IllegalArgumentException("Current number of packs equals packSize");
          }
          if (!Stream.of(srcPacks).allMatch(e -> workloads.length == e.getKey()[e.getKey().length - 1].end + 1)) {
              throw new IllegalArgumentException("srcPack mismatch with workload");
          }
          if (workloads.length != dataSizes.length) {
              throw new IllegalArgumentException("workloads.length != dataSizes.length");
          }
          loadUpperLimit = DoubleStream.of(workloads).sum() / packSize * ratio;
          normalizedWordloads = new double[workloads.length];
          for (int i = 0; i < normalizedWordloads.length; i++) {
              if (workloads[i] > loadUpperLimit) {
                  normalizedWordloads[i] = loadUpperLimit;
              } else {
                  normalizedWordloads[i] = workloads[i];
              }
          }
      }
  
      public PackCalculator calc() {
          checkAndInit();
          result = calcPack();
          normalizedWordloads = null;
          return this;
      }
  
      protected abstract Pack calcPack();
  
  }
  