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