/*
* 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.limiting.entities;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.lang3.Validate;
import org.joda.time.LocalDate;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.limiting.entities.Gap.GapOnQueue;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.workingday.IntraDayDate;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public class InsertionRequirements {
private final LimitingResourceQueueElement element;
private final DateAndHour earliestPossibleStart;
private final DateAndHour earliestPossibleEnd;
private final DateAndHour latestPossibleEnd;
public static InsertionRequirements forElement(
LimitingResourceQueueElement element,
List<LimitingResourceQueueDependency> dependenciesAffectingStart,
List<LimitingResourceQueueDependency> dependenciesAffectingEnd) {
return new InsertionRequirements(element, calculateEarliestPossibleStart(
element, dependenciesAffectingStart),
calculateEarliestPossibleEnd(element, dependenciesAffectingEnd));
}
/**
* Specifies a minimum startTime, earliestStart should be lower than this value
*
* @param element
* @param dependenciesAffectingStart
* @param dependenciesAffectingEnd
* @param startAt
* @return
*/
public static InsertionRequirements forElement(
LimitingResourceQueueElement element,
List<LimitingResourceQueueDependency> dependenciesAffectingStart,
List<LimitingResourceQueueDependency> dependenciesAffectingEnd,
DateAndHour startAt) {
DateAndHour earliesPossibleStart = calculateEarliestPossibleStart(
element, dependenciesAffectingStart);
return new InsertionRequirements(element, DateAndHour.max(
earliesPossibleStart, startAt), calculateEarliestPossibleEnd(
element, dependenciesAffectingEnd));
}
private static DateAndHour calculateEarliestPossibleEnd(
LimitingResourceQueueElement element,
List<LimitingResourceQueueDependency> dependenciesAffectingEnd) {
return DateAndHour.max(asDateAndHour(element
.getEarliestEndDateBecauseOfGantt()),
max(dependenciesAffectingEnd));
}
private static DateAndHour calculateEarliestPossibleStart(
LimitingResourceQueueElement element,
List<LimitingResourceQueueDependency> dependenciesAffectingStart) {
return DateAndHour.max(asDateAndHour(element
.getEarliestStartDateBecauseOfGantt()),
max(dependenciesAffectingStart));
}
private static DateAndHour max(
List<LimitingResourceQueueDependency> dependencies) {
DateAndHour result = null;
for (LimitingResourceQueueDependency each : dependencies) {
assert !each.getHasAsOrigin().isDetached();
result = DateAndHour.max(result, each.getDateFromOrigin());
}
return result;
}
private static DateAndHour asDateAndHour(Date date) {
return DateAndHour.from(LocalDate.fromDateFields(date));
}
public static InsertionRequirements create(
LimitingResourceQueueElement element,
DateAndHour start, DateAndHour end) {
return new InsertionRequirements(element, start, end);
}
private InsertionRequirements(LimitingResourceQueueElement element,
DateAndHour earliestPossibleStart,
DateAndHour earliestPossibleEnd) {
Validate.notNull(element);
Validate.notNull(earliestPossibleStart);
Validate.notNull(earliestPossibleEnd);
this.element = element;
this.earliestPossibleStart = earliestPossibleStart;
this.earliestPossibleEnd = earliestPossibleEnd;
this.latestPossibleEnd = calculateLatestPlanningDate(element);
}
/**
* Returns the earliest date from all the outgoing tasks from element
*
* @param element
* @return
*/
private DateAndHour calculateLatestPlanningDate(LimitingResourceQueueElement element) {
IntraDayDate result = null;
Task task = element.getTask();
for (Dependency each : task.getDependenciesWithThisOrigin()) {
TaskElement destination = each.getDestination();
result = (result == null) ? destination.getIntraDayStartDate()
: IntraDayDate.min(result,
destination.getIntraDayStartDate());
}
return (result != null) ? DateAndHour.from(result) : null;
}
public boolean isPotentiallyValid(Gap gap) {
DateAndHour gapEnd = gap.getEndTime();
return gapEnd == null
|| (earliestPossibleStart.isBefore(gapEnd) && !earliestPossibleEnd
.isAfter(gapEnd));
}
public AllocationSpec guessValidity(GapOnQueue gapOnQueue) {
Gap gap = gapOnQueue.getGap();
if (!isPotentiallyValid(gap)) {
return AllocationSpec.invalidOn(gapOnQueue);
}
DateAndHour realStart = DateAndHour.max(earliestPossibleStart, gap
.getStartTime());
Resource resource = gapOnQueue.getOriginQueue().getResource();
List<Integer> hours = gap.getHoursInGapUntilAllocatingAndGoingToTheEnd(
resource.getCalendar(), realStart,
earliestPossibleEnd, element.getIntentedTotalHours());
int total = sum(hours);
if (total < element.getIntentedTotalHours()) {
return AllocationSpec.invalidOn(gapOnQueue);
} else if (total == element.getIntentedTotalHours()) {
return validAllocation(gapOnQueue, realStart, hours);
} else {
assert total > element.getIntentedTotalHours();
int hoursSurplus = total - element.getIntentedTotalHours();
StartRemoval result = StartRemoval.removeStartSurplus(realStart,
hours, hoursSurplus);
return validAllocation(gapOnQueue, result.newStart, result.hours);
}
}
private AllocationSpec validAllocation(GapOnQueue gap,
DateAndHour realStart,
List<Integer> hours) {
return AllocationSpec.validOn(element, gap, realStart, calculateEnd(
realStart, hours), asArray(hours));
}
private DateAndHour calculateEnd(DateAndHour realStart, List<Integer> hours) {
if (hours.size() == 1) {
return new DateAndHour(realStart.getDate(), hours.get(0)
+ realStart.getHour());
}
return new DateAndHour(realStart.getDate().plusDays(hours.size() - 1),
getLast(hours));
}
private int getLast(List<Integer> hours) {
return hours.get(hours.size() - 1);
}
private static class StartRemoval {
/**
* removes the initial assignments so the resulting list has
* <code>hoursSurplus</code> less hours
*/
static StartRemoval removeStartSurplus(DateAndHour start,
List<Integer> hours, int hoursSurplus) {
int previousSize = hours.size();
int hoursRemovedAtFirstDayOfNewHours = stripStartAssignments(hours,
hoursSurplus);
int currentSize = hours.size();
int daysRemoved = previousSize - currentSize;
LocalDate newStartDay = start.getDate().plusDays(daysRemoved);
return new StartRemoval(new DateAndHour(newStartDay,
hoursRemovedAtFirstDayOfNewHours), hours);
}
/**
* @return the hours reduced in the resulting first assignment
*/
private static int stripStartAssignments(List<Integer> hours,
int hoursSurplus) {
ListIterator<Integer> listIterator = hours.listIterator();
while (listIterator.hasNext() && hoursSurplus > 0) {
Integer current = listIterator.next();
int hoursTaken = Math.min(hoursSurplus, current);
hoursSurplus -= hoursTaken;
if (hoursTaken == current) {
listIterator.remove();
} else {
listIterator.set(hoursTaken);
return current - hoursTaken;
}
}
return 0;
}
private final DateAndHour newStart;
private final List<Integer> hours;
private StartRemoval(DateAndHour newStart, List<Integer> hours) {
this.newStart = newStart;
this.hours = hours;
}
}
private static int[] asArray(Collection<Integer> integers) {
int[] result = new int[integers.size()];
int i = 0;
for (Integer each : integers) {
result[i++] = each;
}
return result;
}
private static int sum(List<Integer> hours) {
int result = 0;
for (int each : hours) {
result += each;
}
return result;
}
public LimitingResourceQueueElement getElement() {
return element;
}
public boolean isAppropiativeAllocation(AllocationSpec allocation) {
Gap gap = allocation.getGap();
DateAndHour realStart = DateAndHour.max(earliestPossibleStart,
gap.getStartTime());
return latestPossibleEnd != null
&& latestPossibleEnd.compareTo(realStart) < 0;
}
public DateAndHour getEarliestPossibleStart() {
return earliestPossibleStart;
}
}