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