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