001package org.cpsolver.studentsct.model;
002
003import java.text.DecimalFormat;
004import java.util.HashSet;
005import java.util.Iterator;
006import java.util.Set;
007
008import org.cpsolver.ifs.assignment.Assignment;
009import org.cpsolver.ifs.model.Value;
010import org.cpsolver.ifs.util.ToolBox;
011import org.cpsolver.studentsct.StudentSectioningModel;
012import org.cpsolver.studentsct.extension.DistanceConflict;
013import org.cpsolver.studentsct.extension.TimeOverlapsCounter;
014import org.cpsolver.studentsct.reservation.Reservation;
015
016
017/**
018 * Representation of an enrollment of a student into a course. A student needs
019 * to be enrolled in a section of each subpart of a selected configuration. When
020 * parent-child relation is defined among sections, if a student is enrolled in
021 * a section that has a parent section defined, he/she has be enrolled in the
022 * parent section as well. Also, the selected sections cannot overlap in time. <br>
023 * <br>
024 * 
025 * @version StudentSct 1.3 (Student Sectioning)<br>
026 *          Copyright (C) 2007 - 2014 Tomas Muller<br>
027 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
028 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
029 * <br>
030 *          This library is free software; you can redistribute it and/or modify
031 *          it under the terms of the GNU Lesser General Public License as
032 *          published by the Free Software Foundation; either version 3 of the
033 *          License, or (at your option) any later version. <br>
034 * <br>
035 *          This library is distributed in the hope that it will be useful, but
036 *          WITHOUT ANY WARRANTY; without even the implied warranty of
037 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
038 *          Lesser General Public License for more details. <br>
039 * <br>
040 *          You should have received a copy of the GNU Lesser General Public
041 *          License along with this library; if not see
042 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
043 */
044
045public class Enrollment extends Value<Request, Enrollment> {
046    private static DecimalFormat sDF = new DecimalFormat("0.000");
047    private Request iRequest = null;
048    private Config iConfig = null;
049    private Course iCourse = null;
050    private Set<? extends SctAssignment> iAssignments = null;
051    private Double iCachedPenalty = null;
052    private int iPriority = 0;
053    private Reservation iReservation = null;
054    private Long iTimeStamp = null;
055    private String iApproval = null;
056
057    /**
058     * Constructor
059     * 
060     * @param request
061     *            course / free time request
062     * @param priority
063     *            zero for the course, one for the first alternative, two for the second alternative
064     * @param course
065     *            selected course
066     * @param config
067     *            selected configuration
068     * @param assignments
069     *            valid list of sections
070     * @param reservation used reservation
071     */
072    public Enrollment(Request request, int priority, Course course, Config config, Set<? extends SctAssignment> assignments, Reservation reservation) {
073        super(request);
074        iRequest = request;
075        iConfig = config;
076        iAssignments = assignments;
077        iPriority = priority;
078        iCourse = course;
079        if (iConfig != null && iCourse == null)
080            for (Course c: ((CourseRequest)iRequest).getCourses()) {
081                if (c.getOffering().getConfigs().contains(iConfig)) {
082                    iCourse = c;
083                    break;
084                }
085            }
086        iReservation = reservation;
087    }
088    
089    /**
090     * Constructor
091     * 
092     * @param request
093     *            course / free time request
094     * @param priority
095     *            zero for the course, one for the first alternative, two for the second alternative
096     * @param config
097     *            selected configuration
098     * @param assignments
099     *            valid list of sections
100     * @param assignment current assignment (to guess the reservation)
101     */
102    public Enrollment(Request request, int priority, Config config, Set<? extends SctAssignment> assignments, Assignment<Request, Enrollment> assignment) {
103        this(request, priority, null, config, assignments, null);
104        if (assignments != null && assignment != null)
105            guessReservation(assignment, true);
106    }
107    
108    /**
109     * Guess the reservation based on the enrollment
110     * @param assignment current assignment
111     * @param onlyAvailable use only reservation that have some space left in them
112     */
113    public void guessReservation(Assignment<Request, Enrollment> assignment, boolean onlyAvailable) {
114        if (iCourse != null) {
115            Reservation best = null;
116            boolean canAssignOverTheLimit = (variable().getModel() == null || ((StudentSectioningModel)variable().getModel()).getReservationCanAssignOverTheLimit());
117            for (Reservation reservation: ((CourseRequest)iRequest).getReservations(iCourse)) {
118                if (reservation.isIncluded(this)) {
119                    if (onlyAvailable && reservation.getContext(assignment).getReservedAvailableSpace(assignment, iRequest) < iRequest.getWeight() &&
120                       (!reservation.canAssignOverLimit() || !canAssignOverTheLimit))
121                        continue;
122                    if (best == null || best.getPriority() > reservation.getPriority()) {
123                        best = reservation;
124                    } else if (best.getPriority() == reservation.getPriority() &&
125                        best.getContext(assignment).getReservedAvailableSpace(assignment, iRequest) < reservation.getContext(assignment).getReservedAvailableSpace(assignment, iRequest)) {
126                        best = reservation;
127                    }
128                }
129            }
130            iReservation = best;
131        }
132    }
133    
134    /** Student 
135     * @return student
136     **/
137    public Student getStudent() {
138        return iRequest.getStudent();
139    }
140
141    /** Request 
142     * @return request
143     **/
144    public Request getRequest() {
145        return iRequest;
146    }
147
148    /** True if the request is course request 
149     * @return true if the request if course request
150     **/
151    public boolean isCourseRequest() {
152        return iConfig != null;
153    }
154
155    /** Offering of the course request 
156     * @return offering of the course request
157     **/
158    public Offering getOffering() {
159        return (iConfig == null ? null : iConfig.getOffering());
160    }
161
162    /** Config of the course request 
163     * @return config of the course request
164     **/
165    public Config getConfig() {
166        return iConfig;
167    }
168    
169    /** Course of the course request 
170     * @return course of the course request
171     **/
172    public Course getCourse() {
173        return iCourse;
174    }
175
176    /** List of assignments (selected sections) 
177     * @return assignments (selected sections)
178     **/
179    @SuppressWarnings("unchecked")
180    public Set<SctAssignment> getAssignments() {
181        return (Set<SctAssignment>) iAssignments;
182    }
183
184    /** List of sections (only for course request) 
185     * @return selected sections
186     **/
187    @SuppressWarnings("unchecked")
188    public Set<Section> getSections() {
189        if (isCourseRequest())
190            return (Set<Section>) iAssignments;
191        return new HashSet<Section>();
192    }
193
194    /** True when this enrollment is overlapping with the given enrollment 
195     * @param enrl other enrollment
196     * @return true if there is an overlap 
197     **/
198    public boolean isOverlapping(Enrollment enrl) {
199        if (enrl == null || isAllowOverlap() || enrl.isAllowOverlap())
200            return false;
201        for (SctAssignment a : getAssignments()) {
202            if (a.isOverlapping(enrl.getAssignments()))
203                return true;
204        }
205        return false;
206    }
207
208    /** Percent of sections that are wait-listed 
209     * @return percent of sections that are wait-listed
210     **/
211    public double percentWaitlisted() {
212        if (!isCourseRequest())
213            return 0.0;
214        CourseRequest courseRequest = (CourseRequest) getRequest();
215        int nrWaitlisted = 0;
216        for (Section section : getSections()) {
217            if (courseRequest.isWaitlisted(section))
218                nrWaitlisted++;
219        }
220        return ((double) nrWaitlisted) / getAssignments().size();
221    }
222
223    /** Percent of sections that are selected 
224     * @return percent of sections that are selected
225     **/
226    public double percentSelected() {
227        if (!isCourseRequest())
228            return 0.0;
229        CourseRequest courseRequest = (CourseRequest) getRequest();
230        int nrSelected = 0;
231        for (Section section : getSections()) {
232            if (courseRequest.isSelected(section))
233                nrSelected++;
234        }
235        return ((double) nrSelected) / getAssignments().size();
236    }
237
238    /** Percent of sections that are initial 
239     * @return percent of sections that of the initial enrollment
240     **/
241    public double percentInitial() {
242        if (!isCourseRequest())
243            return 0.0;
244        if (getRequest().getInitialAssignment() == null)
245            return 0.0;
246        Enrollment inital = getRequest().getInitialAssignment();
247        int nrInitial = 0;
248        for (Section section : getSections()) {
249            if (inital.getAssignments().contains(section))
250                nrInitial++;
251        }
252        return ((double) nrInitial) / getAssignments().size();
253    }
254
255    /** True if all the sections are wait-listed 
256     * @return all the sections are wait-listed 
257     **/
258    public boolean isWaitlisted() {
259        if (!isCourseRequest())
260            return false;
261        CourseRequest courseRequest = (CourseRequest) getRequest();
262        for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
263            Section section = (Section) i.next();
264            if (!courseRequest.isWaitlisted(section))
265                return false;
266        }
267        return true;
268    }
269
270    /** True if all the sections are selected 
271     * @return all the sections are selected
272     **/
273    public boolean isSelected() {
274        if (!isCourseRequest())
275            return false;
276        CourseRequest courseRequest = (CourseRequest) getRequest();
277        for (Section section : getSections()) {
278            if (!courseRequest.isSelected(section))
279                return false;
280        }
281        return true;
282    }
283
284    /**
285     * Enrollment penalty -- sum of section penalties (see
286     * {@link Section#getPenalty()})
287     * @return online penalty
288     */
289    public double getPenalty() {
290        if (iCachedPenalty == null) {
291            double penalty = 0.0;
292            if (isCourseRequest()) {
293                for (Section section : getSections()) {
294                    penalty += section.getPenalty();
295                }
296            }
297            iCachedPenalty = new Double(penalty / getAssignments().size());
298        }
299        return iCachedPenalty.doubleValue();
300    }
301
302    /** Enrollment value */
303    @Override
304    public double toDouble(Assignment<Request, Enrollment> assignment) {
305        return toDouble(assignment, true);
306    }
307    
308    /** Enrollment value
309     * @param assignment current assignment
310     * @param precise if false, distance conflicts and time overlaps are ignored (i.e., much faster, but less precise computation)
311     * @return enrollment penalty
312     **/
313    public double toDouble(Assignment<Request, Enrollment> assignment, boolean precise) {
314        if (precise)
315            return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(assignment, this, distanceConflicts(assignment), timeOverlappingConflicts(assignment));
316        else {
317            Double value = (assignment == null ? null : variable().getContext(assignment).getLastWeight());
318            if (value != null) return - value;
319            return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(assignment, this);
320        }
321    }
322    
323    /** Enrollment name */
324    @Override
325    public String getName() {
326        if (getRequest() instanceof CourseRequest) {
327            Course course = null;
328            CourseRequest courseRequest = (CourseRequest) getRequest();
329            for (Course c : courseRequest.getCourses()) {
330                if (c.getOffering().getConfigs().contains(getConfig())) {
331                    course = c;
332                    break;
333                }
334            }
335            String ret = (course == null ? getConfig() == null ? "" : getConfig().getName() : course.getName());
336            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
337                Section assignment = (Section) i.next();
338                ret += "\n  " + assignment.getLongName(true) + (i.hasNext() ? "," : "");
339            }
340            return ret;
341        } else if (getRequest() instanceof FreeTimeRequest) {
342            return "Free Time " + ((FreeTimeRequest) getRequest()).getTime().getLongName(true);
343        } else {
344            String ret = "";
345            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
346                SctAssignment assignment = i.next();
347                ret += assignment.toString() + (i.hasNext() ? "," : "");
348                if (i.hasNext())
349                    ret += "\n  ";
350            }
351            return ret;
352        }
353    }
354
355    public String toString(Assignment<Request, Enrollment> a) {
356        if (getAssignments().isEmpty()) return "not assigned";
357        Set<DistanceConflict.Conflict> dc = distanceConflicts(a);
358        Set<TimeOverlapsCounter.Conflict> toc = timeOverlappingConflicts(a);
359        int share = 0;
360        if (toc != null)
361            for (TimeOverlapsCounter.Conflict c: toc)
362                share += c.getShare();
363        String ret = sDF.format(toDouble(a)) + "/" + sDF.format(getRequest().getBound())
364                + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty()))
365                + (dc == null || dc.isEmpty() ? "" : "/dc:" + dc.size())
366                + (share <= 0 ? "" : "/toc:" + share);
367        if (getRequest() instanceof CourseRequest) {
368            ret += " ";
369            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
370                SctAssignment assignment = i.next();
371                ret += assignment + (i.hasNext() ? ", " : "");
372            }
373        }
374        if (getReservation() != null) ret = "(r) " + ret;
375        return ret;
376    }
377    
378    @Override
379    public String toString() {
380        if (getAssignments().isEmpty()) return "not assigned";
381        String ret = sDF.format(getRequest().getBound()) + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty()));
382        if (getRequest() instanceof CourseRequest) {
383            ret += " ";
384            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
385                SctAssignment assignment = i.next();
386                ret += assignment + (i.hasNext() ? ", " : "");
387            }
388        }
389        if (getReservation() != null) ret = "(r) " + ret;
390        return ret;
391    }
392
393    @Override
394    public boolean equals(Object o) {
395        if (o == null || !(o instanceof Enrollment))
396            return false;
397        Enrollment e = (Enrollment) o;
398        if (!ToolBox.equals(getConfig(), e.getConfig()))
399            return false;
400        if (!ToolBox.equals(getRequest(), e.getRequest()))
401            return false;
402        if (!ToolBox.equals(getAssignments(), e.getAssignments()))
403            return false;
404        return true;
405    }
406
407    /** Distance conflicts, in which this enrollment is involved. 
408     * @param assignment current assignment
409     * @return distance conflicts
410     **/
411    public Set<DistanceConflict.Conflict> distanceConflicts(Assignment<Request, Enrollment> assignment) {
412        if (!isCourseRequest())
413            return null;
414        if (getRequest().getModel() instanceof StudentSectioningModel) {
415            DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict();
416            if (dc == null) return null;
417            return dc.allConflicts(assignment, this);
418        } else
419            return null;
420    }
421
422    /** Time overlapping conflicts, in which this enrollment is involved. 
423     * @param assignment current assignment
424     * @return time overlapping conflicts
425     **/
426    public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts(Assignment<Request, Enrollment> assignment) {
427        if (getRequest().getModel() instanceof StudentSectioningModel) {
428            TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps();
429            if (toc == null)
430                return null;
431            return toc.allConflicts(assignment, this);
432        } else
433            return null;
434    }
435
436    /** 
437     * Return enrollment priority
438     * @return zero for the course, one for the first alternative, two for the second alternative
439     */
440    public int getPriority() {
441        return iPriority;
442    }
443    
444    /**
445     * Return total number of slots of all sections in the enrollment.
446     * @return number of slots used
447     */
448    public int getNrSlots() {
449        int ret = 0;
450        for (SctAssignment a: getAssignments()) {
451            if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings();
452        }
453        return ret;
454    }
455    
456    /**
457     * Return reservation used for this enrollment
458     * @return used reservation
459     */
460    public Reservation getReservation() { return iReservation; }
461    
462    /**
463     * Set reservation for this enrollment
464     * @param reservation used reservation
465     */
466    public void setReservation(Reservation reservation) { iReservation = reservation; }
467    
468    /**
469     * Time stamp of the enrollment
470     * @return enrollment time stamp
471     */
472    public Long getTimeStamp() {
473        return iTimeStamp;
474    }
475
476    /**
477     * Time stamp of the enrollment
478     * @param timeStamp enrollment time stamp
479     */
480    public void setTimeStamp(Long timeStamp) {
481        iTimeStamp = timeStamp;
482    }
483
484    /**
485     * Approval of the enrollment (only used by the online student sectioning)
486     * @return consent approval
487     */
488    public String getApproval() {
489        return iApproval;
490    }
491
492    /**
493     * Approval of the enrollment (only used by the online student sectioning)
494     * @param approval consent approval
495     */
496    public void setApproval(String approval) {
497        iApproval = approval;
498    }
499    
500    /**
501     * True if this enrollment can overlap with other enrollments of the student.
502     * @return can overlap with other enrollments of the student
503     */
504    public boolean isAllowOverlap() {
505        return (getReservation() != null && getReservation().isAllowOverlap());
506    }
507    
508    /**
509     * Enrollment limit, i.e., the number of students that would be able to get into the offering using this enrollment (if all the sections are empty)  
510     * @return enrollment limit
511     */
512    public int getLimit() {
513        if (!isCourseRequest()) return -1; // free time requests have no limit
514        Integer limit = null;
515        for (Section section: getSections())
516            if (section.getLimit() >= 0) {
517                if (limit == null)
518                    limit = section.getLimit();
519                else
520                    limit = Math.min(limit, section.getLimit());
521            }
522        return (limit == null ? -1 : limit);
523    }
524    
525}