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