001package org.cpsolver.studentsct.model;
002
003import java.text.DecimalFormat;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.Iterator;
009import java.util.List;
010import java.util.Map;
011import java.util.Set;
012
013import org.cpsolver.coursett.model.Placement;
014import org.cpsolver.coursett.model.RoomLocation;
015import org.cpsolver.coursett.model.TimeLocation;
016import org.cpsolver.ifs.assignment.Assignment;
017import org.cpsolver.ifs.assignment.AssignmentComparable;
018import org.cpsolver.ifs.assignment.context.AbstractClassWithContext;
019import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
020import org.cpsolver.ifs.assignment.context.CanInheritContext;
021import org.cpsolver.ifs.model.Model;
022import org.cpsolver.studentsct.StudentSectioningModel;
023import org.cpsolver.studentsct.model.Student.ModalityPreference;
024import org.cpsolver.studentsct.reservation.Reservation;
025
026
027/**
028 * Representation of a class. Each section contains id, name, scheduling
029 * subpart, time/room placement, and a limit. Optionally, parent-child relation
030 * between sections can be defined. <br>
031 * <br>
032 * Each student requesting a course needs to be enrolled in a class of each
033 * subpart of a selected configuration. In the case of parent-child relation
034 * between classes, if a student is enrolled in a section that has a parent
035 * section defined, he/she has to be enrolled in the parent section as well. If
036 * there is a parent-child relation between two sections, the same relation is
037 * defined on their subparts as well, i.e., if section A is a parent section B,
038 * subpart of section A isa parent of subpart of section B. <br>
039 * <br>
040 * 
041 * @author  Tomáš Müller
042 * @version StudentSct 1.3 (Student Sectioning)<br>
043 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
044 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
045 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
046 * <br>
047 *          This library is free software; you can redistribute it and/or modify
048 *          it under the terms of the GNU Lesser General Public License as
049 *          published by the Free Software Foundation; either version 3 of the
050 *          License, or (at your option) any later version. <br>
051 * <br>
052 *          This library is distributed in the hope that it will be useful, but
053 *          WITHOUT ANY WARRANTY; without even the implied warranty of
054 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
055 *          Lesser General Public License for more details. <br>
056 * <br>
057 *          You should have received a copy of the GNU Lesser General Public
058 *          License along with this library; if not see
059 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
060 */
061public class Section extends AbstractClassWithContext<Request, Enrollment, Section.SectionContext> implements SctAssignment, AssignmentComparable<Section, Request, Enrollment>, CanInheritContext<Request, Enrollment, Section.SectionContext>{
062    private static DecimalFormat sDF = new DecimalFormat("0.000");
063    private long iId = -1;
064    private String iName = null;
065    private Map<Long, String> iNameByCourse = null;
066    private Subpart iSubpart = null;
067    private Section iParent = null;
068    private Placement iPlacement = null;
069    private int iLimit = 0;
070    private List<Instructor> iInstructors = null;
071    private double iPenalty = 0.0;
072    private double iSpaceExpected = 0.0;
073    private double iSpaceHeld = 0.0;
074    private String iNote = null;
075    private Set<Long> iIgnoreConflictsWith = null;
076    private boolean iCancelled = false, iEnabled = true, iOnline = false, iPast = false;
077    private List<Unavailability> iUnavailabilities = new ArrayList<Unavailability>();
078
079    /**
080     * Constructor
081     * 
082     * @param id
083     *            section unique id
084     * @param limit
085     *            section limit, i.e., the maximal number of students that can
086     *            be enrolled in this section at the same time
087     * @param name
088     *            section name
089     * @param subpart
090     *            subpart of this section
091     * @param placement
092     *            time/room placement
093     * @param instructors
094     *            assigned instructor(s)
095     * @param parent
096     *            parent section -- if there is a parent section defined, a
097     *            student that is enrolled in this section has to be enrolled in
098     *            the parent section as well. Also, the same relation needs to
099     *            be defined between subpart of this section and the subpart of
100     *            the parent section
101     */
102    public Section(long id, int limit, String name, Subpart subpart, Placement placement, List<Instructor> instructors, Section parent) {
103        iId = id;
104        iLimit = limit;
105        iName = name;
106        iSubpart = subpart;
107        if (iSubpart != null)
108            iSubpart.getSections().add(this);
109        iPlacement = placement;
110        iParent = parent;
111        iInstructors = instructors;
112    }
113    
114    /**
115     * Constructor
116     * 
117     * @param id
118     *            section unique id
119     * @param limit
120     *            section limit, i.e., the maximal number of students that can
121     *            be enrolled in this section at the same time
122     * @param name
123     *            section name
124     * @param subpart
125     *            subpart of this section
126     * @param placement
127     *            time/room placement
128     * @param instructors
129     *            assigned instructor(s)
130     * @param parent
131     *            parent section -- if there is a parent section defined, a
132     *            student that is enrolled in this section has to be enrolled in
133     *            the parent section as well. Also, the same relation needs to
134     *            be defined between subpart of this section and the subpart of
135     *            the parent section
136     */
137    public Section(long id, int limit, String name, Subpart subpart, Placement placement, Section parent, Instructor... instructors) {
138        this(id, limit, name, subpart, placement, Arrays.asList(instructors), parent);
139    }
140    
141    @Deprecated
142    public Section(long id, int limit, String name, Subpart subpart, Placement placement, String instructorIds, String instructorNames, Section parent) {
143        this(id, limit, name, subpart, placement, Instructor.toInstructors(instructorIds, instructorNames), parent);
144    }
145
146    /** Section id */
147    @Override
148    public long getId() {
149        return iId;
150    }
151
152    /**
153     * Section limit. This is defines the maximal number of students that can be
154     * enrolled into this section at the same time. It is -1 in the case of an
155     * unlimited section
156     * @return class limit
157     */
158    public int getLimit() {
159        return iLimit;
160    }
161
162    /** Set section limit 
163     * @param limit class limit
164     **/
165    public void setLimit(int limit) {
166        iLimit = limit;
167    }
168
169    /** Section name 
170     * @return class name
171     **/
172    public String getName() {
173        return iName;
174    }
175    
176    /** Set section name 
177     * @param name class name
178     **/
179    public void setName(String name) {
180        iName = name;
181    }
182
183    /** Scheduling subpart to which this section belongs 
184     * @return scheduling subpart
185     **/
186    public Subpart getSubpart() {
187        return iSubpart;
188    }
189
190    /**
191     * Parent section of this section (can be null). If there is a parent
192     * section defined, a student that is enrolled in this section has to be
193     * enrolled in the parent section as well. Also, the same relation needs to
194     * be defined between subpart of this section and the subpart of the parent
195     * section.
196     * @return parent class
197     */
198    public Section getParent() {
199        return iParent;
200    }
201
202    /**
203     * Time/room placement of the section. This can be null, for arranged
204     * sections.
205     * @return time and room assignment of this class
206     */
207    public Placement getPlacement() {
208        return iPlacement;
209    }
210    
211    /**
212     * Set time/room placement of the section. This can be null, for arranged
213     * sections.
214     * @param placement time and room assignment of this class
215     */
216    public void setPlacement(Placement placement) {
217        iPlacement = placement;
218    }
219
220    /** Time placement of the section. */
221    @Override
222    public TimeLocation getTime() {
223        return (iPlacement == null ? null : iPlacement.getTimeLocation());
224    }
225    
226    /** Check if the class has a time assignment (is not arranged hours) */
227    public boolean hasTime() {
228        return iPlacement != null && iPlacement.getTimeLocation() != null && iPlacement.getTimeLocation().getDayCode() != 0;
229    }
230    
231    /** True if the instructional type is the same */
232    public boolean sameInstructionalType(Section section) {
233        return getSubpart().getInstructionalType().equals(section.getSubpart().getInstructionalType());
234    }
235
236    /** True if the time assignment is the same */
237    public boolean sameTime(Section section) {
238        return getTime() == null ? section.getTime() == null : getTime().equals(section.getTime());
239    }
240    
241    /** True if the instructor(s) are the same */
242    public boolean sameInstructors(Section section) {
243        if (nrInstructors() != section.nrInstructors()) return false;
244        return !hasInstructors() || getInstructors().containsAll(section.getInstructors());
245    }
246    
247    /** True if the time assignment as well as the instructor(s) are the same */
248    public boolean sameChoice(Section section) {
249        return sameInstructionalType(section) && sameTime(section) && sameInstructors(section);
250    }
251
252    /** Number of rooms in which the section meet. */
253    @Override
254    public int getNrRooms() {
255        return (iPlacement == null ? 0 : iPlacement.getNrRooms());
256    }
257
258    /**
259     * Room placement -- list of
260     * {@link org.cpsolver.coursett.model.RoomLocation}
261     */
262    @Override
263    public List<RoomLocation> getRooms() {
264        if (iPlacement == null)
265            return null;
266        if (iPlacement.getRoomLocations() == null && iPlacement.getRoomLocation() != null) {
267            List<RoomLocation> ret = new ArrayList<RoomLocation>(1);
268            ret.add(iPlacement.getRoomLocation());
269            return ret;
270        }
271        return iPlacement.getRoomLocations();
272    }
273
274    /**
275     * True, if this section overlaps with the given assignment in time and
276     * space
277     */
278    @Override
279    public boolean isOverlapping(SctAssignment assignment) {
280        if (isAllowOverlap() || assignment.isAllowOverlap()) return false;
281        if (getTime() == null || assignment.getTime() == null) return false;
282        if (assignment instanceof Section && isToIgnoreStudentConflictsWith(assignment.getId())) return false;
283        return getTime().hasIntersection(assignment.getTime());
284    }
285
286    /**
287     * True, if this section overlaps with one of the given set of assignments
288     * in time and space
289     */
290    @Override
291    public boolean isOverlapping(Set<? extends SctAssignment> assignments) {
292        if (isAllowOverlap()) return false;
293        if (getTime() == null || assignments == null)
294            return false;
295        for (SctAssignment assignment : assignments) {
296            if (assignment.isAllowOverlap())
297                continue;
298            if (assignment.getTime() == null)
299                continue;
300            if (assignment instanceof Section && isToIgnoreStudentConflictsWith(assignment.getId()))
301                continue;
302            if (getTime().hasIntersection(assignment.getTime()))
303                return true;
304        }
305        return false;
306    }
307
308    /** Called when an enrollment with this section is assigned to a request */
309    @Override
310    public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
311        getContext(assignment).assigned(assignment, enrollment);
312    }
313
314    /** Called when an enrollment with this section is unassigned from a request */
315    @Override
316    public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
317        getContext(assignment).unassigned(assignment, enrollment);
318    }
319
320    /** Long name: subpart name + time long name + room names + instructor names
321     * @param useAmPm use 12-hour format 
322     * @return long name
323     **/
324    public String getLongName(boolean useAmPm) {
325        return getSubpart().getName() + " " + getName() + " " + (getTime() == null ? "" : " " + getTime().getLongName(useAmPm))
326                + (getNrRooms() == 0 ? "" : " " + getPlacement().getRoomName(","))
327                + (hasInstructors() ? " " + getInstructorNames(",") : "");
328    }
329    
330    @Deprecated
331    public String getLongName() {
332        return getLongName(true);
333    }
334
335    @Override
336    public String toString() {
337        return getSubpart().getConfig().getOffering().getName() + " " + getSubpart().getName() + " " + getName()
338                + (getTime() == null ? "" : " " + getTime().getLongName(true))
339                + (getNrRooms() == 0 ? "" : " " + getPlacement().getRoomName(","))
340                + (hasInstructors() ? " " + getInstructorNames(",") : "") + " (L:"
341                + (getLimit() < 0 ? "unlimited" : "" + getLimit())
342                + (getPenalty() == 0.0 ? "" : ",P:" + sDF.format(getPenalty())) + ")";
343    }
344
345    /** Instructors assigned to this section 
346     * @return list of instructors
347     **/
348    public List<Instructor> getInstructors() {
349        return iInstructors;
350    }
351    
352    /**
353     * Has any instructors assigned
354     * @return return true if there is at least one instructor assigned
355     */
356    public boolean hasInstructors() {
357        return iInstructors != null && !iInstructors.isEmpty();
358    }
359    
360    /**
361     * Return number of instructors of this section
362     * @return number of assigned instructors
363     */
364    public int nrInstructors() {
365        return iInstructors == null ? 0 : iInstructors.size();
366    }
367    
368    /**
369     * Instructor names
370     * @param delim delimiter
371     * @return instructor names
372     */
373    public String getInstructorNames(String delim) {
374        if (iInstructors == null || iInstructors.isEmpty()) return "";
375        StringBuffer sb = new StringBuffer();
376        for (Iterator<Instructor> i = iInstructors.iterator(); i.hasNext(); ) {
377            Instructor instructor = i.next();
378            sb.append(instructor.getName() != null ? instructor.getName() : instructor.getExternalId() != null ? instructor.getExternalId() : "I" + instructor.getId());
379            if (i.hasNext()) sb.append(delim);
380        }
381        return sb.toString();
382    }
383
384    /**
385     * Return penalty which is added to an enrollment that contains this
386     * section.
387     * @return online penalty
388     */
389    public double getPenalty() {
390        return iPenalty;
391    }
392
393    /** Set penalty which is added to an enrollment that contains this section. 
394     * @param penalty online penalty
395     **/
396    public void setPenalty(double penalty) {
397        iPenalty = penalty;
398    }
399
400    /**
401     * Compare two sections, prefer sections with lower penalty and more open
402     * space
403     */
404    @Override
405    public int compareTo(Assignment<Request, Enrollment> assignment, Section s) {
406        int cmp = Double.compare(getPenalty(), s.getPenalty());
407        if (cmp != 0)
408            return cmp;
409        cmp = Double.compare(
410                getLimit() < 0 ? getContext(assignment).getEnrollmentWeight(assignment, null) : getContext(assignment).getEnrollmentWeight(assignment, null) - getLimit(),
411                s.getLimit() < 0 ? s.getContext(assignment).getEnrollmentWeight(assignment, null) : s.getContext(assignment).getEnrollmentWeight(assignment, null) - s.getLimit());
412        if (cmp != 0)
413            return cmp;
414        return Double.compare(getId(), s.getId());
415    }
416    
417    /**
418     * Compare two sections, prefer sections with lower penalty
419     */
420    @Override
421    public int compareTo(Section s) {
422        int cmp = Double.compare(getPenalty(), s.getPenalty());
423        if (cmp != 0)
424            return cmp;
425        return Double.compare(getId(), s.getId());
426    }
427
428    /**
429     * Return the amount of space of this section that is held for incoming
430     * students. This attribute is computed during the batch sectioning (it is
431     * the overall weight of dummy students enrolled in this section) and it is
432     * being updated with each incomming student during the online sectioning.
433     * @return space held
434     */
435    public double getSpaceHeld() {
436        return iSpaceHeld;
437    }
438
439    /**
440     * Set the amount of space of this section that is held for incoming
441     * students. See {@link Section#getSpaceHeld()} for more info.
442     * @param spaceHeld space held
443     */
444    public void setSpaceHeld(double spaceHeld) {
445        iSpaceHeld = spaceHeld;
446    }
447
448    /**
449     * Return the amount of space of this section that is expected to be taken
450     * by incoming students. This attribute is computed during the batch
451     * sectioning (for each dummy student that can attend this section (without
452     * any conflict with other enrollments of that student), 1 / x where x is
453     * the number of such sections of this subpart is added to this value).
454     * Also, this value is being updated with each incoming student during the
455     * online sectioning.
456     * @return space expected
457     */
458    public double getSpaceExpected() {
459        return iSpaceExpected;
460    }
461
462    /**
463     * Set the amount of space of this section that is expected to be taken by
464     * incoming students. See {@link Section#getSpaceExpected()} for more info.
465     * @param spaceExpected space expected
466     */
467    public void setSpaceExpected(double spaceExpected) {
468        iSpaceExpected = spaceExpected;
469    }
470
471    /**
472     * Online sectioning penalty.
473     * @param assignment current assignment
474     * @return online sectioning penalty
475     */
476    public double getOnlineSectioningPenalty(Assignment<Request, Enrollment> assignment) {
477        if (getLimit() <= 0)
478            return 0.0;
479
480        double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, null);
481
482        double penalty = (getSpaceExpected() - available) / getLimit();
483
484        return Math.max(-1.0, Math.min(1.0, penalty));
485    }
486
487    /**
488     * Return true if overlaps are allowed, but the number of overlapping slots should be minimized.
489     * This can be changed on the subpart, using {@link Subpart#setAllowOverlap(boolean)}.
490     **/
491    @Override
492    public boolean isAllowOverlap() {
493        return iSubpart.isAllowOverlap();
494    }
495    
496    /** Sections first, then by {@link FreeTimeRequest#getId()} */
497    @Override
498    public int compareById(SctAssignment a) {
499        if (a instanceof Section) {
500            return Long.valueOf(getId()).compareTo(((Section)a).getId());
501        } else {
502            return -1;
503        }
504    }
505
506    /**
507     * Available space in the section that is not reserved by any section reservation
508     * @param assignment current assignment
509     * @param excludeRequest excluding given request (if not null)
510     * @return unreserved space in this class
511     **/
512    public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
513        // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 
514        // (in which case there is no unreserved space)
515        if (getLimit() < 0) {
516            // exclude reservations that are not directly set on this section
517            for (Reservation r: getSectionReservations()) {
518                // ignore expired reservations
519                if (r.isExpired()) continue;
520                // there is an unlimited reservation -> no unreserved space
521                if (r.getLimit(getSubpart().getConfig()) < 0) return 0.0;
522            }
523            return Double.MAX_VALUE;
524        }
525        
526        double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
527        // exclude reservations that are not directly set on this section
528        for (Reservation r: getSectionReservations()) {
529            // ignore expired reservations
530            if (r.isExpired()) continue;
531            // unlimited reservation -> all the space is reserved
532            if (r.getLimit(getSubpart().getConfig()) < 0.0) return 0.0;
533            // compute space that can be potentially taken by this reservation
534            double reserved = r.getContext(assignment).getReservedAvailableSpace(assignment, getSubpart().getConfig(), excludeRequest);
535            // deduct the space from available space
536            available -= Math.max(0.0, reserved);
537        }
538        
539        return available;
540    }
541    
542    /**
543     * Total space in the section that cannot be used by any section reservation
544     * @return total unreserved space in this class
545     **/
546    public synchronized double getTotalUnreservedSpace() {
547        if (iTotalUnreservedSpace == null)
548            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
549        return iTotalUnreservedSpace;
550    }
551    private Double iTotalUnreservedSpace = null;
552    private double getTotalUnreservedSpaceNoCache() {
553        // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 
554        // (in which case there is no unreserved space)
555        if (getLimit() < 0) {
556            // exclude reservations that are not directly set on this section
557            for (Reservation r: getSectionReservations()) {
558                // ignore expired reservations
559                if (r.isExpired()) continue;
560                // there is an unlimited reservation -> no unreserved space
561                if (r.getLimit(getSubpart().getConfig()) < 0) return 0.0;
562            }
563            return Double.MAX_VALUE;
564        }
565        
566        // we need to check all reservations linked with this section
567        double available = getLimit(), reserved = 0, exclusive = 0;
568        Set<Section> sections = new HashSet<Section>();
569        reservations: for (Reservation r: getSectionReservations()) {
570            // ignore expired reservations
571            if (r.isExpired()) continue;
572            // unlimited reservation -> no unreserved space
573            if (r.getLimit(getSubpart().getConfig()) < 0) return 0.0;
574            for (Section s: r.getSections(getSubpart())) {
575                if (s.equals(this)) continue;
576                if (s.getLimit() < 0) continue reservations;
577                if (sections.add(s))
578                    available += s.getLimit();
579            }
580            reserved += r.getLimit(getSubpart().getConfig());
581            if (r.getSections(getSubpart()).size() == 1)
582                exclusive += r.getLimit(getSubpart().getConfig());
583        }
584        
585        return Math.min(available - reserved, getLimit() - exclusive);
586    }
587    
588    
589    /**
590     * Get reservations for this section
591     * @return reservations that can use this class
592     */
593    public synchronized List<Reservation> getReservations() {
594        if (iReservations == null) {
595            iReservations = new ArrayList<Reservation>();
596            for (Reservation r: getSubpart().getConfig().getOffering().getReservations()) {
597                if (r.getSections(getSubpart()) == null || r.getSections(getSubpart()).contains(this))
598                    iReservations.add(r);
599            }
600        }
601        return iReservations;
602    }
603    private List<Reservation> iReservations = null;
604    
605    /**
606     * Get reservations that require this section
607     * @return reservations that must use this class
608     */
609    public synchronized List<Reservation> getSectionReservations() {
610        if (iSectionReservations == null) {
611            iSectionReservations = new ArrayList<Reservation>();
612            for (Reservation r: getSubpart().getSectionReservations()) {
613                if (r.getSections(getSubpart()).contains(this))
614                    iSectionReservations.add(r);
615            }
616        }
617        return iSectionReservations;
618    }
619    private List<Reservation> iSectionReservations = null;
620
621    /**
622     * Clear reservation information that was cached on this section
623     */
624    public synchronized void clearReservationCache() {
625        iReservations = null;
626        iSectionReservations = null;
627        iTotalUnreservedSpace = null;
628    }
629    
630    /**
631     * Return course-dependent section name
632     * @param courseId course offering unique id
633     * @return course dependent class name
634     */
635    public String getName(long courseId) {
636        if (iNameByCourse == null) return getName();
637        String name = iNameByCourse.get(courseId);
638        return (name == null ? getName() : name);
639    }
640    
641    /**
642     * Set course-dependent section name
643     * @param courseId course offering unique id
644     * @param name course dependent class name
645     */
646    public void setName(long courseId, String name) {
647        if (iNameByCourse == null) iNameByCourse = new HashMap<Long, String>();
648        iNameByCourse.put(courseId, name);
649    }
650
651    /**
652     * Return course-dependent section names
653     * @return map of course-dependent class names
654     */
655    public Map<Long, String> getNameByCourse() { return iNameByCourse; }
656    
657    @Override
658    public boolean equals(Object o) {
659        if (o == null || !(o instanceof Section)) return false;
660        return getId() == ((Section)o).getId();
661    }
662    
663    @Override
664    public int hashCode() {
665        return (int) (iId ^ (iId >>> 32));
666    }
667    
668    /**
669     * Section note
670     * @return scheduling note
671     */
672    public String getNote() { return iNote; }
673    
674    /**
675     * Section note
676     * @param note scheduling note
677     */
678    public void setNote(String note) { iNote = note; }
679    
680    /**
681     * Add section id of a section that student conflicts are to be ignored with
682     * @param sectionId class unique id
683     */
684    public void addIgnoreConflictWith(long sectionId) {
685        if (iIgnoreConflictsWith == null) iIgnoreConflictsWith = new HashSet<Long>();
686        iIgnoreConflictsWith.add(sectionId);
687    }
688    
689    /**
690     * Returns true if student conflicts between this section and the given one are to be ignored
691     * @param sectionId class unique id
692     * @return true if student conflicts between these two sections are to be ignored
693     */
694    public boolean isToIgnoreStudentConflictsWith(long sectionId) {
695        return iIgnoreConflictsWith != null && iIgnoreConflictsWith.contains(sectionId);
696    }
697    
698    /**
699     * Returns a set of ids of sections that student conflicts are to be ignored with (between this section and the others)
700     * @return set of class unique ids of the sections that student conflicts are to be ignored with 
701     */
702    public Set<Long> getIgnoreConflictWithSectionIds() {
703        return iIgnoreConflictsWith;
704    }
705    
706    /** Set of assigned enrollments */
707    @Override
708    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
709        return getContext(assignment).getEnrollments();
710    }
711    
712    /**
713     * Enrollment weight -- weight of all requests which have an enrollment that
714     * contains this section, excluding the given one. See
715     * {@link Request#getWeight()}.
716     * @param assignment current assignment
717     * @param excludeRequest course request to ignore, if any
718     * @return enrollment weight
719     */
720    public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
721        return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
722    }
723    
724    /**
725     * Enrollment weight including over the limit enrollments.
726     * That is enrollments that have reservation with {@link Reservation#canBatchAssignOverLimit()} set to true.
727     * @param assignment current assignment
728     * @param excludeRequest course request to ignore, if any
729     * @return enrollment weight
730     */
731    public double getEnrollmentTotalWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
732        return getContext(assignment).getEnrollmentTotalWeight(assignment, excludeRequest);
733    }
734    
735    /**
736     * Maximal weight of a single enrollment in the section
737     * @param assignment current assignment
738     * @return maximal enrollment weight
739     */
740    public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
741        return getContext(assignment).getMaxEnrollmentWeight();
742    }
743
744    /**
745     * Minimal weight of a single enrollment in the section
746     * @param assignment current assignment
747     * @return minimal enrollment weight
748     */
749    public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
750        return getContext(assignment).getMinEnrollmentWeight();
751    }
752    
753    /**
754     * Return cancelled flag of the class.
755     * @return true if the class is cancelled
756     */
757    public boolean isCancelled() { return iCancelled; }
758    
759    /**
760     * Set cancelled flag of the class.
761     * @param cancelled true if the class is cancelled
762     */
763    public void setCancelled(boolean cancelled) { iCancelled = cancelled; }
764
765    /**
766     * Return past flag of the class.
767     * @return true if the class is in the past and should be avoided when possible
768     */
769    public boolean isPast() { return iPast; }
770    
771    /**
772     * Set past flag of the class.
773     * @param past true if the class is in the past and should be avoided when possible
774     */
775    public void setPast(boolean past) { iPast = past; }
776    
777    /**
778     * Return enabled flag of the class. Use {@link Section#isEnabled(Student)} where applicable.
779     * @return true if the class is enabled for student scheduling
780     */
781    public boolean isEnabled() { return iEnabled; }
782    
783    /**
784     * Return enabled flag of the class. Also check student class dates.
785     * @return true if the class is enabled for student scheduling
786     */
787    public boolean isEnabled(Student student) {
788        if (!iEnabled) return false;
789        if (student != null && student.getModalityPreference() == ModalityPreference.ONLINE_REQUIRED && !isOnline()) return false;
790        if (student != null && getPlacement() != null && getPlacement().getTimeLocation() != null) {
791            if (student.getClassFirstDate() != null) {
792                if (getPlacement().getTimeLocation().getDayCode() != 0 && getPlacement().getTimeLocation().getFirstMeeting(getDayOfWeekOffset()) < student.getClassFirstDate())
793                    return false;
794                else if (getPlacement().getTimeLocation().getDayCode() == 0) {
795                    int firstMeeting = getPlacement().getTimeLocation().getWeekCode().nextSetBit(0);
796                    if (firstMeeting >= 0 && firstMeeting < student.getClassFirstDate())
797                        return false;
798                }
799            }
800            if (student.getClassLastDate() != null) {
801                if (getPlacement().getTimeLocation().getDayCode() != 0 && getPlacement().getTimeLocation().getLastMeeting(getDayOfWeekOffset()) > student.getClassLastDate())
802                    return false;
803                else if (getPlacement().getTimeLocation().getDayCode() == 0) {
804                    int lastMeeting = getPlacement().getTimeLocation().getWeekCode().length() - 1;
805                    if (lastMeeting >= 0 && lastMeeting > student.getClassLastDate())
806                        return false;
807                }
808            }    
809        }
810        return true;
811    }
812    
813    protected int getDayOfWeekOffset() {
814        Model<Request, Enrollment> model = getModel();
815        if (model != null && model instanceof StudentSectioningModel)
816            return ((StudentSectioningModel)model).getDayOfWeekOffset();
817        return 0;
818    }
819    
820    /**
821     * Set enabled flag of the class.
822     * @param enabled true if the class is enabled for student scheduling
823     */
824    public void setEnabled(boolean enabled) { iEnabled = enabled; }
825    
826    /**
827     * Return whether the class is online.
828     * @return true if the class is online
829     */
830    public boolean isOnline() { return iOnline; }
831    
832    public boolean isModalityPreferred(Student student) {
833        if (student.getModalityPreference() == null) return false;
834        switch (student.getModalityPreference()) {
835            case ONLINE_PREFERRED: return isOnline();
836            case ONILNE_DISCOURAGED: return !isOnline();
837            default: return false;
838        }
839    }
840    
841    /**
842     * Set whether the class is online.
843     * @param online true if the class is online
844     */
845    public void setOnline(boolean online) { iOnline = online; }
846    
847    @Override
848    public Model<Request, Enrollment> getModel() {
849        return getSubpart().getConfig().getOffering().getModel();
850    }
851
852    @Override
853    public SectionContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
854        return new SectionContext(assignment);
855    }
856    
857    @Override
858    public SectionContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, SectionContext parentContext) {
859        return new SectionContext(parentContext);
860    }
861    
862    public class SectionContext implements AssignmentConstraintContext<Request, Enrollment> {
863        private Set<Enrollment> iEnrollments = null;
864        private double iEnrollmentWeight = 0.0;
865        private double iEnrollmentTotalWeight = 0.0;
866        private double iMaxEnrollmentWeight = 0.0;
867        private double iMinEnrollmentWeight = 0.0;
868        private boolean iReadOnly = false;
869
870        public SectionContext(Assignment<Request, Enrollment> assignment) {
871            iEnrollments = new HashSet<Enrollment>();
872            for (Course course: getSubpart().getConfig().getOffering().getCourses()) {
873                for (CourseRequest request: course.getRequests()) {
874                    Enrollment enrollment = assignment.getValue(request);
875                    if (enrollment != null && enrollment.getSections().contains(Section.this))
876                        assigned(assignment, enrollment);
877                }
878            }
879        }
880        
881        public SectionContext(SectionContext parent) {
882            iEnrollmentWeight = parent.iEnrollmentWeight;
883            iEnrollmentTotalWeight = parent.iEnrollmentTotalWeight;
884            iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight;
885            iMinEnrollmentWeight = parent.iMinEnrollmentWeight;
886            iEnrollments = parent.iEnrollments;
887            iReadOnly = true;
888        }
889
890        /** Called when an enrollment with this section is assigned to a request */
891        @Override
892        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
893            if (iReadOnly) {
894                iEnrollments = new HashSet<Enrollment>(iEnrollments);
895                iReadOnly = false;
896            }
897            if (iEnrollments.isEmpty()) {
898                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
899            } else {
900                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
901                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
902            }
903            if (iEnrollments.add(enrollment)) {
904                iEnrollmentTotalWeight += enrollment.getRequest().getWeight();
905                if (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())
906                    iEnrollmentWeight += enrollment.getRequest().getWeight();
907            }
908        }
909
910        /** Called when an enrollment with this section is unassigned from a request */
911        @Override
912        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
913            if (iReadOnly) {
914                iEnrollments = new HashSet<Enrollment>(iEnrollments);
915                iReadOnly = false;
916            }
917            if (iEnrollments.remove(enrollment)) {
918                iEnrollmentTotalWeight -= enrollment.getRequest().getWeight();
919                if (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())
920                    iEnrollmentWeight -= enrollment.getRequest().getWeight();
921            }
922            if (iEnrollments.isEmpty()) {
923                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
924            } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
925                if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
926                    double newMinEnrollmentWeight = Double.MAX_VALUE;
927                    for (Enrollment e : iEnrollments) {
928                        if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
929                            newMinEnrollmentWeight = iMinEnrollmentWeight;
930                            break;
931                        } else {
932                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
933                        }
934                    }
935                    iMinEnrollmentWeight = newMinEnrollmentWeight;
936                }
937                if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
938                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
939                    for (Enrollment e : iEnrollments) {
940                        if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
941                            newMaxEnrollmentWeight = iMaxEnrollmentWeight;
942                            break;
943                        } else {
944                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
945                        }
946                    }
947                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
948                }
949            }
950        }
951        
952        /** Set of assigned enrollments 
953         * @return assigned enrollments of this section
954         **/
955        public Set<Enrollment> getEnrollments() {
956            return iEnrollments;
957        }
958        
959        /**
960         * Enrollment weight -- weight of all requests which have an enrollment that
961         * contains this section, excluding the given one. See
962         * {@link Request#getWeight()}.
963         * @param assignment current assignment
964         * @param excludeRequest course request to ignore, if any
965         * @return enrollment weight
966         */
967        public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
968            double weight = iEnrollmentWeight;
969            if (excludeRequest != null) {
970                Enrollment enrollment = assignment.getValue(excludeRequest);
971                if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
972                    weight -= excludeRequest.getWeight();
973            }
974            return weight;
975        }
976        
977        /**
978         * Enrollment weight including over the limit enrollments.
979         *  That is enrollments that have reservation with {@link Reservation#canBatchAssignOverLimit()} set to true.
980         * @param assignment current assignment
981         * @param excludeRequest course request to ignore, if any
982         * @return enrollment weight
983         */
984        public double getEnrollmentTotalWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
985            double weight = iEnrollmentTotalWeight;
986            if (excludeRequest != null) {
987                Enrollment enrollment = assignment.getValue(excludeRequest);
988                if (enrollment!= null && iEnrollments.contains(enrollment))
989                    weight -= excludeRequest.getWeight();
990            }
991            return weight;
992        }
993        
994        /**
995         * Maximal weight of a single enrollment in the section
996         * @return maximal enrollment weight
997         */
998        public double getMaxEnrollmentWeight() {
999            return iMaxEnrollmentWeight;
1000        }
1001
1002        /**
1003         * Minimal weight of a single enrollment in the section
1004         * @return minimal enrollment weight
1005         */
1006        public double getMinEnrollmentWeight() {
1007            return iMinEnrollmentWeight;
1008        }
1009    }
1010    
1011    /**
1012     * Choice matching this section
1013     * @return choice matching this section
1014     */
1015    public Choice getChoice() {
1016        return new Choice(this);
1017    }
1018    
1019    /**
1020     * List of student unavailabilities
1021     * @return student unavailabilities
1022     */
1023    public List<Unavailability> getUnavailabilities() { return iUnavailabilities; }
1024}