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