package resa.migrate.plan;

import java.util.Map;
import java.util.stream.IntStream;
import java.util.stream.Stream;

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

    private KuhnMunkres kmAlg;
    private double gain;

    @Override
    protected Pack calcPack() {
        Range[] retPack = convertPack(PackingAlg.calc(this.workloads, this.packSize));
        int maxPackSize = Stream.of(srcPacks).map(Map.Entry::getKey).mapToInt(pack -> pack.length).max().getAsInt();
        kmAlg = new KuhnMunkres(Math.max(retPack.length, maxPackSize));
        gain = totalGain(retPack);
        Range[] buf = new Range[retPack.length];
        while (true) {
            int packToAdj = -1, newEnd = -1;
            for (int i = 0; i < retPack.length - 1; i++) {
                int point = retPack[i].end;
                // step forward
                System.arraycopy(retPack, 0, buf, 0, buf.length);
                int p = stepForward(buf, i);
                if (p != point) {
                    packToAdj = i;
                    newEnd = p;
                }
                // step backward
                System.arraycopy(retPack, 0, buf, 0, buf.length);
                p = stepBackward(buf, i);
                if (p != point) {
                    packToAdj = i;
                    newEnd = p;
                }
            }
            // nothing to update
            if (packToAdj == -1) {
                break;
            } else {
                retPack[packToAdj] = new Range(retPack[packToAdj].start, newEnd);
                retPack[packToAdj + 1] = new Range(newEnd + 1, retPack[packToAdj + 1].end);
            }
        }
        kmAlg = null;
        return new Pack(convertPack(retPack), gain);
    }

    private int stepForward(Range[] orgPack, int point) {
        double workload = totalWorkload(orgPack[point].start, orgPack[point].end + 1);
        int newPos = orgPack[point].end;
        int j = orgPack[point].end + 1;
        final int upBound = orgPack[point + 1].end;
        while (j < upBound && (workload += normalizedWordloads[j]) <= loadUpperLimit) {
            orgPack[point] = new Range(orgPack[point].start, j);
            orgPack[point + 1] = new Range(j + 1, orgPack[point + 1].end);
            double g = totalGain(orgPack);
            if (g > gain) {
                gain = g;
                newPos = j;
            }
            j++;
        }
        return newPos;
    }

    private int stepBackward(Range[] orgPack, int point) {
        double workload = totalWorkload(orgPack[point + 1].start, orgPack[point + 1].end + 1);
        int newPos = orgPack[point].end;
        int j = orgPack[point].end - 1;
        final int lowBound = orgPack[point].start;
        while (j > lowBound && (workload += normalizedWordloads[j]) <= loadUpperLimit) {
            orgPack[point] = new Range(orgPack[point].start, j);
            orgPack[point + 1] = new Range(j + 1, orgPack[point + 1].end);
            double g = totalGain(orgPack);
            if (g > gain) {
                gain = g;
                newPos = j;
            }
            j--;
        }
        return newPos;
    }

    private double totalGain(Range[] pack) {
        return Stream.of(srcPacks).mapToDouble(e -> packGain(pack, e.getKey()) * e.getValue()).sum();
    }

    private double packGain(Range[] pack1, Range[] pack2) {
        double[][] weights = new double[pack1.length][pack2.length];
        for (int i = 0; i < pack1.length; i++) {
            for (int j = 0; j < pack2.length; j++) {
                weights[i][j] = overlap(pack1[i], pack2[j]);
            }
        }
        double[] maxWeight = new double[1];
        kmAlg.getMaxBipartie(weights, maxWeight);
        // int[][] pairs = kmAlg.getMaxBipartie(weights, maxWeight);
        // return Stream.of(pairs).mapToDouble(r -> overlap(newPacks[r[0]], currPacks[pStart + r[1]])).sum();
        return maxWeight[0];
    }

    private double overlap(Range r1, Range r2) {
        if (r1.end < r2.start || r1.start > r2.end) {
            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;
    }

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

}
