<commit_before>/*
 * This file is part of NavalPlan
 *
 * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e
 *                    Desenvolvemento Tecnolóxico de Galicia
 *
 * 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.navalplanner.business.planner.entities;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.joda.time.LocalDate;
import org.navalplanner.business.calendars.entities.ResourceCalendar;
import org.navalplanner.business.resources.entities.Criterion;
import org.navalplanner.business.resources.entities.CriterionCompounder;
import org.navalplanner.business.resources.entities.ICriterion;
import org.navalplanner.business.resources.entities.LimitingResourceQueue;
import org.navalplanner.business.resources.entities.Resource;

/**
 * Handles all the logic related to allocation of
 * {@link LimitingResourceQueueElement} into {@link LimitingResourceQueue}
 *
 * The class does not do the allocation itself but provides methods:
 * <em>getFirstValidGap</em>, <em>calculateStartAndEndTime</em> or
 * <em>generateDayAssignments</em>, needed to do the allocation of
 * {@link LimitingResourceQueueElement}
 *
 * @author <NAME> <<EMAIL>>
 *
 */
public class LimitingResourceAllocator {

    private final static ResourcesPerDay ONE_RESOURCE_PER_DAY = ResourcesPerDay
            .amount(new BigDecimal(1));

    /**
     * Returns first valid gap in queue for element
     *
     * Returns null if there is not a valid gap. This case can only happen on
     * trying to allocate an element related to a generic resource allocation.
     * It is possible that queue.resource does not hold element.criteria at any
     * interval of time
     *
     * @param queue search gap inside queue
     * @param element element to fit into queue
     * @return
     */
    public static LimitingResourceQueueElementGap getFirstValidGap(LimitingResourceQueue queue,
            LimitingResourceQueueElement element) {

        final Resource resource = queue.getResource();
        final List<LimitingResourceQueueElement> elements = new LinkedList<LimitingResourceQueueElement>(
                queue.getLimitingResourceQueueElements());
        final int size = elements.size();

        // Iterate through queue elements
        int pos = 0;
        do {
            LimitingResourceQueueElementGap gap = getGapInQueueAtPosition(
                    resource, elements, element, pos++);

            // The queue cannot hold this element (queue.resource
            // doesn't meet element.criteria)
            if (gap == null) {
                return null;
            }

            if (canFitIntoGap(element, gap, resource)) {
                return gap;
            }
        } while (pos <= size);

        return null;
    }

    private static boolean canFitIntoGap(LimitingResourceQueueElement element,
            LimitingResourceQueueElementGap gap, final Resource resource) {

        final boolean canfit = gap.canFit(element);
        final ResourceAllocation<?> resourceAllocation = element
                .getResourceAllocation();

        if (resourceAllocation instanceof SpecificResourceAllocation) {
            return canfit;
        } else if (resourceAllocation instanceof GenericResourceAllocation) {
            // Resource must satisfy element.criteria during for the
            // period of time the element will be allocated in the
            // queue
            final GenericResourceAllocation generic = (GenericResourceAllocation) resourceAllocation;
            List<DayAssignment> dayAssignments = generateDayAssignments(
                    resourceAllocation, resource, gap.getStartTime());
            DateAndHour[] startAndEndTime = calculateStartAndEndTime(dayAssignments);
            return canfit
                    && (satisfiesCriteriaDuringInterval(resource, generic
                            .getCriterions(), startAndEndTime));
        }
        return false;
    }

    /**
     * Calculates start and end date out of a list of day assignments.
     *
     * The first day is the day were the first day assignment happened.
     * The last day is the day were the last day assignment happened.
     * @param dayAssignments
     * @return
     */
    public static DateAndHour[] calculateStartAndEndTime(List<DayAssignment> dayAssignments) {
        DateAndHour[] result = new DateAndHour[2];

        final DayAssignment start = dayAssignments.get(0);
        final DayAssignment end = dayAssignments.get(dayAssignments.size() - 1);
        result[0] = new DateAndHour(start.getDay(), start.getHours());
        result[1] = new DateAndHour(end.getDay(), end.getHours());

        return result;
    }

    private static boolean satisfiesCriteriaDuringInterval(Resource resource, Set<Criterion> criteria, DateAndHour[] interval) {
        final Date startDate = interval[0].getDate().toDateTimeAtStartOfDay().toDate();
        final Date endDate = interval[1].getDate().toDateTimeAtStartOfDay().toDate();
        return satisfiesCriteriaDuringInterval(resource, criteria, startDate, endDate);
    }

