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