001package org.cpsolver.studentsct.model;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.List;
006import java.util.Set;
007import java.util.regex.Matcher;
008import java.util.regex.Pattern;
009
010import org.cpsolver.studentsct.reservation.Reservation;
011
012
013/**
014 * Representation of a scheduling subpart. Each scheduling subpart contains id,
015 * instructional type, name, instructional offering configuration, and a list of
016 * sections. Optionally, parent-child relation between subparts can be defined. <br>
017 * <br>
018 * 
019 * @author  Tomáš Müller
020 * @version StudentSct 1.3 (Student Sectioning)<br>
021 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
022 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
023 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
024 * <br>
025 *          This library is free software; you can redistribute it and/or modify
026 *          it under the terms of the GNU Lesser General Public License as
027 *          published by the Free Software Foundation; either version 3 of the
028 *          License, or (at your option) any later version. <br>
029 * <br>
030 *          This library is distributed in the hope that it will be useful, but
031 *          WITHOUT ANY WARRANTY; without even the implied warranty of
032 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
033 *          Lesser General Public License for more details. <br>
034 * <br>
035 *          You should have received a copy of the GNU Lesser General Public
036 *          License along with this library; if not see
037 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
038 */
039public class Subpart implements Comparable<Subpart> {
040    private long iId = -1;
041    private String iInstructionalType = null;
042    private String iName = null;
043    private List<Section> iSections = new ArrayList<Section>();
044    private Config iConfig = null;
045    private Subpart iParent = null;
046    private boolean iAllowOverlap = false;
047    private String iCredit = null;
048    private Float iCreditValue = null;
049
050    /**
051     * Constructor
052     * 
053     * @param id
054     *            scheduling subpart unique id
055     * @param itype
056     *            instructional type
057     * @param name
058     *            subpart name
059     * @param config
060     *            instructional offering configuration to which this subpart
061     *            belongs
062     * @param parent
063     *            parent subpart, if parent-child relation is defined between
064     *            subparts
065     */
066    public Subpart(long id, String itype, String name, Config config, Subpart parent) {
067        iId = id;
068        iInstructionalType = itype;
069        iName = name;
070        iConfig = config;
071        iParent = parent;
072        iConfig.getSubparts().add(this);
073    }
074
075    /** Subpart id 
076     * @return scheduling subpart unique id
077     **/
078    public long getId() {
079        return iId;
080    }
081
082    /** Instructional type, e.g., Lecture, Recitation or Laboratory 
083     * @return instructional type
084     **/
085    public String getInstructionalType() {
086        return iInstructionalType;
087    }
088
089    /** Subpart name 
090     * @return scheduling subpart name
091     **/
092    public String getName() {
093        return iName;
094    }
095
096    /** Instructional offering configuration to which this subpart belongs 
097     * @return instructional offering configuration
098     **/
099    public Config getConfig() {
100        return iConfig;
101    }
102
103    /** List of sections 
104     * @return classes of this scheduling supart
105     **/
106    public List<Section> getSections() {
107        return iSections;
108    }
109
110    /** Parent subpart, if parent-child relation is defined between subparts 
111     * @return parent scheduling subpart
112     **/
113    public Subpart getParent() {
114        return iParent;
115    }
116
117    @Override
118    public String toString() {
119        return getName();
120    }
121
122    /**
123     * True, if this subpart is parent (or parent of a parent etc.) of the given
124     * subpart
125     * @param subpart parent scheduling subpart
126     * @return true if parent (even indirect)
127     */
128    public boolean isParentOf(Subpart subpart) {
129        if (subpart.getParent() == null)
130            return false;
131        if (subpart.getParent().equals(this))
132            return true;
133        return isParentOf(subpart.getParent());
134    }
135
136    /**
137     * Compare two subparts: put parents first, use ids if there is no
138     * parent-child relation
139     */
140    @Override
141    public int compareTo(Subpart s) {
142        if (isParentOf(s))
143            return -1;
144        if (s.isParentOf(this))
145            return 1;
146        int cmp = getInstructionalType().compareTo(s.getInstructionalType());
147        if (cmp != 0)
148            return cmp;
149        return Double.compare(getId(), s.getId());
150    }
151
152    /** List of available choices of the sections of this subpart. 
153     * @return set of available choices
154     **/
155    public Set<Choice> getChoices() {
156        Set<Choice> choices = new HashSet<Choice>();
157        for (Section section : getSections()) {
158            choices.add(new Choice(section));
159        }
160        return choices;
161    }
162
163    /** Minimal penalty from {@link Section#getPenalty()} 
164     * @return minimal penalty
165     **/
166    public double getMinPenalty() {
167        double min = Double.MAX_VALUE;
168        for (Section section : getSections()) {
169            min = Math.min(min, section.getPenalty());
170        }
171        return (min == Double.MAX_VALUE ? 0.0 : min);
172    }
173
174    /** Maximal penalty from {@link Section#getPenalty()} 
175     * @return maximal penalty
176     **/
177    public double getMaxPenalty() {
178        double max = Double.MIN_VALUE;
179        for (Section section : getSections()) {
180            max = Math.max(max, section.getPenalty());
181        }
182        return (max == Double.MIN_VALUE ? 0.0 : max);
183    }
184
185    /** Return children subparts 
186     * @return children scheduling subparts
187     **/
188    public List<Subpart> getChildren() {
189        List<Subpart> ret = new ArrayList<Subpart>(getConfig().getSubparts().size());
190        for (Subpart s : getConfig().getSubparts()) {
191            if (this.equals(s.getParent()))
192                ret.add(s);
193        }
194        return ret;
195    }
196    
197    /** Return true if overlaps are allowed, but the number of overlapping slots should be minimized. 
198     * @return true if overlaps of classes of this scheduling subpart are allowed
199     **/
200    public boolean isAllowOverlap() {
201        return iAllowOverlap;
202    }
203    
204    /** Set to true if overlaps are allowed, but the number of overlapping slots should be minimized (defaults to false). 
205     * @param allowOverlap are overlaps of classes of this scheduling subpart allowed
206     **/
207    public void setAllowOverlap(boolean allowOverlap) {
208        iAllowOverlap = allowOverlap;
209    }
210    
211    /**
212     * Get reservations that require sections of this subpart
213     * @return reservations that require a class of this scheduling subpart
214     */
215    public synchronized List<Reservation> getSectionReservations() {
216        if (iSectionReservations == null) {
217            iSectionReservations = new ArrayList<Reservation>();
218            for (Reservation r: getConfig().getOffering().getReservations()) {
219                if (r.getSections(this) != null)
220                    iSectionReservations.add(r);
221            }
222        }
223        return iSectionReservations;
224    }
225    private List<Reservation> iSectionReservations = null;
226    
227    /**
228     * Clear reservation information that was cached on this subpart or below
229     */
230    public synchronized void clearReservationCache() {
231        for (Section s: getSections())
232            s.clearReservationCache();
233        iSectionReservations = null;
234    }
235    
236    /**
237     * Sum of the section limits (unlimited, if one or more sections are unlimited)
238     * @return total class limit
239     */
240    public int getLimit() {
241        if (iLimit == null)
242            iLimit = getLimitNoCache();
243        return iLimit;
244    }
245    private int getLimitNoCache() {
246        int limit = 0;
247        for (Section section: getSections()) {
248            if (section.getLimit() < 0) return -1;
249            limit += section.getLimit();
250        }
251        return limit;
252    }
253    private Integer iLimit = null; 
254    
255    @Override
256    public boolean equals(Object o) {
257        if (o == null || !(o instanceof Subpart)) return false;
258        return getId() == ((Subpart)o).getId();
259    }
260    
261    @Override
262    public int hashCode() {
263        return (int) (iId ^ (iId >>> 32));
264    }
265    
266    /**
267     * Set credit (Online Student Scheduling only)
268     * @param credit scheduling subpart credit
269     */
270    public void setCredit(String credit) {
271        iCredit = credit;
272        if (iCreditValue == null && credit != null) {
273            int split = credit.indexOf('|');
274            String abbv = null;
275            if (split >= 0) {
276                abbv = credit.substring(0, split);
277            } else {
278                abbv = credit;
279            }
280            Matcher m = Pattern.compile("(^| )(\\d+\\.?\\d*)([,-]?(\\d+\\.?\\d*))?($| )").matcher(abbv);
281            if (m.find())
282                iCreditValue = Float.parseFloat(m.group(2));
283        }
284    }
285    
286    /**
287     * Get credit (Online Student Scheduling only)
288     * @return scheduling subpart credit
289     */
290    public String getCredit() { return iCredit; }
291
292    /**
293     * True if this subpart has a credit value defined
294     * @return true if a credit value is set
295     */
296    public boolean hasCreditValue() { return iCreditValue != null; }
297    
298    /**
299     * Set subpart's credit value (null if not set)
300     * @param creditValue subpart's credit value
301     */
302    public void setCreditValue(Float creditValue) { iCreditValue = creditValue; }
303    
304    /**
305     * Get subpart's credit value (null if not set)
306     * return subpart's credit value
307     */
308    public Float getCreditValue() { return iCreditValue; }
309    
310    public boolean isOnline() {
311        for (Section section: getSections())
312            if (!section.isOnline()) return false;
313        return true;
314    }
315    
316    public boolean hasTime() {
317        for (Section section: getSections())
318            if (section.hasTime()) return true;
319        return false;
320    }
321    
322    public boolean isPast() {
323        for (Section section: getSections())
324            if (!section.isPast()) return false;
325        return true;
326    }
327}