/*
* Copyright 2009-2012 by KNURT Systeme (http://www.knurt.de)
*
* Licensed under the Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://creativecommons.org/licenses/by-nc-sa/3.0/
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.knurt.fam.core.util.booking;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import org.springframework.dao.DataIntegrityViolationException;
import de.knurt.fam.core.model.config.BookingRule;
import de.knurt.fam.core.model.persist.User;
import de.knurt.heinzelmann.util.time.TimeFrame;
/**
* receive a request and return free booking possibilities equals to the request.
* to find equal possibilities, search nearest units and reduce (or expand) only
* one parameter of the request to get another request with positive answer.
* @author Daniel Oltmanns
* @since 0.20091002 (10/02/2009)
*/
public class BookingFinder {
private static TimeBookingRequest getSameLowerTimeUnits(TimeBookingRequest tbr) {
TimeBookingRequest result = null;
if (tbr.getRequestedTimeUnits() > tbr.getBookingRule().getMinBookableTimeUnits(tbr.getUser())) {
int units2try = tbr.getRequestedTimeUnits();
int before = units2try;
while (units2try > tbr.getBookingRule().getMinBookableTimeUnits(tbr.getUser())) {
units2try--;
tbr.setRequestedTimeUnits(units2try);
if (tbr.isAvailable()) {
result = (TimeBookingRequest) tbr.clone();
break;
}
}
tbr.setRequestedTimeUnits(before);
}
return result;
}
private static TimeBookingRequest getSameLowerCapacityUnits(TimeBookingRequest tbr) {
TimeBookingRequest result = null;
if (tbr.getRequestedCapacityUnits() > tbr.getBookingRule().getMinBookableCapacityUnits(tbr.getUser())) {
int units2try = tbr.getRequestedCapacityUnits();
int before = units2try;
while (units2try > tbr.getBookingRule().getMinBookableCapacityUnits(tbr.getUser())) {
units2try--;
tbr.setRequestedCapacityUnits(units2try);
if (tbr.isAvailable()) {
result = (TimeBookingRequest) tbr.clone();
break;
}
}
tbr.setRequestedCapacityUnits(before);
}
return result;
}
/**
* return a request containing the given parameters as it is perfectly.
* "perfectly" means, it comes as near as the user want it and it is allowed to book.
* e.g. if the user wants to book 3 units from 2:01am to 3:00am, but booking
* must start at 2:00am and there are only 2 units left at this time, return
* booking request with 2 units from 2:00am to 3am.
* @param br rules to apply for the request
* @param user requesting it
* @param tf time frame user has requested
* @param capacityUnitsInterestedIn units of capacity, user is interensted in
* @return a request containing the given parameters as it is perfectly.
*/
public static TimeBookingRequest getValidFrom(BookingRule br, User user, TimeFrame tf, Integer capacityUnitsInterestedIn) {
int smallestMinutesBookable = br.getSmallestMinutesBookable();
// set capacity units
int capacityUnitsUsed = capacityUnitsInterestedIn == null ? br.getMinBookableCapacityUnits(user) : capacityUnitsInterestedIn.intValue();
if (capacityUnitsUsed < br.getMinBookableCapacityUnits(user)) {
capacityUnitsUsed = br.getMinBookableCapacityUnits(user);
}
if (capacityUnitsUsed > br.getMaxBookableCapacityUnits(user)) {
capacityUnitsUsed = br.getMaxBookableCapacityUnits(user);
}
// get the nearest start and end time conform with the configuration of the facility
Calendar startTimeInterestedIn = tf.getCalendarStart();
startTimeInterestedIn.set(Calendar.SECOND, 0);
startTimeInterestedIn.set(Calendar.MILLISECOND, 0);
Calendar endTimeInterestedIn = tf.getCalendarEnd();
endTimeInterestedIn.set(Calendar.SECOND, 0);
endTimeInterestedIn.set(Calendar.MILLISECOND, 0);
Calendar nearestStartTimePossible = (Calendar) startTimeInterestedIn.clone();
nearestStartTimePossible.set(Calendar.SECOND, 0);
nearestStartTimePossible.set(Calendar.MILLISECOND, 0);
nearestStartTimePossible.set(Calendar.HOUR_OF_DAY, 0);
nearestStartTimePossible.set(Calendar.MINUTE, 0);
if (br.getMustStartAt() != null) {
nearestStartTimePossible.set(Calendar.MINUTE, br.getMustStartAt().intValue());
}
boolean isAfterMustStartTime = false;
while (startTimeInterestedIn.after(nearestStartTimePossible)) {
nearestStartTimePossible.add(Calendar.MINUTE, TimeBookingRequest.getMinutesOfOneStep(br));
isAfterMustStartTime = true;
}
// get one step back if it is allowed and nearer to what user wanted
if (isAfterMustStartTime && !c1IsNearest2c2(startTimeInterestedIn, nearestStartTimePossible, br)) { // it is nearer
nearestStartTimePossible.add(Calendar.MINUTE, -TimeBookingRequest.getMinutesOfOneStep(br));
}
Calendar endTime = (Calendar) nearestStartTimePossible.clone();
int timeUnitsInterestedIn = 0;
while (endTimeInterestedIn.after(endTime)) {
endTime.add(Calendar.MINUTE, smallestMinutesBookable);
timeUnitsInterestedIn++;
}
if (timeUnitsInterestedIn >= br.getMaxBookableTimeUnits(user)) {
timeUnitsInterestedIn = br.getMaxBookableTimeUnits(user);
} else if (timeUnitsInterestedIn <= br.getMinBookableTimeUnits(user)) {
timeUnitsInterestedIn = br.getMinBookableTimeUnits(user);
} else if (timeUnitsInterestedIn > 1 && !c1IsNearest2c2(endTimeInterestedIn, endTime, br)) { // one step back if it is a better choice
timeUnitsInterestedIn--;
}
TimeBookingRequest result = new TimeBookingRequest(br, user, capacityUnitsUsed, timeUnitsInterestedIn, nearestStartTimePossible);
return result;
}
private static TimeBookingRequest getSameStartingNextToForward(TimeBookingRequest tbr) {
TimeBookingRequest result = null;
int minutesOfOneStep = TimeBookingRequest.getMinutesOfOneStep(tbr.getBookingRule());
int trials = 24 * 7 * 60 / minutesOfOneStep; // one week
Calendar backup = (Calendar) tbr.getRequestedStartTime().clone();
Calendar classic = tbr.getRequestedStartTime();
while (trials-- > 0) {
classic.add(Calendar.MINUTE, minutesOfOneStep);
if (tbr.isAvailable()) {
result = (TimeBookingRequest) tbr.clone();
break;
}
}
tbr.setRequestedStartTime(backup);
return result;
}
private static TimeBookingRequest getSameStartingNextToBackward(TimeBookingRequest tbr) {
TimeBookingRequest result = null;
int trials = 24 * 7 * 60 / TimeBookingRequest.getMinutesOfOneStep(tbr.getBookingRule()); // one week
int pointer = 0;
Calendar backup = (Calendar) tbr.getRequestedStartTime().clone();
Calendar classic = tbr.getRequestedStartTime();
while (pointer++ < trials) {
classic.add(Calendar.MINUTE, TimeBookingRequest.getMinutesOfOneStep(tbr.getBookingRule()) * -1);
if (tbr.isAvailable()) {
result = (TimeBookingRequest) tbr.clone();
break;
}
}
tbr.setRequestedStartTime(backup);
return result;
}
/**
* return booking requests, that comes as near as possible to given time frame.
* capacityUnits interested in are min capacity units bookable by given booking rule, if null given.
* if there is no booking available, give back two versions: one with capacity units leaving unchanged
* and a second where the requested time is reduced.
* @see BookingRule#getMinBookableCapacityUnits()
* @param candidate search for free slots next to this candidate. assume, that the candidate is valid. otherwise an exception is thrown.
* @return a booking request, that comes as near as possible to given time frame.
*/
public static List<TimeBookingRequest> getBookingRequestNextTo(TimeBookingRequest candidate) {
assert candidate.getUser() != null;
List<TimeBookingRequest> result = new ArrayList<TimeBookingRequest>();
// candidate is as near as "perfect" now - but is it available?
if (!candidate.isAvailable()) { // no! ;-(
if (candidate.isRequest4Yesterdays()) { // do not bother me with that queries
candidate = null;
} else if (!candidate.isValidRequest()) {
throw new DataIntegrityViolationException("candidate's validity must checked before invoking this [200910061602]");
} else { // take a sledgehammer to crack a nut
// add the next possibility on another start time on same day
TimeBookingRequest tmpTbr1a = getSameStartingNextToForward(candidate);
if (tmpTbr1a != null) {
result.add(tmpTbr1a);
}
// add the next possibility on another start time on same day
TimeBookingRequest tmpTbr1b = getSameStartingNextToBackward(candidate);
if (tmpTbr1b != null) {
result.add(tmpTbr1b);
}
// add the next possibility with lower time units on same day
TimeBookingRequest tmpTbr2 = getSameLowerTimeUnits(candidate);
if (tmpTbr2 != null) {
result.add(tmpTbr2);
}
// try to find it with same time but lower capacity units
TimeBookingRequest tmpTbr3 = getSameLowerCapacityUnits(candidate);
if (tmpTbr3 != null) {
result.add(tmpTbr3);
}
// add minimals if nothing found
if (result.size() == 0) {
candidate.setRequestedTimeUnits(candidate.getBookingRule().getMinBookableTimeUnits(candidate.getUser()));
candidate.setRequestedCapacityUnits(candidate.getBookingRule().getMinBookableCapacityUnits(candidate.getUser()));
if (candidate.isAvailable()) {
result.add(candidate);
}
}
}
} else {
result.add(candidate);
}
return result;
}
private static boolean c1IsNearest2c2(Calendar c1, Calendar c2, BookingRule br) {
return getDistanceInMillis(c1, c2) < TimeBookingRequest.getMinutesOfOneStep(br) * 30000;
}
private static long getDistanceInMillis(Calendar c1, Calendar c2) {
return Math.abs(c1.getTimeInMillis() - c2.getTimeInMillis());
}
private BookingFinder() {
}
}