001package org.cpsolver.studentsct.model;
002
003import java.text.DecimalFormat;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Map;
011import java.util.Set;
012import java.util.TreeSet;
013
014import org.cpsolver.coursett.model.TimeLocation;
015import org.cpsolver.ifs.assignment.Assignment;
016import org.cpsolver.ifs.assignment.AssignmentComparator;
017import org.cpsolver.ifs.util.ToolBox;
018import org.cpsolver.studentsct.StudentSectioningModel;
019import org.cpsolver.studentsct.constraint.ConfigLimit;
020import org.cpsolver.studentsct.constraint.CourseLimit;
021import org.cpsolver.studentsct.constraint.LinkedSections;
022import org.cpsolver.studentsct.constraint.SectionLimit;
023import org.cpsolver.studentsct.reservation.Reservation;
024import org.cpsolver.studentsct.reservation.Restriction;
025
026
027/**
028 * Representation of a request of a student for one or more course. A student
029 * requests one of the given courses, preferably the first one. <br>
030 * <br>
031 * 
032 * @author  Tomáš Müller
033 * @version StudentSct 1.3 (Student Sectioning)<br>
034 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
035 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
036 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
037 * <br>
038 *          This library is free software; you can redistribute it and/or modify
039 *          it under the terms of the GNU Lesser General Public License as
040 *          published by the Free Software Foundation; either version 3 of the
041 *          License, or (at your option) any later version. <br>
042 * <br>
043 *          This library is distributed in the hope that it will be useful, but
044 *          WITHOUT ANY WARRANTY; without even the implied warranty of
045 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
046 *          Lesser General Public License for more details. <br>
047 * <br>
048 *          You should have received a copy of the GNU Lesser General Public
049 *          License along with this library; if not see
050 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
051 */
052public class CourseRequest extends Request {
053    private static DecimalFormat sDF = new DecimalFormat("0.000");
054    private List<Course> iCourses = null;
055    private Set<Choice> iWaitlistedChoices = new HashSet<Choice>();
056    private Set<Choice> iSelectedChoices = new HashSet<Choice>();
057    private Set<Choice> iRequiredChoices = new HashSet<Choice>();
058    private boolean iWaitlist = false;
059    private Long iTimeStamp = null;
060    private Double iCachedMinPenalty = null, iCachedMaxPenalty = null;
061    public static boolean sSameTimePrecise = false;
062    private Set<RequestGroup> iRequestGroups = new HashSet<RequestGroup>();
063    private Enrollment iFixed = null;
064
065    /**
066     * Constructor
067     * 
068     * @param id
069     *            request unique id
070     * @param priority
071     *            request priority
072     * @param alternative
073     *            true if the request is alternative (alternative request can be
074     *            assigned instead of a non-alternative course requests, if it
075     *            is left unassigned)
076     * @param student
077     *            appropriate student
078     * @param courses
079     *            list of requested courses (in the correct order -- first is
080     *            the requested course, second is the first alternative, etc.)
081     * @param waitlist
082     *            time stamp of the request if the student can be put on a wait-list (no alternative
083     *            course request will be given instead)
084     * @param critical
085     *            is the course request is critical for the student in order to move forward in their degree 
086     * @param timeStamp request time stamp
087     */
088    public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, boolean waitlist, boolean critical, Long timeStamp) {
089        super(id, priority, alternative, student);
090        iCourses = new ArrayList<Course>(courses);
091        for (Course course: iCourses)
092            course.getRequests().add(this);
093        iWaitlist = waitlist;
094        setRequestPriority(critical ? RequestPriority.Critical : RequestPriority.Normal);
095        iTimeStamp = timeStamp;
096    }
097    
098    /**
099     * Constructor
100     * 
101     * @param id
102     *            request unique id
103     * @param priority
104     *            request priority
105     * @param alternative
106     *            true if the request is alternative (alternative request can be
107     *            assigned instead of a non-alternative course requests, if it
108     *            is left unassigned)
109     * @param student
110     *            appropriate student
111     * @param courses
112     *            list of requested courses (in the correct order -- first is
113     *            the requested course, second is the first alternative, etc.)
114     * @param waitlist
115     *            time stamp of the request if the student can be put on a wait-list (no alternative
116     *            course request will be given instead)
117     * @param importance
118     *            request priority 
119     * @param timeStamp request time stamp
120     */
121    public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, boolean waitlist, RequestPriority importance, Long timeStamp) {
122        super(id, priority, alternative, student);
123        iCourses = new ArrayList<Course>(courses);
124        for (Course course: iCourses)
125            course.getRequests().add(this);
126        iWaitlist = waitlist;
127        setRequestPriority(importance);
128        iTimeStamp = timeStamp;
129    }
130    
131    /**
132     * Constructor
133     * 
134     * @param id
135     *            request unique id
136     * @param priority
137     *            request priority
138     * @param alternative
139     *            true if the request is alternative (alternative request can be
140     *            assigned instead of a non-alternative course requests, if it
141     *            is left unassigned)
142     * @param student
143     *            appropriate student
144     * @param courses
145     *            list of requested courses (in the correct order -- first is
146     *            the requested course, second is the first alternative, etc.)
147     * @param waitlist
148     *            time stamp of the request if the student can be put on a wait-list (no alternative
149     *            course request will be given instead)
150     * @param timeStamp request time stamp
151     */
152    public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, boolean waitlist, Long timeStamp) {
153        this(id, priority, alternative, student, courses, waitlist, false, timeStamp);
154    }
155
156    /**
157     * List of requested courses (in the correct order -- first is the requested
158     * course, second is the first alternative, etc.)
159     * @return requested course offerings
160     */
161    public List<Course> getCourses() {
162        return iCourses;
163    }
164
165    /**
166     * Create enrollment for the given list of sections. The list of sections
167     * needs to be correct, i.e., a section for each subpart of a configuration
168     * of one of the requested courses.
169     * @param sections selected sections
170     * @param reservation selected reservation
171     * @return enrollment
172     */
173    public Enrollment createEnrollment(Set<? extends SctAssignment> sections, Reservation reservation) {
174        if (sections == null || sections.isEmpty())
175            return null;
176        Config config = ((Section) sections.iterator().next()).getSubpart().getConfig();
177        Course course = null;
178        for (Course c: iCourses) {
179            if (c.getOffering().getConfigs().contains(config)) {
180                course = c;
181                break;
182            }
183        }
184        return new Enrollment(this, iCourses.indexOf(course), course, config, sections, reservation);
185    }
186    
187    /**
188     * Create enrollment for the given list of sections. The list of sections
189     * needs to be correct, i.e., a section for each subpart of a configuration
190     * of one of the requested courses.
191     * @param course selected course
192     * @param sections selected sections
193     * @param reservation selected reservation
194     * @return enrollment
195     */
196    public Enrollment createEnrollment(Course course, Set<? extends SctAssignment> sections, Reservation reservation) {
197        if (sections == null || sections.isEmpty())
198            return null;
199        Config config = ((Section) sections.iterator().next()).getSubpart().getConfig();
200        return new Enrollment(this, iCourses.indexOf(course), course, config, sections, reservation);
201    }
202
203    /**
204     * Create enrollment for the given list of sections. The list of sections
205     * needs to be correct, i.e., a section for each subpart of a configuration
206     * of one of the requested courses.
207     * @param assignment current assignment (to guess the reservation)
208     * @param sections selected sections
209     * @return enrollment
210     */
211    public Enrollment createEnrollment(Assignment<Request, Enrollment> assignment, Set<? extends SctAssignment> sections) {
212        Enrollment ret = createEnrollment(sections, null);
213        ret.guessReservation(assignment, true);
214        return ret;
215        
216    }
217    
218    /**
219     * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit.
220     * @return maximal domain size, -1 if unlimited
221     */
222    protected int getMaxDomainSize() {
223        StudentSectioningModel model = (StudentSectioningModel) getModel();
224        return model == null ? -1 : model.getMaxDomainSize();
225    }
226    
227    /**
228     * Return all possible enrollments.
229     */
230    @Override
231    public List<Enrollment> computeEnrollments(Assignment<Request, Enrollment> assignment) {
232        List<Enrollment> ret = new ArrayList<Enrollment>();
233        if (isFixed()) {
234            ret.add(getFixedValue());
235            return ret;
236        }
237        if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) {
238            ret.add(getInitialAssignment());
239            return ret;
240        }
241        int idx = 0;
242        for (Course course : iCourses) {
243            for (Config config : course.getOffering().getConfigs()) {
244                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false,
245                        false, false, getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), false);
246            }
247            idx++;
248        }
249        return ret;
250    }
251
252    /**
253     * Return a subset of all enrollments -- randomly select only up to
254     * limitEachConfig enrollments of each config.
255     * @param assignment current assignment
256     * @param limitEachConfig maximal number of enrollments in each configuration
257     * @return computed enrollments
258     */
259    public List<Enrollment> computeRandomEnrollments(Assignment<Request, Enrollment> assignment, int limitEachConfig) {
260        List<Enrollment> ret = new ArrayList<Enrollment>();
261        if (isFixed()) {
262            ret.add(getFixedValue());
263            return ret;
264        }
265        if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) {
266            ret.add(getInitialAssignment());
267            return ret;
268        }
269        int idx = 0;
270        for (Course course : iCourses) {
271            for (Config config : course.getOffering().getConfigs()) {
272                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false,
273                        false, true, (limitEachConfig <= 0 ? limitEachConfig : ret.size() + limitEachConfig), false);
274            }
275            idx++;
276        }
277        return ret;
278    }
279
280    /**
281     * Return true if the both sets of sections contain sections of the same
282     * subparts, and each pair of sections of the same subpart is offered at the
283     * same time.
284     */
285    private boolean sameTimes(Set<Section> sections1, Set<Section> sections2) {
286        for (Section s1 : sections1) {
287            Section s2 = null;
288            for (Section s : sections2) {
289                if (s.getSubpart().equals(s1.getSubpart())) {
290                    s2 = s;
291                    break;
292                }
293            }
294            if (s2 == null)
295                return false;
296            if (!ToolBox.equals(s1.getTime(), s2.getTime()))
297                return false;
298        }
299        return true;
300    }
301
302    /**
303     * Recursive computation of enrollments
304     * 
305     * @param enrollments
306     *            list of enrollments to be returned
307     * @param priority
308     *            zero for the course, one for the first alternative, two for the second alternative
309     * @param penalty
310     *            penalty of the selected sections
311     * @param course
312     *            selected course
313     * @param config
314     *            selected configuration
315     * @param sections
316     *            sections selected so far
317     * @param idx
318     *            index of the subparts (a section of 0..idx-1 subparts has been
319     *            already selected)
320     * @param availableOnly
321     *            only use available sections
322     * @param skipSameTime
323     *            for each possible times, pick only one section
324     * @param selectedOnly
325     *            select only sections that are selected (
326     *            {@link CourseRequest#isSelected(Section)} is true)
327     * @param random
328     *            pick sections in a random order (useful when limit is used)
329     * @param limit
330     *            when above zero, limit the number of selected enrollments to
331     *            this limit
332     * @param ignoreDisabled
333     *            are sections that are disabled for student scheduling allowed to be used
334     * @param reservations
335     *            list of applicable reservations
336     */
337    private void computeEnrollments(Assignment<Request, Enrollment> assignment, Collection<Enrollment> enrollments, int priority, double penalty, Course course, Config config,
338            HashSet<Section> sections, int idx, boolean availableOnly, boolean skipSameTime, boolean selectedOnly,
339            boolean random, int limit, boolean checkParent) {
340        if (limit > 0 && enrollments.size() >= limit)
341            return;
342        if (checkParent && course.hasParent()) {
343            Course parent = course.getParent();
344            for (Request r: getStudent().getRequests()) {
345                if (r.hasCourse(parent)) {
346                    Enrollment e = assignment.getValue(r);
347                    if (e == null || !parent.equals(e.getCourse())) return;
348                }
349            }
350        }
351        if (idx == 0) { // run only once for each configuration
352            if (isNotAllowed(course, config)) return;
353            boolean canOverLimit = false;
354            if (availableOnly) {
355                for (Reservation r: getReservations(course)) {
356                    if (!r.canBatchAssignOverLimit()) continue;
357                    if (r.neverIncluded()) continue;
358                    if (!r.getConfigs().isEmpty() && !r.getConfigs().contains(config)) continue;
359                    if (r.getReservedAvailableSpace(assignment, this) < getWeight()) continue;
360                    if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
361                    canOverLimit = true; break;
362                }
363            }
364            if (!canOverLimit) {
365                if (availableOnly && config.getLimit() >= 0 && ConfigLimit.getEnrollmentWeight(assignment, config, this) > config.getLimit())
366                    return;
367                if (availableOnly && course.getLimit() >= 0 && CourseLimit.getEnrollmentWeight(assignment, course, this) > course.getLimit())
368                    return;
369                if (config.getOffering().hasReservations()) {
370                    boolean hasReservation = false, hasConfigReservation = false, reservationMustBeUsed = false;
371                    for (Reservation r: getReservations(course)) {
372                        if (r.mustBeUsed()) reservationMustBeUsed = true;
373                        if (availableOnly && r.getReservedAvailableSpace(assignment, this) < getWeight()) continue;
374                        if (availableOnly && r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
375                        if (r.neverIncluded()) {
376                        } else if (r.getConfigs().isEmpty()) {
377                            hasReservation = true;
378                        } else if (r.getConfigs().contains(config)) {
379                            hasReservation = true;
380                            hasConfigReservation = true;
381                        } else if (!r.areRestrictionsInclusive()) {
382                            hasReservation = true;
383                        }
384                    }
385                    if (!hasConfigReservation && config.getTotalUnreservedSpace() < getWeight())
386                        return;
387                    if (!hasReservation && config.getOffering().getTotalUnreservedSpace() < getWeight())
388                        return;
389                    if (availableOnly && !hasReservation && config.getOffering().getUnreservedSpace(assignment, this) < getWeight())
390                        return;
391                    if (availableOnly && !hasConfigReservation && config.getUnreservedSpace(assignment, this) < getWeight())
392                        return;
393                    if (!hasReservation && reservationMustBeUsed)
394                        return;
395                }
396            }
397        }
398        if (config.getSubparts().size() == idx) {
399            if (skipSameTime && sSameTimePrecise) {
400                boolean waitListedOrSelected = false;
401                if (!getSelectedChoices().isEmpty() || !getWaitlistedChoices().isEmpty()) {
402                    for (Section section : sections) {
403                        if (isWaitlisted(section) || isSelected(section)) {
404                            waitListedOrSelected = true;
405                            break;
406                        }
407                    }
408                }
409                if (!waitListedOrSelected) {
410                    for (Enrollment enrollment : enrollments) {
411                        if (sameTimes(enrollment.getSections(), sections))
412                            return;
413                    }
414                }
415            }
416            Enrollment e = new Enrollment(this, priority, course, config, new HashSet<SctAssignment>(sections), null);
417            if (isNotAllowed(e)) {
418            } else if (!config.getOffering().hasReservations()) {
419                enrollments.add(e);
420            } else {
421                boolean mustHaveReservation = config.getOffering().getTotalUnreservedSpace() < getWeight();
422                boolean mustHaveConfigReservation = config.getTotalUnreservedSpace() < getWeight();
423                boolean mustHaveSectionReservation = false;
424                boolean containDisabledSection = false;
425                for (Section s: sections) {
426                    if (s.getTotalUnreservedSpace() < getWeight()) {
427                        mustHaveSectionReservation = true;
428                    }
429                    if (!getStudent().isAllowDisabled() && !s.isEnabled(getStudent())) {
430                        containDisabledSection = true;
431                    }
432                }
433                boolean canOverLimit = false;
434                if (availableOnly) {
435                    for (Reservation r: getReservations(course)) {
436                        if (!r.canBatchAssignOverLimit() || !r.isIncluded(e)) continue;
437                        if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
438                        if (containDisabledSection && !r.isAllowDisabled()) continue;
439                        enrollments.add(new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), r));
440                        canOverLimit = true;
441                    }
442                }
443                if (!canOverLimit) {
444                    boolean reservationMustBeUsed = false;
445                    reservations: for (Reservation r: (availableOnly ? getSortedReservations(assignment, course) : getReservations(course))) {
446                        if (r.mustBeUsed()) reservationMustBeUsed = true;
447                        if (!r.isIncluded(e)) continue;
448                        if (availableOnly && r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
449                        if (mustHaveConfigReservation && r.getConfigs().isEmpty()) continue;
450                        if (mustHaveSectionReservation)
451                            for (Section s: sections)
452                                if (r.getSections(s.getSubpart()) == null && s.getTotalUnreservedSpace() < getWeight()) continue reservations;
453                        if (containDisabledSection && !r.isAllowDisabled()) continue;
454                        enrollments.add(new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), r));
455                        if (availableOnly) return; // only one available reservation suffice (the best matching one)
456                    }
457                    // a case w/o reservation
458                    if (!(mustHaveReservation || mustHaveConfigReservation || mustHaveSectionReservation) &&
459                        !(availableOnly && config.getOffering().getUnreservedSpace(assignment, this) < getWeight()) &&
460                        !reservationMustBeUsed && !containDisabledSection) {
461                        enrollments.add(new Enrollment(this, priority, !getReservations(course).isEmpty(), null, config, new HashSet<SctAssignment>(sections), null));
462                    }
463                }
464            }
465        } else {
466            Subpart subpart = config.getSubparts().get(idx);
467            HashSet<TimeLocation> times = (skipSameTime ? new HashSet<TimeLocation>() : null);
468            List<Section> sectionsThisSubpart = subpart.getSections();
469            if (skipSameTime) {
470                sectionsThisSubpart = new ArrayList<Section>(subpart.getSections());
471                Collections.sort(sectionsThisSubpart, new AssignmentComparator<Section, Request, Enrollment>(assignment));
472            }
473            List<Section> matchingSectionsThisSubpart = new ArrayList<Section>(subpart.getSections().size());
474            boolean hasChildren = !subpart.getChildren().isEmpty();
475            for (Section section : sectionsThisSubpart) {
476                if (section.isCancelled())
477                    continue;
478                if (!isRequired(section))
479                    continue;
480                if (getInitialAssignment() != null && (getModel() != null && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) &&
481                        !getInitialAssignment().getAssignments().contains(section))
482                    continue;
483                if (isFixed() && !getFixedValue().getAssignments().contains(section))
484                    continue;
485                if (section.getParent() != null && !sections.contains(section.getParent()))
486                    continue;
487                if (section.isOverlapping(sections))
488                    continue;
489                if (selectedOnly && hasSelection(section) && !isSelected(section))
490                    continue;
491                if (isNotAllowed(course, section))
492                    continue;
493                if (!getStudent().isAvailable(section)) {
494                    boolean canOverlap = false;
495                    for (Reservation r: getReservations(course)) {
496                        if (!r.isAllowOverlap()) continue;
497                        if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue;
498                        if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
499                        canOverlap = true; break;
500                    }
501                    if (!canOverlap) continue;
502                }
503                boolean canOverLimit = false;
504                if (availableOnly) {
505                    for (Reservation r: getReservations(course)) {
506                        if (!r.canBatchAssignOverLimit()) continue;
507                        if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue;
508                        if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
509                        canOverLimit = true; break;
510                    }
511                }
512                if (!canOverLimit) {
513                    if (availableOnly && section.getLimit() >= 0
514                            && SectionLimit.getEnrollmentWeight(assignment, section, this) > section.getLimit())
515                        continue;
516                    if (config.getOffering().hasReservations()) {
517                        boolean hasReservation = false, hasSectionReservation = false, reservationMustBeUsed = false;
518                        for (Reservation r: getReservations(course)) {
519                            if (r.mustBeUsed()) reservationMustBeUsed = true;
520                            if (availableOnly && r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
521                            if (r.getSections(subpart) == null) {
522                                hasReservation = true;
523                            } else if (r.getSections(subpart).contains(section)) {
524                                hasReservation = true;
525                                hasSectionReservation = true;
526                            }
527                        }
528                        if (!hasSectionReservation && section.getTotalUnreservedSpace() < getWeight())
529                            continue;
530                        if (availableOnly && !hasSectionReservation && section.getUnreservedSpace(assignment, this) < getWeight())
531                            continue;
532                        if (!hasReservation && reservationMustBeUsed)
533                            continue;
534                    }
535                }
536                if (!getStudent().isAllowDisabled() && !section.isEnabled(getStudent())) {
537                    boolean allowDisabled = false;
538                    for (Reservation r: getReservations(course)) {
539                        if (!r.isAllowDisabled()) continue;
540                        if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue;
541                        if (!r.getConfigs().isEmpty() && !r.getConfigs().contains(config)) continue;
542                        allowDisabled = true; break;
543                    }
544                    if (!allowDisabled) continue;
545                }
546                if (skipSameTime && section.getTime() != null && !hasChildren && !times.add(section.getTime()) && !isSelected(section) && !isWaitlisted(section) && 
547                        (section.getIgnoreConflictWithSectionIds() == null || section.getIgnoreConflictWithSectionIds().isEmpty()))
548                    continue;
549                matchingSectionsThisSubpart.add(section);
550            }
551            if (random || limit > 0) {
552                sectionsThisSubpart = new ArrayList<Section>(sectionsThisSubpart);
553                Collections.shuffle(sectionsThisSubpart);
554            }
555            int i = 0;
556            for (Section section: matchingSectionsThisSubpart) {
557                sections.add(section);
558                computeEnrollments(assignment, enrollments, priority, penalty + section.getPenalty(), course, config, sections, idx + 1,
559                        availableOnly, skipSameTime, selectedOnly, random,
560                        limit < 0 ? limit : Math.max(1, limit * (1 + i) / matchingSectionsThisSubpart.size()),
561                        checkParent);
562                sections.remove(section);
563                i++;
564            }
565        }
566    }
567
568    /** Return all enrollments that are available 
569     * @param assignment current assignment
570     * @return all available enrollments
571     **/
572    public List<Enrollment> getAvaiableEnrollments(Assignment<Request, Enrollment> assignment) {
573        return getAvaiableEnrollments(assignment, true);
574    }
575        
576    /** Return all enrollments that are available 
577     * @param assignment current assignment
578     * @return all available enrollments
579     **/
580    public List<Enrollment> getAvaiableEnrollments(Assignment<Request, Enrollment> assignment, boolean checkParent) {
581        List<Enrollment> ret = new ArrayList<Enrollment>();
582        if (isFixed()) {
583            ret.add(getFixedValue());
584            return ret;
585        }
586        if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) {
587            ret.add(getInitialAssignment());
588            return ret;
589        }
590        int idx = 0;
591        for (Course course : iCourses) {
592            for (Config config : course.getOffering().getConfigs()) {
593                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, true, false, false, false,
594                        getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), checkParent);
595            }
596            idx++;
597        }
598        return ret;
599    }
600
601    /**
602     * Return all enrollments of the first course that are selected (
603     * {@link CourseRequest#isSelected(Section)} is true)
604     * 
605     * @param assignment current assignment
606     * @param availableOnly
607     *            pick only available sections
608     * @return selected enrollments
609     */
610    public List<Enrollment> getSelectedEnrollments(Assignment<Request, Enrollment> assignment, boolean availableOnly) {
611        if (getSelectedChoices().isEmpty())
612            return null;
613        List<Enrollment> enrollments = new ArrayList<Enrollment>();
614        if (isFixed()) return enrollments;
615        if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments())
616            return enrollments;
617        for (Course course : iCourses) {
618            boolean hasChoice = false;
619            for (Choice choice: getSelectedChoices())
620                if (course.getOffering().equals(choice.getOffering())) { hasChoice = true; break; }
621            if (hasChoice)
622                for (Config config : course.getOffering().getConfigs()) {
623                    computeEnrollments(assignment, enrollments, 0, 0, course, config, new HashSet<Section>(), 0, availableOnly, false, true, false, -1, false);
624                }
625            break;
626        }
627        return enrollments;
628    }
629
630    /**
631     * Return all enrollments that are available, pick only the first section of
632     * the sections with the same time (of each subpart, {@link Section}
633     * comparator is used)
634     * @param assignment current assignment
635     * @return available enrollments 
636     */
637    public List<Enrollment> getAvaiableEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment) {
638        return getAvaiableEnrollmentsSkipSameTime(assignment, true);
639    }
640    
641    /**
642     * Return all enrollments that are available, pick only the first section of
643     * the sections with the same time (of each subpart, {@link Section}
644     * comparator is used)
645     * @param assignment current assignment
646     * @param checkParent check course dependency with other assigned courses
647     * @return available enrollments 
648     */
649    public List<Enrollment> getAvaiableEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment, boolean checkParent) {
650        List<Enrollment> ret = new ArrayList<Enrollment>();
651        if (isFixed()) {
652            ret.add(getFixedValue());
653            return ret;
654        }
655        if (getInitialAssignment() != null) {
656            ret.add(getInitialAssignment());
657            if (getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments())
658                return ret;
659        }
660        int idx = 0;
661        for (Course course : iCourses) {
662            boolean skipSameTime = true;
663            for (LinkedSections link: getStudent().getLinkedSections())
664                if (link.getOfferings().contains(course.getOffering())) { skipSameTime = false; break; }
665            for (Config config : course.getOffering().getConfigs()) {
666                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, true, skipSameTime, false, false,
667                        getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), checkParent);
668            }
669            idx++;
670        }
671        return ret;
672    }
673
674    /**
675     * Return all possible enrollments, but pick only the first section of
676     * the sections with the same time (of each subpart, {@link Section}
677     * comparator is used).
678     * @param assignment current assignment
679     * @return computed enrollments 
680     */
681    public List<Enrollment> getEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment) {
682        List<Enrollment> ret = new ArrayList<Enrollment>();
683        if (isFixed()) {
684            ret.add(getFixedValue());
685            return ret;
686        }
687        if (getInitialAssignment() != null) {
688            ret.add(getInitialAssignment());
689            if (getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments())
690                return ret;
691        }
692        int idx = 0;
693        for (Course course : iCourses) {
694            for (Config config : course.getOffering().getConfigs()) {
695                boolean skipSameTime = true;
696                for (LinkedSections link: getStudent().getLinkedSections())
697                    if (link.getOfferings().contains(course.getOffering())) { skipSameTime = false; break; }
698                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, skipSameTime, false, false,
699                        getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), false);
700            }
701            idx++;
702        }
703        return ret;
704    }
705
706    /** Wait-listed choices 
707     * @return wait-listed choices
708     **/
709    public Set<Choice> getWaitlistedChoices() {
710        return iWaitlistedChoices;
711    }
712
713    /**
714     * Return true when the given section is wait-listed (i.e., its choice is
715     * among wait-listed choices)
716     * @param section given section
717     * @return true if the given section matches the wait-listed choices
718     */
719    public boolean isWaitlisted(Section section) {
720        for (Choice choice: iWaitlistedChoices)
721            if (choice.sameChoice(section)) return true;
722        return false;
723    }
724
725    /** Selected choices 
726     * @return selected choices
727     **/
728    public Set<Choice> getSelectedChoices() {
729        return iSelectedChoices;
730    }
731    
732    /**
733     * Return true when the given section is selected (i.e., its choice is among
734     * selected choices)
735     * @param section given section
736     * @return true if the given section matches the selected choices
737     */
738    public boolean isSelected(Section section) {
739        for (Choice choice: iSelectedChoices)
740            if (choice.sameSection(section) || choice.sameConfiguration(section)) return true;
741        return false;
742    }
743    
744    /**
745     * Return true when the given section has a preference (i.e., there is a matching selection),
746     * or when there is a section preference for a different configuration
747     * @param section given section
748     * @return true if the there is a matching choice for the given section that is of the same offering
749     */
750    public boolean hasSelection(Section section) {
751        boolean hasSectionChoices = false, hasSectionChoicesThisConfig = false;
752        for (Choice choice: iSelectedChoices) {
753            if (choice.sameOffering(section)) {
754                if (choice.isMatching(section)) return true;
755                if (choice.getSubpartId() != null) {
756                    hasSectionChoices = true;
757                    for (Subpart subpart: section.getSubpart().getConfig().getSubparts()) {
758                        if (choice.getSubpartId().equals(subpart.getId())) { hasSectionChoicesThisConfig = true; }
759                    }
760                }
761            }
762        }
763        return (hasSectionChoices && !hasSectionChoicesThisConfig);
764    }
765    
766    /**
767     * Required choices
768     * @return required choices
769     */
770    public Set<Choice> getRequiredChoices() {
771        return iRequiredChoices;
772    }
773    
774    /**
775     * Return true when the given section is required (i.e., its choice is among required choices, or there are no requirements)
776     * @param section given section
777     * @return true if the given section matches the required choices
778     */
779    public boolean isRequired(Section section) {
780        if (iRequiredChoices.isEmpty()) return true;
781        boolean hasConfig = false, hasMatchingConfig = false;
782        boolean hasSubpart = false, hasMatchingSection = false;
783        boolean hasSectionReq = false;
784        for (Choice choice: iRequiredChoices) {
785            // different offering -> skip
786            if (!choice.getOffering().equals(section.getSubpart().getConfig().getOffering())) continue;
787            // has config -> check config
788            if (choice.getConfigId() != null) {
789                hasConfig = true;
790                if (choice.sameConfiguration(section)) hasMatchingConfig = true;
791            }
792            // has section of the matching subpart -> check section
793            if (choice.getSubpartId() != null) {
794                hasSectionReq = true;
795                if (choice.getSubpartId().equals(section.getSubpart().getId())) {
796                    hasSubpart = true;
797                    if (choice.sameSection(section)) hasMatchingSection = true;
798                } else if (!hasMatchingConfig) {
799                    for (Subpart subpart: section.getSubpart().getConfig().getSubparts()) {
800                        if (choice.getSubpartId().equals(subpart.getId())) {
801                            hasMatchingConfig = true;
802                            break;
803                        }
804                    }
805                }
806            }
807        }
808        if (hasConfig && !hasMatchingConfig) return false;
809        if (hasSubpart && !hasMatchingSection) return false;
810        // no match, but there are section requirements for a different config -> not satisfied 
811        if (!hasMatchingConfig && !hasMatchingSection && hasSectionReq) return false;
812        return true;
813    }
814
815    /**
816     * Request name: A for alternative, 1 + priority, (w) when wait-list, list of
817     * course names
818     */
819    @Override
820    public String getName() {
821        String ret = (isAlternative() ? "A" : "")
822                + (1 + getPriority() + (isAlternative() ? -getStudent().nrRequests() : 0)) + ". "
823                + (getRequestPriority() != RequestPriority.Normal ?
824                  (isWaitlist() ? "(" + getRequestPriority().getAbbreviation() + "w) " : "(" + getRequestPriority().getAbbreviation() + ") ")
825                  : isWaitlist() ? "(w) " : "");
826        int idx = 0;
827        for (Course course : iCourses) {
828            if (idx == 0)
829                ret += course.getName();
830            else
831                ret += ", " + idx + ". alt " + course.getName();
832            idx++;
833        }
834        if (getStudent().getExternalId() != null)
835            ret = "[" + getStudent().getExternalId() + "] " + ret;
836        return ret;
837    }
838
839    /**
840     * True if the student can be put on a wait-list (no alternative course
841     * request will be given instead)
842     * @return true if the request can be wait-listed
843     */
844    public boolean isWaitlist() {
845        return iWaitlist;
846    }
847    
848    /**
849     * True if the student can be put on a wait-list (no alternative course
850     * request will be given instead)
851     * @param waitlist true if the request can be wait-listed
852     */
853    public void setWaitlist(boolean waitlist) {
854        iWaitlist = waitlist;
855    }
856    
857    /**
858     * True if the course request is critical for the student in order to move forward in their degree 
859     * @param critical true if the request is critical
860     */
861    @Deprecated
862    public void setCritical(boolean critical) {
863        setRequestPriority(critical ? RequestPriority.Critical : RequestPriority.Normal);
864    }
865    
866    /**
867     * Time stamp of the request
868     * @return request time stamp
869     */
870    public Long getTimeStamp() {
871        return iTimeStamp;
872    }
873
874    @Override
875    public String toString() {
876        return getName() + (getWeight() != 1.0 ? " (W:" + sDF.format(getWeight()) + ")" : "");
877    }
878
879    /** Return course of the requested courses with the given id 
880     * @param courseId course offering id
881     * @return course of the given id
882     **/
883    public Course getCourse(long courseId) {
884        for (Course course : iCourses) {
885            if (course.getId() == courseId)
886                return course;
887        }
888        return null;
889    }
890
891    /** Return configuration of the requested courses with the given id 
892     * @param configId instructional offering configuration unique id
893     * @return config of the given id
894     **/
895    public Config getConfig(long configId) {
896        for (Course course : iCourses) {
897            for (Config config : course.getOffering().getConfigs()) {
898                if (config.getId() == configId)
899                    return config;
900            }
901        }
902        return null;
903    }
904
905    /** Return subpart of the requested courses with the given id 
906     * @param subpartId scheduling subpart unique id
907     * @return subpart of the given id
908     **/
909    public Subpart getSubpart(long subpartId) {
910        for (Course course : iCourses) {
911            for (Config config : course.getOffering().getConfigs()) {
912                for (Subpart subpart : config.getSubparts()) {
913                    if (subpart.getId() == subpartId)
914                        return subpart;
915                }
916            }
917        }
918        return null;
919    }
920
921    /** Return section of the requested courses with the given id 
922     * @param sectionId class unique id
923     * @return section of the given id
924     **/
925    public Section getSection(long sectionId) {
926        for (Course course : iCourses) {
927            for (Config config : course.getOffering().getConfigs()) {
928                for (Subpart subpart : config.getSubparts()) {
929                    for (Section section : subpart.getSections()) {
930                        if (section.getId() == sectionId)
931                            return section;
932                    }
933                }
934            }
935        }
936        return null;
937    }
938
939    /**
940     * Minimal penalty (minimum of {@link Offering#getMinPenalty()} among
941     * requested courses)
942     * @return minimal penalty
943     */
944    public double getMinPenalty() {
945        if (iCachedMinPenalty == null) {
946            double min = Double.MAX_VALUE;
947            for (Course course : iCourses) {
948                min = Math.min(min, course.getOffering().getMinPenalty());
949            }
950            iCachedMinPenalty = Double.valueOf(min);
951        }
952        return iCachedMinPenalty.doubleValue();
953    }
954
955    /**
956     * Maximal penalty (maximum of {@link Offering#getMaxPenalty()} among
957     * requested courses)
958     * @return maximal penalty
959     */
960    public double getMaxPenalty() {
961        if (iCachedMaxPenalty == null) {
962            double max = Double.MIN_VALUE;
963            for (Course course : iCourses) {
964                max = Math.max(max, course.getOffering().getMaxPenalty());
965            }
966            iCachedMaxPenalty = Double.valueOf(max);
967        }
968        return iCachedMaxPenalty.doubleValue();
969    }
970
971    /** Clear cached min/max penalties and cached bound */
972    public void clearCache() {
973        iCachedMaxPenalty = null;
974        iCachedMinPenalty = null;
975    }
976
977    /**
978     * Estimated bound for this request -- it estimates the smallest value among
979     * all possible enrollments
980     */
981    @Override
982    public double getBound() {
983        return - getWeight() * ((StudentSectioningModel)getModel()).getStudentWeights().getBound(this);
984        /*
985        if (iCachedBound == null) {
986            iCachedBound = Double.valueOf(-Math.pow(Enrollment.sPriorityWeight, getPriority())
987                    * (isAlternative() ? Enrollment.sAlterativeWeight : 1.0)
988                    * Math.pow(Enrollment.sInitialWeight, (getInitialAssignment() == null ? 0 : 1))
989                    * Math.pow(Enrollment.sSelectedWeight, (iSelectedChoices.isEmpty() ? 0 : 1))
990                    * Math.pow(Enrollment.sWaitlistedWeight, (iWaitlistedChoices.isEmpty() ? 0 : 1))
991                    *
992                    // Math.max(Enrollment.sMinWeight,getWeight()) *
993                    (getStudent().isDummy() ? Student.sDummyStudentWeight : 1.0)
994                    * Enrollment.normalizePenalty(getMinPenalty()));
995        }
996        return iCachedBound.doubleValue();
997        */
998    }
999
1000    /** Return true if request is assigned. */
1001    @Override
1002    public boolean isAssigned(Assignment<Request, Enrollment> assignment) {
1003        Enrollment e = assignment.getValue(this);
1004        return e != null && !e.getAssignments().isEmpty();
1005    }
1006
1007    @Override
1008    public boolean equals(Object o) {
1009        return super.equals(o) && (o instanceof CourseRequest);
1010    }
1011    
1012    /**
1013     * Get reservations for this course requests
1014     * @param course given course
1015     * @return reservations for this course requests and the given course
1016     */
1017    public synchronized List<Reservation> getReservations(Course course) {
1018        if (iReservations == null)
1019            iReservations = new HashMap<Course, List<Reservation>>();
1020        List<Reservation> reservations = iReservations.get(course);
1021        if (reservations == null) {
1022            reservations = new ArrayList<Reservation>();
1023            boolean mustBeUsed = false;
1024            for (Reservation r: course.getOffering().getReservations()) {
1025                if (!r.isApplicable(getStudent(), course)) continue;
1026                if (!mustBeUsed && r.mustBeUsed()) { reservations.clear(); mustBeUsed = true; }
1027                if (mustBeUsed && !r.mustBeUsed()) continue;
1028                reservations.add(r);
1029            }
1030            iReservations.put(course, reservations);
1031        }
1032        return reservations;
1033    }
1034    private Map<Course, List<Reservation>> iReservations = null;
1035    
1036    /**
1037     * Get reservations for this course requests ordered using {@link Reservation#compareTo(Assignment, Reservation)}
1038     * @param course given course
1039     * @return reservations for this course requests and the given course
1040     */
1041    public TreeSet<Reservation> getSortedReservations(Assignment<Request, Enrollment> assignment, Course course) {
1042        TreeSet<Reservation> reservations = new TreeSet<Reservation>(new AssignmentComparator<Reservation, Request, Enrollment>(assignment));
1043        reservations.addAll(getReservations(course));
1044        return reservations;
1045    }
1046    
1047    /**
1048     * Return true if there is a reservation for a course of this request
1049     * @return true if there is a reservation for a course of this request
1050     */
1051    public boolean hasReservations() {
1052        for (Course course: getCourses())
1053            if (!getReservations(course).isEmpty())
1054                return true;
1055        return false;
1056    }
1057    
1058    /**
1059     * Clear reservation information that was cached on this section
1060     */
1061    public synchronized void clearReservationCache() {
1062        if (iReservations != null) iReservations.clear();
1063    }
1064    
1065    /**
1066     * Get restrictions for this course requests
1067     * @param course given course
1068     * @return restrictions for this course requests and the given course
1069     */
1070    public synchronized List<Restriction> getRestrictions(Course course) {
1071        if (iRestrictions == null)
1072            iRestrictions = new HashMap<Course, List<Restriction>>();
1073        List<Restriction> restrictions = iRestrictions.get(course);
1074        if (restrictions == null) {
1075            restrictions = new ArrayList<Restriction>();
1076            for (Restriction r: course.getOffering().getRestrictions()) {
1077                if (r.isApplicable(getStudent(), course))
1078                    restrictions.add(r);
1079            }
1080            iRestrictions.put(course, restrictions);
1081        }
1082        return restrictions;
1083    }
1084    private Map<Course, List<Restriction>> iRestrictions = null;
1085    
1086    /**
1087     * Return true if there is a restriction for a course of this request
1088     * @return true if there is a restriction for a course of this request
1089     */
1090    public boolean hasRestrictions(Course course) {
1091        return !getRestrictions(course).isEmpty();
1092    }
1093    
1094    /**
1095     * Return true when there are restrictions for a course of this course request and the given config does not meet any of them
1096     */
1097    public boolean isNotAllowed(Course course, Config config) {
1098        List<Restriction> restrictions = getRestrictions(course);
1099        if (restrictions.isEmpty()) return false;
1100        for (Restriction r: restrictions)
1101            if (r.isIncluded(config)) return false;
1102        return true;
1103    }
1104    
1105    /**
1106     * Return true when there are restrictions for a course of this course request and the given section does not meet any of them
1107     */
1108    public boolean isNotAllowed(Course course, Section section) {
1109        List<Restriction> restrictions = getRestrictions(course);
1110        if (restrictions.isEmpty()) return false;
1111        for (Restriction r: restrictions)
1112            if (r.isIncluded(section)) return false;
1113        return true;
1114    }
1115    
1116    /**
1117     * Return true when there are restrictions for a course of this course request and the given enrollment does not meet any of them
1118     */
1119    public boolean isNotAllowed(Enrollment e) {
1120        List<Restriction> restrictions = getRestrictions(e.getCourse());
1121        if (restrictions.isEmpty()) return false;
1122        for (Restriction r: restrictions)
1123            if (r.isIncluded(e)) return false;
1124        return true;
1125    }
1126    
1127    /**
1128     * Clear restriction information that was cached on this request
1129     */
1130    public synchronized void clearRestrictionCache() {
1131        if (iRestrictions != null) iRestrictions.clear();
1132    }
1133    
1134    /**
1135     * Return true if this request can track MPP
1136     * @return true if the request is course request and it either has an initial enrollment
1137     */
1138    @Override
1139    public boolean isMPP() {
1140        StudentSectioningModel model = (StudentSectioningModel) getModel();
1141        if (model == null || !model.isMPP()) return false;
1142        return !getStudent().isDummy() && getInitialAssignment() != null; 
1143    }
1144    
1145    /**
1146     * Return true if this request has any selection
1147     * @return true if the request is course request and has some selected choices.
1148     */
1149    @Override
1150    public boolean hasSelection() {
1151        if (getStudent().isDummy() || getSelectedChoices().isEmpty()) return false;
1152        for (Choice choice: getSelectedChoices())
1153            if (choice.getSectionId() != null || choice.getConfigId() != null) return true;
1154        return false;
1155    }
1156    
1157    /**
1158     * Add request group to this request.
1159     * @param group request group to be added
1160     */
1161    public void addRequestGroup(RequestGroup group) {
1162        iRequestGroups.add(group);
1163        group.addRequest(this);
1164    }
1165    
1166    /**
1167     * Removed request group from this request.
1168     * @param group request group to be removed
1169     */
1170    public void removeRequestGroup(RequestGroup group) {
1171        iRequestGroups.remove(group);
1172        group.removeRequest(this);
1173    }
1174
1175    /**
1176     * Lists request groups of this request
1177     * @return request groups of this course requests
1178     */
1179    public Set<RequestGroup> getRequestGroups() {
1180        return iRequestGroups;
1181    }
1182    
1183    @Override
1184    public void variableAssigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment enrollment) {
1185        super.variableAssigned(assignment, iteration, enrollment);
1186        for (RequestGroup g: getRequestGroups())
1187            if (g.getCourse().equals(enrollment.getCourse()))
1188                g.assigned(assignment, enrollment);
1189    }
1190
1191    @Override
1192    public void variableUnassigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment enrollment) {
1193        super.variableUnassigned(assignment, iteration, enrollment);
1194        for (RequestGroup g: getRequestGroups())
1195            if (g.getCourse().equals(enrollment.getCourse()))
1196                g.unassigned(assignment, enrollment);
1197        /*
1198        if (enrollment != null && enrollment.getCourse() != null && enrollment.getCourse().hasChildren()) {
1199            for (Request r: enrollment.getStudent().getRequests()) {
1200                if (r.equals(enrollment.getRequest())) continue;
1201                Enrollment e = assignment.getValue(r);
1202                if (e != null && e.getCourse() != null && enrollment.getCourse().equals(e.getCourse().getParent())) {
1203                    assignment.unassign(iteration, r);
1204                }
1205            }
1206        }*/
1207    }
1208
1209    @Override
1210    public float getMinCredit() {
1211        Float credit = null;
1212        for (Course course: getCourses()) {
1213            if (course.hasCreditValue() && (credit == null || credit > course.getCreditValue()))
1214                    credit = course.getCreditValue();
1215            for (Config config: course.getOffering().getConfigs()) {
1216                Float configCredit = config.getCreditValue();
1217                if (configCredit != null && (credit == null || credit > configCredit))
1218                        credit = configCredit;
1219            }
1220        }
1221        return (credit == null ? 0 : credit.floatValue());
1222    }
1223    
1224    public boolean isFixed() { return iFixed != null; }
1225    public Enrollment getFixedValue() { return iFixed; }
1226    public void setFixedValue(Enrollment constant) { iFixed = constant; }
1227    
1228    @Override
1229    public boolean hasCourse(Course course) {
1230        return iCourses.contains(course);
1231    }
1232    
1233    @Override
1234    public boolean hasChildren() {
1235        for (Course course: iCourses)
1236            if (course.hasChildren()) return true;
1237        return false;
1238    }
1239}