001package org.cpsolver.studentsct.model;
002
003import java.util.ArrayList;
004import java.util.BitSet;
005import java.util.HashSet;
006import java.util.Iterator;
007import java.util.List;
008import java.util.Set;
009
010import org.cpsolver.coursett.model.TimeLocation;
011
012
013/**
014 * Student choice. Students have a choice of availabe time (but not room) and
015 * instructor(s).
016 * 
017 * Choices of subparts that have the same instrutional type are also merged
018 * together. For instance, a student have a choice of a time/instructor of a
019 * Lecture and of a Recitation.
020 * 
021 * <br>
022 * <br>
023 * 
024 * @version StudentSct 1.3 (Student Sectioning)<br>
025 *          Copyright (C) 2007 - 2014 Tomas Muller<br>
026 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
027 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
028 * <br>
029 *          This library is free software; you can redistribute it and/or modify
030 *          it under the terms of the GNU Lesser General Public License as
031 *          published by the Free Software Foundation; either version 3 of the
032 *          License, or (at your option) any later version. <br>
033 * <br>
034 *          This library is distributed in the hope that it will be useful, but
035 *          WITHOUT ANY WARRANTY; without even the implied warranty of
036 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
037 *          Lesser General Public License for more details. <br>
038 * <br>
039 *          You should have received a copy of the GNU Lesser General Public
040 *          License along with this library; if not see
041 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
042 */
043public class Choice {
044    private Long iSectionId = null;
045    private Long iSubpartId = null;
046    private Long iConfigId = null;
047    private Offering iOffering = null;
048    private String iInstructionalType = null;
049    private TimeLocation iTime = null;
050    private List<Instructor> iInstructors = null;
051    private int iHashCode;
052
053    /**
054     * Constructor
055     * 
056     * @param offering
057     *            instructional offering to which the choice belongs
058     * @param instructionalType
059     *            instructional type to which the choice belongs (e.g., Lecture,
060     *            Recitation or Laboratory)
061     * @param time
062     *            time assignment
063     * @param instructors
064     *            instructor(s)
065     */
066    public Choice(Offering offering, String instructionalType, TimeLocation time, List<Instructor> instructors) {
067        iOffering = offering;
068        iInstructionalType = instructionalType;
069        iTime = time;
070        iInstructors = instructors;
071        iHashCode = getId().hashCode();
072    }
073    
074    @Deprecated
075    public Choice(Offering offering, String instructionalType, TimeLocation time, String instructorIds, String instructorNames) {
076        this(offering, instructionalType, time, Instructor.toInstructors(instructorIds, instructorNames));
077    }
078    
079    /**
080     * Constructor
081     * @param section section to base the choice on
082     */
083    public Choice(Section section) {
084        this(section.getSubpart().getConfig().getOffering(), section.getSubpart().getInstructionalType(), section.getTime(), section.getInstructors());
085        iSectionId = section.getId();
086        iSubpartId = section.getSubpart().getId();
087        // iConfigId = section.getSubpart().getConfig().getId();
088    }
089    
090    /**
091     * Constructor
092     * @param config configuration to base the choice on
093     */
094    public Choice(Config config) {
095        this(config.getOffering(), "N/A", null, null);
096        iConfigId = config.getId();
097    }
098
099    /**
100     * Constructor
101     * 
102     * @param offering
103     *            instructional offering to which the choice belongs
104     * @param choiceId
105     *            choice id is in format instructionalType|time|instructorIds
106     *            where time is of format dayCode:startSlot:length:datePatternId
107     */
108    public Choice(Offering offering, String choiceId) {
109        iOffering = offering;
110        String[] choices = choiceId.split("\\|");
111        iInstructionalType = choices[0];
112        if (choices.length > 1 && !choices[1].isEmpty()) {
113            String[] times = choices[1].split(":");
114            int dayCode = Integer.parseInt(times[0]);
115            int startSlot = Integer.parseInt(times[1]);
116            int length = Integer.parseInt(times[2]);
117            Long datePatternId = (times.length > 3 ? Long.valueOf(times[3]) : null);
118            iTime = new TimeLocation(dayCode, startSlot, length, 0, 0, datePatternId, "N/A", new BitSet(), 0);
119        }
120        if (choices.length > 2 && !choices[2].isEmpty()) {
121            iInstructors = new ArrayList<Instructor>();
122            for (String id: choices[2].split(":"))
123                iInstructors.add(new Instructor(Long.parseLong(id)));
124        }
125        if (choices.length > 3 && !choices[3].isEmpty()) {
126            String[] ids = choices[3].split(":"); 
127            iSectionId = (ids.length < 1 || ids[0].isEmpty() ? null : Long.valueOf(ids[0]));
128            iSubpartId = (ids.length < 2 || ids[1].isEmpty() ? null : Long.valueOf(ids[1]));
129            iConfigId = (ids.length < 3 || ids[2].isEmpty() ? null : Long.valueOf(ids[2]));
130        }
131        iHashCode = getId().hashCode();
132    }
133
134    /** Instructional offering to which this choice belongs 
135     * @return instructional offering
136     **/
137    public Offering getOffering() {
138        return iOffering;
139    }
140
141    /**
142     * Instructional type (e.g., Lecture, Recitation or Laboratory) to which
143     * this choice belongs
144     * @return instructional type
145     */
146    public String getInstructionalType() {
147        return iInstructionalType;
148    }
149
150    /** Time location of the choice
151     * @return selected time
152     **/
153    public TimeLocation getTime() {
154        return iTime;
155    }
156    
157    /**
158     * Return true if the given choice has the same instructional type and time
159     * return true if the two choices have the same ime
160     */
161    public boolean sameTime(Choice choice) {
162        return getInstructionalType().equals(choice.getInstructionalType()) &&
163                (getTime() == null ? choice.getTime() == null : getTime().equals(choice.getTime()));
164    }
165
166    /**
167     * Instructor(s) id of the choice, can be null if the section has no
168     * instructor assigned
169     * @return selected instructors
170     */
171    @Deprecated
172    public String getInstructorIds() {
173        if (hasInstructors()) {
174            StringBuffer sb = new StringBuffer();
175            for (Iterator<Instructor> i = getInstructors().iterator(); i.hasNext(); ) {
176                Instructor instructor = i.next();
177                sb.append(instructor.getId());
178                if (i.hasNext()) sb.append(":");
179            }
180            return sb.toString();
181        }
182        return null;
183    }
184
185    /**
186     * Instructor(s) name of the choice, can be null if the section has no
187     * instructor assigned
188     * @return selected instructors
189     */
190    @Deprecated
191    public String getInstructorNames() {
192        if (hasInstructors()) {
193            StringBuffer sb = new StringBuffer();
194            for (Iterator<Instructor> i = getInstructors().iterator(); i.hasNext(); ) {
195                Instructor instructor = i.next();
196                if (instructor.getName() != null)
197                    sb.append(instructor.getName());
198                else if (instructor.getExternalId() != null)
199                    sb.append(instructor.getExternalId());
200                if (i.hasNext()) sb.append(":");
201            }
202            return sb.toString();
203        }
204        return null;
205    }
206    
207    /**
208     * Instructor names
209     * @param delim delimiter
210     * @return instructor names
211     */
212    public String getInstructorNames(String delim) {
213        if (iInstructors == null || iInstructors.isEmpty()) return "";
214        StringBuffer sb = new StringBuffer();
215        for (Iterator<Instructor> i = iInstructors.iterator(); i.hasNext(); ) {
216            Instructor instructor = i.next();
217            sb.append(instructor.getName() != null ? instructor.getName() : instructor.getExternalId() != null ? instructor.getExternalId() : "I" + instructor.getId());
218            if (i.hasNext()) sb.append(delim);
219        }
220        return sb.toString();
221    }
222    
223    /**
224     * Choice id combined from instructionalType, time and instructorIds in the
225     * following format: instructionalType|time|instructorIds where time is of
226     * format dayCode:startSlot:length:datePatternId
227     * @return choice id
228     */
229    public String getId() {
230        String ret = getInstructionalType() + "|";
231        if (getTime() != null)
232            ret += getTime().getDayCode() + ":" + getTime().getStartSlot() + ":" + getTime().getLength() + (getTime().getDatePatternId() == null ? "" : ":" + getTime().getDatePatternId());
233        ret += "|" + (hasInstructors() ? getInstructorIds() : "");
234        ret += "|" + (iSectionId == null ? "" : iSectionId) + ":" + (iSubpartId == null ? "" : iSubpartId) + ":" + (iConfigId == null ? "" : iConfigId);
235        return ret;
236    }
237
238    /** Compare two choices, based on {@link Choice#getId()} */
239    @Override
240    public boolean equals(Object o) {
241        if (o == null || !(o instanceof Choice))
242            return false;
243        return ((Choice) o).getId().equals(getId());
244    }
245
246    /** Choice hash id, based on {@link Choice#getId()} */
247    @Override
248    public int hashCode() {
249        return iHashCode;
250    }
251
252    /**
253     * List of sections of the instructional offering which represent this
254     * choice. Note that there can be multiple sections with the same choice
255     * (e.g., only if the room location differs).
256     * @return set of sections for matching this choice
257     */
258    public Set<Section> getSections() {
259        Set<Section> sections = new HashSet<Section>();
260        for (Config config : getOffering().getConfigs()) {
261            for (Subpart subpart : config.getSubparts()) {
262                if (!subpart.getInstructionalType().equals(getInstructionalType()))
263                    continue;
264                for (Section section : subpart.getSections()) {
265                    if (this.sameChoice(section))
266                        sections.add(section);
267                }
268            }
269        }
270        return sections;
271    }
272
273    /**
274     * List of parent sections of sections of the instructional offering which
275     * represent this choice. Note that there can be multiple sections with the
276     * same choice (e.g., only if the room location differs).
277     * @return set of parent sections
278     */
279    public Set<Section> getParentSections() {
280        Set<Section> parentSections = new HashSet<Section>();
281        for (Config config : getOffering().getConfigs()) {
282            for (Subpart subpart : config.getSubparts()) {
283                if (!subpart.getInstructionalType().equals(getInstructionalType()))
284                    continue;
285                if (subpart.getParent() == null)
286                    continue;
287                for (Section section : subpart.getSections()) {
288                    if (this.sameChoice(section) && section.getParent() != null)
289                        parentSections.add(section.getParent());
290                }
291            }
292        }
293        return parentSections;
294    }
295
296    /**
297     * Choice name: name of the appropriate subpart + long name of time +
298     * instructor(s) name
299     * @return choice name
300     */
301    public String getName() {
302        return (getOffering().getSubparts(getInstructionalType()).iterator().next()).getName()
303                + " "
304                + (getTime() == null ? "" : getTime().getLongName(true))
305                + (hasInstructors() ? " " + getInstructorNames(",") : "");
306    }
307    
308    /** True if the instructional type is the same */
309    public boolean sameInstructionalType(Section section) {
310        return getInstructionalType() != null && getInstructionalType().equals(section.getSubpart().getInstructionalType());
311    }
312
313    /** True if the time assignment is the same */
314    public boolean sameTime(Section section) {
315        return getTime() == null ? section.getTime() == null : getTime().equals(section.getTime());
316    }
317    
318    /** True if the section contains all instructors of this choice */
319    public boolean sameInstructors(Section section) {
320        return !hasInstructors() || (section.hasInstructors() && section.getInstructors().containsAll(getInstructors()));
321    }
322    
323    /** True if the time assignment as well as the instructor(s) are the same */
324    public boolean sameChoice(Section section) {
325        return sameInstructionalType(section) && sameTime(section) && sameInstructors(section);
326    }
327    
328    /** True if the section is the very same */
329    public boolean sameSection(Section section) {
330        return iSectionId != null && iSectionId.equals(section.getId());
331    }
332    
333    /** True if the subpart is the very same */
334    public boolean sameSubart(Section section) {
335        return iSubpartId != null && iSubpartId.equals(section.getSubpart().getId());
336    }
337    
338    /** True if the configuration is the very same */
339    public boolean sameConfiguration(Section section) {
340        return iConfigId != null && iConfigId.equals(section.getSubpart().getConfig().getId()); 
341    }
342    
343    /** True if the configuration is the very same */
344    public boolean sameConfiguration(Enrollment enrollment) {
345        return iConfigId != null && enrollment.getConfig() != null && iConfigId.equals(enrollment.getConfig().getId()); 
346    }
347    
348    /** True if the configuration is the very same */
349    public boolean sameSection(Enrollment enrollment) {
350        if (iSectionId == null || !enrollment.isCourseRequest()) return false;
351        for (Section section: enrollment.getSections())
352            if (iSectionId.equals(section.getId())) return true;
353        return false; 
354    }
355    
356    /** True if this choice is applicable to the given section (that is, the choice is a config choice or with the same subpart / instructional type) */
357    public boolean isMatching(Section section) {
358        if (iConfigId != null) return true;
359        if (iSubpartId != null && iSubpartId.equals(section.getSubpart().getId())) return true;
360        if (iSubpartId == null && iInstructionalType != null && iInstructionalType.equals(section.getSubpart().getInstructionalType())) return true;
361        return false; 
362    }
363    
364    /** section id */
365    public Long getSectionId() { return iSectionId; }
366    /** subpart id */
367    public Long getSubpartId() { return iSubpartId; }
368    /** config id */
369    public Long getConfigId() { return iConfigId; }
370
371    @Override
372    public String toString() {
373        return getName();
374    }
375    
376    /** Instructors of this choice 
377     * @return list of instructors
378     **/
379    public List<Instructor> getInstructors() {
380        return iInstructors;
381    }
382    
383    /**
384     * Has any instructors
385     * @return return true if there is at least one instructor in this choice
386     */
387    public boolean hasInstructors() {
388        return iInstructors != null && !iInstructors.isEmpty();
389    }
390    
391    /**
392     * Return number of instructors of this choice
393     * @return number of instructors of this choice
394     */
395    public int nrInstructors() {
396        return iInstructors == null ? 0 : iInstructors.size();
397    }
398}