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