package resa.migrate.plan;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;

/**
 * Created by ding on 14-6-27.
 */
public class DPBasedCalculator extends PackCalculator {

    private Map<String, Pack> cache;

    protected Pack calcPack() {
        //init buffer
        cache = new HashMap<>();
        int[] packSegments = new int[srcPacks.length * 2];
        for (int i = 0; i < srcPacks.length; i++) {
            packSegments[2 * i] = 0;
            packSegments[2 * i + 1] = srcPacks[i].getKey().length;
        }
        Pack p = getCachedOrCompute(0, normalizedWordloads.length, packSize, packSegments);
        // release memory
        cache = null;
        return p;
    }

    private Pack getCachedOrCompute(int wStart, int wEnd, int numPartition, int[] packSegments) {
        if (wEnd - wStart < numPartition || numPartition == 0) {
            throw new IllegalStateException("start=" + wStart + ", end=" + wEnd + ", numPartition=" + numPartition);
        }
        String key = wStart + "-" + wEnd + "-" + numPartition + "@" + Arrays.toString(packSegments);
        Pack p = cache.get(key);
        if (p == null) {
            cache.put(key, (p = compute(wStart, wEnd, numPartition, packSegments)));
        }
        return p;
    }

    private Pack compute(int wStart, int wEnd, int numPartition, int[] packSegments) {
        if (numPartition == 1) {
            return totalWorkload(wStart, wEnd) > loadUpperLimit ? INFEASIBLE :
                    new Pack(new int[]{wEnd - wStart}, gain(new Range(wStart, wEnd - 1), packSegments));
        }
        int split = wStart + 1;
        double gain = Double.MIN_VALUE;
        Pack rightPack = INFEASIBLE;
        int[] tmpSegments = new int[packSegments.length];
        int[] newPos = new int[packSegments.length];
        for (int i = split; i < wEnd; i++) {
            if (totalWorkload(wStart, i) > loadUpperLimit || wEnd - i < numPartition - 1) {
                break;
            }
            if (totalWorkload(i, wEnd) > (numPartition - 1) * loadUpperLimit){
                continue;
            }
            for (int j = 0; j < srcPacks.length; j++) {
                Range[] srcPack = srcPacks[j].getKey();
                int startPack = findPack(srcPack, i);
                int stopPack = srcPack[startPack].start < i ? startPack + 1 : startPack;
                startPack = Math.max(startPack, packSegments[j * 2]);
                stopPack = Math.min(stopPack, packSegments[j * 2 + 1]);
                if (startPack == stopPack) {
                    newPos[j * 2] = startPack;
                    newPos[j * 2 + 1] = startPack;
                } else if (startPack == stopPack - 1) {
                    newPos[j * 2] = startPack;
                    newPos[j * 2 + 1] = stopPack;
                } else {
                    throw new IllegalStateException("for package " + j + ", start=" + startPack + ", stop=" + stopPack);
                }
            }
            long tryTimes = 1 << srcPacks.length;
            for (long j = 0; j < tryTimes; j++) {
                // go right
                for (int k = 0; k < srcPacks.length; k++) {
                    if ((j & (1L << k)) > 0) {
                        tmpSegments[k * 2] = newPos[k * 2 + 1];
                    } else {
                        tmpSegments[k * 2] = newPos[k * 2];
                    }
                    tmpSegments[k * 2 + 1] = packSegments[k * 2 + 1];
                }
                Pack right = getCachedOrCompute(i, wEnd, numPartition - 1, tmpSegments);
                if (right == INFEASIBLE) {
                    continue;
                }
                // go left
                for (int k = 0; k < srcPacks.length; k++) {
                    if ((j & (1L << k)) > 0) {
                        tmpSegments[k * 2 + 1] = newPos[k * 2 + 1];
                    } else {
                        tmpSegments[k * 2 + 1] = newPos[k * 2];
                    }
                    tmpSegments[k * 2] = packSegments[k * 2];
                }
                Pack left = getCachedOrCompute(wStart, i, 1, tmpSegments);
                if (left != INFEASIBLE) {
                    double newGain = left.gain + right.gain;
                    if (Double.compare(newGain, gain) > 0) {
                        gain = newGain;
                        split = i;
                        rightPack = right;
                    }
                }
            }
        }
        if (rightPack == INFEASIBLE) {
            return INFEASIBLE;
        }
        int[] ret = new int[numPartition];
        ret[0] = split - wStart;
        System.arraycopy(rightPack.packing, 0, ret, 1, numPartition - 1);
        return new Pack(ret, gain);
    }

    private double gain(Range newPacks, int[] packSegments) {
        double sum = 0;
        for (int i = 0; i < srcPacks.length; i++) {
            double gain = 0;
            Range[] srcPack = srcPacks[i].getKey();
            for (int j = packSegments[i * 2]; j < packSegments[i * 2 + 1]; j++) {
                gain = Math.max(gain, overlap(newPacks, srcPack[j]));
            }
            sum += (gain * srcPacks[i].getValue());
        }
        return sum;
    }


    private int findPack(Range[] srcPack, int v) {
        for (int i = 0; i < srcPack.length; i++) {
            if (srcPack[i].end >= v) {
                return i;
            }
        }
        throw new IllegalStateException("value is " + v);
    }

    private double totalWorkload(int wStart, int wEnd) {
        double sum = 0;
        for (int i = wStart; i < wEnd; i++) {
            sum += normalizedWordloads[i];
        }
        return sum;
    }

    private double overlap(Range r1, Range r2) {
        if (r1.start > r2.end || r1.end < r2.start) {
            return 0;
        } else if (r1.start <= r2.start && r1.end >= r2.start) {
            return IntStream.rangeClosed(r2.start, Math.min(r2.end, r1.end)).mapToDouble(i -> dataSizes[i]).sum();
        } else if (r1.start >= r2.start && r1.start <= r2.end) {
            return IntStream.rangeClosed(r1.start, Math.min(r2.end, r1.end)).mapToDouble(i -> dataSizes[i]).sum();
        }
        return 0;
    }

}
