001package org.cpsolver.studentsct.model;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.List;
006import java.util.Set;
007
008import org.cpsolver.ifs.assignment.Assignment;
009import org.cpsolver.ifs.model.Model;
010import org.cpsolver.studentsct.reservation.Reservation;
011import org.cpsolver.studentsct.reservation.Restriction;
012
013
014
015/**
016 * Representation of an instructional offering. An offering contains id, name,
017 * the list of course offerings, and the list of possible configurations. See
018 * {@link Config} and {@link Course}.
019 * 
020 * <br>
021 * <br>
022 * 
023 * @author  Tomáš Müller
024 * @version StudentSct 1.3 (Student Sectioning)<br>
025 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
026 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
027 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
028 * <br>
029 *          This library is free software; you can redistribute it and/or modify
030 *          it under the terms of the GNU Lesser General Public License as
031 *          published by the Free Software Foundation; either version 3 of the
032 *          License, or (at your option) any later version. <br>
033 * <br>
034 *          This library is distributed in the hope that it will be useful, but
035 *          WITHOUT ANY WARRANTY; without even the implied warranty of
036 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
037 *          Lesser General Public License for more details. <br>
038 * <br>
039 *          You should have received a copy of the GNU Lesser General Public
040 *          License along with this library; if not see
041 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
042 */
043public class Offering {
044    private long iId = -1;
045    private String iName = null;
046    private Model<Request, Enrollment> iModel = null;
047    private List<Config> iConfigs = new ArrayList<Config>();
048    private List<Course> iCourses = new ArrayList<Course>();
049    private List<Reservation> iReservations = new ArrayList<Reservation>();
050    private List<Restriction> iRestrictions = new ArrayList<Restriction>();
051    private boolean iDummy = false;
052
053    /**
054     * Constructor
055     * 
056     * @param id
057     *            instructional offering unique id
058     * @param name
059     *            instructional offering name (this is usually the name of the
060     *            controlling course)
061     */
062    public Offering(long id, String name) {
063        iId = id;
064        iName = name;
065    }
066
067    /** Offering id 
068     * @return instructional offering unique id
069     **/
070    public long getId() {
071        return iId;
072    }
073
074    /** Offering name 
075     * @return instructional offering name
076     **/
077    public String getName() {
078        return iName;
079    }
080
081    /** Possible configurations 
082     * @return instructional offering configurations
083     **/
084    public List<Config> getConfigs() {
085        return iConfigs;
086    }
087
088    /**
089     * List of courses. One instructional offering can contain multiple courses
090     * (names under which it is offered)
091     * @return list of course offerings
092     */
093    public List<Course> getCourses() {
094        return iCourses;
095    }
096
097    /**
098     * Return section of the given id, if it is part of one of this offering
099     * configurations.
100     * @param sectionId class unique id
101     * @return section of the given id
102     */
103    public Section getSection(long sectionId) {
104        for (Config config : getConfigs()) {
105            for (Subpart subpart : config.getSubparts()) {
106                for (Section section : subpart.getSections()) {
107                    if (section.getId() == sectionId)
108                        return section;
109                }
110            }
111        }
112        return null;
113    }
114
115    /** Return course, under which the given student enrolls into this offering. 
116     * @param student given student
117     * @return course of this offering requested by the student
118     **/
119    public Course getCourse(Student student) {
120        if (getCourses().isEmpty())
121            return null;
122        if (getCourses().size() == 1)
123            return getCourses().get(0);
124        for (Request request : student.getRequests()) {
125            if (request instanceof CourseRequest) {
126                for (Course course : ((CourseRequest) request).getCourses()) {
127                    if (getCourses().contains(course))
128                        return course;
129                }
130            }
131        }
132        return getCourses().get(0);
133    }
134
135    /** Return set of instructional types, union over all configurations. 
136     * @return set of instructional types
137     **/
138    public Set<String> getInstructionalTypes() {
139        Set<String> instructionalTypes = new HashSet<String>();
140        for (Config config : getConfigs()) {
141            for (Subpart subpart : config.getSubparts()) {
142                instructionalTypes.add(subpart.getInstructionalType());
143            }
144        }
145        return instructionalTypes;
146    }
147
148    /**
149     * Return the list of all possible choices of the given instructional type
150     * for this offering.
151     * @param instructionalType instructional type
152     * @return set of choices of the given instructional type
153     */
154    public Set<Choice> getChoices(String instructionalType) {
155        Set<Choice> choices = new HashSet<Choice>();
156        for (Config config : getConfigs()) {
157            for (Subpart subpart : config.getSubparts()) {
158                if (!instructionalType.equals(subpart.getInstructionalType()))
159                    continue;
160                choices.addAll(subpart.getChoices());
161            }
162        }
163        return choices;
164    }
165
166    /**
167     * Return list of all subparts of the given isntructional type for this
168     * offering.
169     * @param instructionalType instructional type
170     * @return subpart of the given instructional type
171     */
172    public Set<Subpart> getSubparts(String instructionalType) {
173        Set<Subpart> subparts = new HashSet<Subpart>();
174        for (Config config : getConfigs()) {
175            for (Subpart subpart : config.getSubparts()) {
176                if (instructionalType.equals(subpart.getInstructionalType()))
177                    subparts.add(subpart);
178            }
179        }
180        return subparts;
181    }
182
183    /** Minimal penalty from {@link Config#getMinPenalty()} 
184     * @return minimal penalty
185     **/
186    public double getMinPenalty() {
187        double min = Double.MAX_VALUE;
188        for (Config config : getConfigs()) {
189            min = Math.min(min, config.getMinPenalty());
190        }
191        return (min == Double.MAX_VALUE ? 0.0 : min);
192    }
193
194    /** Maximal penalty from {@link Config#getMaxPenalty()} 
195     * @return maximal penalty
196     **/
197    public double getMaxPenalty() {
198        double max = Double.MIN_VALUE;
199        for (Config config : getConfigs()) {
200            max = Math.max(max, config.getMaxPenalty());
201        }
202        return (max == Double.MIN_VALUE ? 0.0 : max);
203    }
204
205    @Override
206    public String toString() {
207        return iName;
208    }
209    
210    /** Reservations associated with this offering 
211     * @return reservations for this offering
212     **/
213    public List<Reservation> getReservations() { return iReservations; }
214    
215    /** True if there are reservations for this offering 
216     * @return true if there is at least one reservation
217     **/
218    public boolean hasReservations() { return !iReservations.isEmpty(); }
219    
220    /** Restrictions associated with this offering 
221     * @return restrictions for this offering
222     **/
223    public List<Restriction> getRestrictions() { return iRestrictions; }
224    
225    /** True if there are restrictions for this offering 
226     * @return true if there is at least one restriction
227     **/
228    public boolean hasRestrictions() { return !iRestrictions.isEmpty(); }
229    
230    /**
231     * Total space in the offering that is not reserved by any reservation 
232     * @return total unreserved space in the offering
233     **/
234    public synchronized double getTotalUnreservedSpace() {
235        if (iTotalUnreservedSpace == null)
236            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
237        return iTotalUnreservedSpace;
238    }
239    Double iTotalUnreservedSpace = null;
240    private double getTotalUnreservedSpaceNoCache() {
241        // compute overall available space
242        double available = 0.0;
243        for (Config config: getConfigs()) {
244            available += config.getLimit();
245            // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too 
246            // (in which case there is no unreserved space)
247            if (config.getLimit() < 0) {
248                for (Reservation r: getReservations()) {
249                    // ignore expired reservations
250                    if (r.isExpired()) continue;
251                    // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
252                    if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
253                    // there is an unlimited reservation -> no unreserved space
254                    if (r.getLimit(config) < 0) return 0.0;
255                }
256                return Double.MAX_VALUE;
257            }
258        }
259        
260        // compute maximal reserved space (out of the available space)
261        double reserved = 0;
262        for (Reservation r: getReservations()) {
263            // ignore expired reservations
264            if (r.isExpired()) continue;
265            // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
266            if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
267            // unlimited reservation -> no unreserved space
268            if (r.getLimit() < 0) return 0.0;
269            reserved += r.getLimit();
270        }
271        
272        return Math.max(0.0, available - reserved);
273    }
274
275    /**
276     * Available space in the offering that is not reserved by any reservation 
277     * @param assignment current request
278     * @param excludeRequest excluding given request (if not null)
279     * @return remaining unreserved space in the offering
280     **/
281    public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
282        // compute available space
283        double available = 0.0;
284        for (Config config: getConfigs()) {
285            available += config.getLimit() - config.getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
286            // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too 
287            // (in which case there is no unreserved space)
288            if (config.getLimit() < 0) {
289                for (Reservation r: getReservations()) {
290                    // ignore expired reservations
291                    if (r.isExpired()) continue;
292                    // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
293                    if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
294                    // there is an unlimited reservation -> no unreserved space
295                    if (r.getLimit(config) < 0) return 0.0;
296                }
297                return Double.MAX_VALUE;
298            }
299        }
300        
301        // compute reserved space (out of the available space)
302        double reserved = 0;
303        for (Reservation r: getReservations()) {
304            // ignore expired reservations
305            if (r.isExpired()) continue;
306            // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
307            if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
308            // unlimited reservation -> no unreserved space
309            if (r.getLimit() < 0) return 0.0;
310            reserved += Math.max(0.0, r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest));
311        }
312        
313        return available - reserved;
314    }
315
316    
317    /**
318     * Clear reservation information that was cached on this offering or below
319     */
320    public synchronized void clearReservationCache() {
321        for (Config c: getConfigs())
322            c.clearReservationCache();
323        for (Course c: getCourses())
324            for (CourseRequest r: c.getRequests())
325                r.clearReservationCache();
326        iTotalUnreservedSpace = null;
327    }
328    
329    /**
330     * Clear restriction information that was cached on this offering or below
331     */
332    public synchronized void clearRestrictionCache() {
333        for (Course c: getCourses())
334            for (CourseRequest r: c.getRequests())
335                r.clearRestrictionCache();
336    }
337
338    @Override
339    public boolean equals(Object o) {
340        if (o == null || !(o instanceof Offering)) return false;
341        return getId() == ((Offering)o).getId();
342    }
343    
344    @Override
345    public int hashCode() {
346        return (int) (iId ^ (iId >>> 32));
347    }
348    
349    public Model<Request, Enrollment> getModel() { return iModel; }
350    public void setModel(Model<Request, Enrollment> model) { iModel = model; }
351    
352    /**
353     * Dummy courses that should show on the solver dashboard
354     * (e.g., because they are loaded due to an other session unavailability)
355     * @return true if the course is "dummy"
356     */
357    public boolean isDummy() { return iDummy; }
358    
359    /**
360     * Dummy courses that should show on the solver dashboard
361     * (e.g., because they are loaded due to an other session unavailability)
362    */
363    public void setDummy(boolean dummy) { iDummy = dummy; }
364}