package resa.migrate.plan;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.DoubleStream;

/**
 * Created by ding on 14-6-3.
 */
public class PackingAlg {

    private static class Pack {
        int[] packing;
        double sumOfDiffSquare;

        Pack(int[] packing, double sumOfDiffSquare) {
            this.packing = packing;
            this.sumOfDiffSquare = sumOfDiffSquare;
        }
    }

    public static int[] calc(double[] workloads, int numPartition) {
        if (workloads.length < numPartition) {
            throw new IllegalArgumentException("numPartition cann't larger than workloads size");
        } else if (workloads.length == numPartition) {
            int[] ret = new int[workloads.length];
            Arrays.fill(ret, 1);
            return ret;
        }
        double avg = DoubleStream.of(workloads).sum() / numPartition;
        Map<String, Pack> cache = new HashMap<>();
        calc0(workloads, 0, workloads.length, avg, numPartition, cache);
        int[] ret = cache.get("0-" + workloads.length + "-" + numPartition).packing;
        return Objects.requireNonNull(ret, "Cannot arrived here");
    }

    public static double calc0(double[] workloads, int start, int end, double avg, int numPartition,
                               Map<String, Pack> cache) {
        if (end - start < numPartition || numPartition == 0) {
            throw new IllegalStateException("start=" + start + ", end=" + end + ", numPartition=" + numPartition);
        }
        String key = start + "-" + end + "-" + numPartition;
        Pack pack = cache.get(key);
        if (pack != null) {
            return pack.sumOfDiffSquare;
        } else if (numPartition == 1) {
            double sum = calcDiffSquare(workloads, start, end, avg);
            cache.put(key, new Pack(new int[]{end - start}, sum));
            return sum;
        } else if (end - start == numPartition) {
            int[] ret = new int[numPartition];
            double sum = 0;
            for (int i = 0; i < numPartition; i++) {
                ret[i] = 1;
                double diff = workloads[start + i] - avg;
                sum = sum + diff * diff;
            }
            cache.put(key, new Pack(ret, sum));
            return sum;
        }
        int split = start;
        double sum = Double.MAX_VALUE;
        for (int i = start; i < end - 1 && end - i >= numPartition; i++) {
            double newSum = calcDiffSquare(workloads, start, i + 1, avg)
                    + calc0(workloads, i + 1, end, avg, numPartition - 1, cache);
            if (newSum < sum) {
                sum = newSum;
                split = i;
            }
        }
        int[] ret = new int[numPartition];
        ret[0] = split - start + 1;
        System.arraycopy(cache.get((split + 1) + "-" + end + "-" + (numPartition - 1)).packing, 0, ret, 1,
                numPartition - 1);
        cache.put(key, new Pack(ret, sum));
        return sum;
    }

    private static double calcDiffSquare(double[] workloads, int start, int end, double avg) {
        double diff = avg;
        for (int i = start; i < end; i++) {
            diff = diff - workloads[i];
        }
        return diff * diff;
    }

}