    private static boolean satisfiesCriteriaDuringInterval(Resource resource, Set<Criterion> criteria, Date startDate, Date endDate) {
        ICriterion compositedCriterion = CriterionCompounder.buildAnd(criteria)
                .getResult();
        return compositedCriterion.isSatisfiedBy(resource, startDate, endDate);
    }

    private static LimitingResourceQueueElementGap getGapInQueueAtPosition(
            Resource resource, List<LimitingResourceQueueElement> elements,
            LimitingResourceQueueElement element, int pos) {

        final int size = elements.size();
        final DateAndHour startTimeBecauseOfGantt = getStartTimeBecauseOfGantt(element);

        if (size == 0) {
            return createLastGap(element, null, resource);
        }

        if (pos == size) {
            return createLastGap(element, elements.get(size - 1), resource);
        }

        LimitingResourceQueueElement current = elements.get(pos);
        // First element<commit_msg> ItEr58S10CUAsignacionRecursosLimitantesItEr57S11: For a generic resource allocation, split a gap into several gaps matching resourceAllocation.criteria<commit_after>/*
 * This file is part of NavalPlan
 *
 * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e
 *                    Desenvolvemento Tecnolóxico de Galicia
 *
 * 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.navalplanner.business.planner.entities;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.joda.time.LocalDate;
import org.navalplanner.business.calendars.entities.ResourceCalendar;
import org.navalplanner.business.resources.entities.Criterion;
import org.navalplanner.business.resources.entities.CriterionCompounder;
import org.navalplanner.business.resources.entities.ICriterion;
import org.navalplanner.business.resources.entities.LimitingResourceQueue;
import org.navalplanner.business.resources.entities.Resource;

/**
 * Handles all the logic related to allocation of
 * {@link LimitingResourceQueueElement} into {@link LimitingResourceQueue}
 *
 * The class does not do the allocation itself but provides methods:
 * <em>getFirstValidGap</em>, <em>calculateStartAndEndTime</em> or
 * <em>generateDayAssignments</em>, needed to do the allocation of
 * {@link LimitingResourceQueueElement}
 *
 * @author <NAME> <<EMAIL>>
 *
 */
public class LimitingResourceAllocator {

    private final static ResourcesPerDay ONE_RESOURCE_PER_DAY = ResourcesPerDay
            .amount(new BigDecimal(1));

    /**
     * Returns first valid gap in queue for element
     *
     * Returns null if there is not a valid gap. This case can only happen on
     * trying to allocate an element related to a generic resource allocation.
     * It is possible that queue.resource does not hold element.criteria at any
     * interval of time
     *
     * @param queue search gap inside queue
     * @param element element to fit into queue
     * @return
     */
    public static LimitingResourceQueueElementGap getFirstValidGap(LimitingResourceQueue queue,
            LimitingResourceQueueElement element) {

        final Resource resource = queue.getResource();
        final List<LimitingResourceQueueElement> elements = new LinkedList<LimitingResourceQueueElement>(
                queue.getLimitingResourceQueueElements());
        final int size = elements.size();

        // Iterate through queue elements
        int pos = 0;
        do {
            LimitingResourceQueueElementGap gap = getGapInQueueAtPosition(
                    resource, elements, element, pos++);

            // The queue cannot hold this element (queue.resource
            // doesn't meet element.criteria)
            if (gap == null) {
                return null;
            }

            if (isSpecific(element) && gap.canFit(element)) {
                return gap;
            } else if (isGeneric(element)){
                final List<LimitingResourceQueueElementGap> gaps = splitIntoGapsSatisfyingCriteria(
                        resource, getCriteria(element), gap);
                for (LimitingResourceQueueElementGap subGap: gaps) {
                    if (subGap.canFit(element)) {
                        return subGap;
                    }
                }
            }
        } while (pos <= size);

        return null;
    }

    private void print(List<Criterion> criteria) {
        System.out.println("### print");
        for (Criterion each: criteria) {
            System.out.println("### each: " + each);
        }
        System.out.println("### ");
    }

    private static boolean isGeneric(LimitingResourceQueueElement element) {
        return element.getResourceAllocation() instanceof GenericResourceAllocation;
    }

    private static boolean isSpecific(LimitingResourceQueueElement element) {
        return element.getResourceAllocation() instanceof SpecificResourceAllocation;
    }

    private static Set<Criterion> getCriteria(LimitingResourceQueueElement element) {
        final ResourceAllocation<?> resourceAllocation = element.getResourceAllocation();
        if (resourceAllocation instanceof GenericResourceAllocation) {
            return ((GenericResourceAllocation) resourceAllocation).getCriterions();
        }
        return null;
    }

    private static Date toDate(LocalDate date) {
        return date != null ? date.toDateTimeAtStartOfDay().toDate() : null;
    }

    private static List<LimitingResourceQueueElementGap> splitIntoGapsSatisfyingCriteria(
            Resource resource, Set<Criterion> criteria, LimitingResourceQueueElementGap gap) {
        return splitIntoGapsSatisfyingCriteria(resource, criteria, gap.getStartTime(), gap.getEndTime());
    }

    /**
     * Returns a set of {@link LimitingResourceQueueElementGap} composed by those gaps
     * which satisfy <em>criteria</em> within the period: <em>gapStartTime</em> till <em>gapEndTime</em>
     *
     * @param resource
     * @param criteria
     *            criteria to be satisfied by resource
     * @param gapStartTime
     *            start time of gap
     * @param gapEndTime
     *            end time of gap
     * @return
     */
    private static List<LimitingResourceQueueElementGap> splitIntoGapsSatisfyingCriteria(
            Resource resource, Set<Criterion> criteria, DateAndHour gapStartTime,
            DateAndHour gapEndTime) {

        final ICriterion compositedCriterion = CriterionCompounder.buildAnd(criteria)
                .getResult();
        final ResourceCalendar calendar = resource.getCalendar();

        // FIXME: If endTime is null (lastGap), set endTime as 100 years ahead startTime
        final LocalDate gapEndDate = gapEndTime != null ? gapEndTime.getDate().plusDays(1)
                : gapStartTime.getDate().plusYears(10);
        final LocalDate gapStartDate = gapStartTime.getDate();

        List<LimitingResourceQueueElementGap> result = new ArrayList<LimitingResourceQueueElementGap>();

        LocalDate date = gapStartDate;
        boolean open = compositedCriterion.isSatisfiedBy(resource, toDate(date));
        DateAndHour startTime = gapStartTime, endTime;
        while (date.isBefore(gapEndDate)) {
            if (calendar.getCapacityAt(date) == 0) {
                date = date.plusDays(1);
                continue;
            }

            if (open == false && compositedCriterion.isSatisfiedBy(resource, toDate(date))) {
                startTime = new DateAndHour(date, 0);
                open = true;
            }
            if (open == true && !compositedCriterion.isSatisfiedBy(resource, toDate(date))) {
                endTime = new DateAndHour(date, 0);
                result.add(LimitingResourceQueueElementGap.create(resource,
                        startTime, endTime));
                open = false;
            }
            date = date.plusDays(1);
        }
        result.add(LimitingResourceQueueElementGap.create(resource, startTime, gapEndTime));

        return result;
    }

    /**
     * Calculates start and end date out of a list of day assignments.
     *
     * The first day is the day were the first day assignment happened.
     * The last day is the day were the last day assignment happened.
     * @param dayAssignments
     * @return
     */
    public static DateAndHour[] calculateStartAndEndTime(List<DayAssignment> dayAssignments) {
        DateAndHour[] result = new DateAndHour[2];

        final DayAssignment start = dayAssignments.get(0);
        final DayAssignment end = dayAssignments.get(dayAssignments.size() - 1);
        result[0] = new DateAndHour(start.getDay(), start.getHours());
        result[1] = new DateAndHour(end.getDay(), end.getHours());

        return result;
    }

    private static LimitingResourceQueueElementGap getGapInQueueAtPosition(
            Resource resource, List<LimitingResourceQueueElement> elements,
            LimitingResourceQueueElement element, int pos) {

        final int size = elements.size();
        final DateAndHour startTimeBecauseOfGantt = getStartTimeBecauseOfGantt(element);

        if (size == 0) {
            return createLastGap(element, null, resource);
        }

        if (pos == size) {
            return createLastGap(element, elements.get(size - 1), resource);
        }

        LimitingResourceQueueElement current = elements.get(pos);
        // First element
{'fraction_non_alphanumeric': 0.07938299473288186, 'fraction_numerical': 0.003072485578128919, 'mean_word_length': 2.878647859922179, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4633261', 'n_tokens_mistral': 4107, 'n_tokens_neox': 4044, 'n_words': 1228}