/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.planner.entities.allocationalgorithms;
import static org.libreplan.business.workingday.EffortDuration.seconds;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.lang3.Validate;
import org.libreplan.business.calendars.entities.Capacity;
import org.libreplan.business.planner.entities.Share;
import org.libreplan.business.planner.entities.ShareDivision;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.EffortDuration.IEffortFrom;
/**
* Distributes an EffortDuration among several capacities. It respects the extra
* hours requirements of the {@link Capacity capacities} and distributes the
* effort evenly.
*
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public class Distributor {
public static Distributor among(Capacity... capacities) {
return among(Arrays.asList(capacities));
}
public static Distributor among(Collection<? extends Capacity> capacities) {
Validate.noNullElements(capacities);
return new Distributor(capacities.toArray(new Capacity[0]));
}
private final Capacity[] capacities;
private final List<Share> normalCapacityShares;
private final List<Share> limitedOverloadShares;
private final List<Share> unlimitedOverloadShares;
private final List<List<Share>> phases = new ArrayList<List<Share>>();
private Distributor(Capacity[] capacities) {
Validate.notNull(capacities);
this.capacities = capacities;
this.normalCapacityShares = createNormalCapacityShares(capacities);
this.limitedOverloadShares = createOverloadShares(capacities);
this.unlimitedOverloadShares = createUnlimitedShares(capacities);
this.phases.add(normalCapacityShares);
this.phases.add(limitedOverloadShares);
this.phases.add(this.unlimitedOverloadShares);
}
private static List<Share> createNormalCapacityShares(Capacity[] capacities) {
List<Share> result = new ArrayList<Share>();
for (Capacity each : capacities) {
result.add(createNormalCapacityShare(each));
}
return result;
}
private List<Share> createOverloadShares(Capacity[] capacities) {
List<Share> result = new ArrayList<Share>();
EffortDuration maxExtraEffort = getMaxExtraEffort(capacities);
for (Capacity each : capacities) {
result.add(maxExtraEffort == null ? noSpaceAvailable()
: createOverloadShare(each, maxExtraEffort));
}
return result;
}
private EffortDuration getMaxExtraEffort(Capacity[] capacities) {
if (capacities.length == 0) {
return null;
}
Capacity max = Collections.max(Arrays.asList(capacities), new Comparator<Capacity>(){
@Override
public int compare(Capacity o1, Capacity o2) {
if (o1.getAllowedExtraEffort() == o2.getAllowedExtraEffort()) {
return 0;
} else if (o1.getAllowedExtraEffort() == null) {
return -1;
} else if (o2.getAllowedExtraEffort() == null) {
return 1;
}
return o1.getAllowedExtraEffort().compareTo(
o2.getAllowedExtraEffort());
}
});
return max.getAllowedExtraEffort();
}
private static Share createNormalCapacityShare(Capacity each) {
return new Share(-each.getStandardEffort().getSeconds());
}
private Share createOverloadShare(Capacity each,
EffortDuration maxExtraEffort) {
if (each.getAllowedExtraEffort() == null && !each.isOverAssignableWithoutLimit()) {
return noSpaceAvailable();
}
EffortDuration effort = each.getAllowedExtraEffort() != null ? each
.getAllowedExtraEffort() : maxExtraEffort;
return new Share(-effort.getSeconds());
}
private Share noSpaceAvailable() {
return new Share(Integer.MAX_VALUE);
}
private List<Share> createUnlimitedShares(Capacity[] capacities) {
List<Share> result = new ArrayList<Share>();
for (Capacity each : capacities) {
result.add(each.isOverAssignableWithoutLimit() ? new Share(0)
: noSpaceAvailable());
}
return result;
}
public List<EffortDuration> distribute(EffortDuration effort) {
EffortDuration[] result = new EffortDuration[capacities.length];
Arrays.fill(result, EffortDuration.zero());
for (List<Share> shares : phases) {
EffortDuration remaining = effort.minus(sum(result));
if (remaining.isZero()) {
return asList(result);
}
result = limitByCapacities(sum(result,
distribute(remaining, shares)));
}
return asList(result);
}
private EffortDuration[] sum(EffortDuration[] a, EffortDuration[] b) {
EffortDuration[] result = new EffortDuration[a.length];
for (int i = 0; i < result.length; i++) {
result[i] = a[i].plus(b[i]);
}
return result;
}
private EffortDuration[] distribute(EffortDuration effort,
List<Share> shares) {
ShareDivision division = ShareDivision.create(shares);
return fromSecondsToDurations(division.to(division.plus(effort
.getSeconds())));
}
private List<EffortDuration> asList(EffortDuration[] acc) {
return new ArrayList<EffortDuration>(Arrays.asList(acc));
}
private EffortDuration[] limitByCapacities(EffortDuration[] efforts) {
EffortDuration[] result = new EffortDuration[efforts.length];
for (int i = 0; i < efforts.length; i++) {
result[i] = capacities[i].limitDuration(efforts[i]);
}
return result;
}
private EffortDuration[] fromSecondsToDurations(int[] seconds) {
EffortDuration[] result = new EffortDuration[seconds.length];
for (int i = 0; i < result.length; i++) {
result[i] = seconds(seconds[i]);
}
return result;
}
private EffortDuration sum(EffortDuration[] durations) {
return EffortDuration.sum(asList(durations),
new IEffortFrom<EffortDuration>() {
@Override
public EffortDuration from(EffortDuration each) {
return each;
}});
}
}