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