001package org.cpsolver.instructor.model;
002
003import java.util.Collection;
004
005import org.cpsolver.coursett.Constants;
006import org.cpsolver.coursett.model.TimeLocation;
007import org.cpsolver.instructor.criteria.DifferentLecture;
008
009/**
010 * Section. A section (part of a teaching request that needs an instructor) has an id, a name, a time, a room.
011 * A section may be allowed to overlap in time (in which case the overlapping time is to be minimized) and/or
012 * marked as common. This is, for instance, to be able to ensure that all assignments of a course that are
013 * given to a single instructor share the same lecture.  
014 * 
015 * @author  Tomáš Müller
016 * @version IFS 1.3 (Instructor Sectioning)<br>
017 *          Copyright (C) 2016 Tomáš Müller<br>
018 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
019 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
020 * <br>
021 *          This library is free software; you can redistribute it and/or modify
022 *          it under the terms of the GNU Lesser General Public License as
023 *          published by the Free Software Foundation; either version 3 of the
024 *          License, or (at your option) any later version. <br>
025 * <br>
026 *          This library is distributed in the hope that it will be useful, but
027 *          WITHOUT ANY WARRANTY; without even the implied warranty of
028 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
029 *          Lesser General Public License for more details. <br>
030 * <br>
031 *          You should have received a copy of the GNU Lesser General Public
032 *          License along with this library; if not see
033 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
034 */
035public class Section {
036    private Long iId;
037    private String iExternalId;
038    private String iType;
039    private String iName;
040    private TimeLocation iTime;
041    private String iRoom;
042    private boolean iAllowOverlap;
043    private boolean iCommon;
044    
045    /**
046     * Constructor
047     * @param id section unique id
048     * @param externalId section external id
049     * @param type instructional type
050     * @param name section name
051     * @param time section time assignment
052     * @param room section room assignment
053     * @param allowOverlap can this section overlap with some other section
054     * @param common is this a common part of the course
055     */
056    public Section(long id, String externalId, String type, String name, TimeLocation time, String room, boolean allowOverlap, boolean common) {
057        iId = id;
058        iExternalId = externalId;
059        iType = type;
060        iName = name;
061        iTime = time;
062        iRoom = room;
063        iAllowOverlap = allowOverlap;
064        iCommon = common;
065    }
066    
067    /**
068     * Section unique id that was provided in the constructor
069     * @return section unique id
070     */
071    public Long getSectionId() { return iId; }
072    
073    /**
074     * Section external unique id that was provided in the constructor
075     * @return section external id
076     */
077    public String getExternalId() { return iExternalId; }
078    
079    /**
080     * Section instructional type (e.g., Lecture) that was provided in the constructor
081     * @return section instructional type
082     */
083    public String getSectionType() { return iType; }
084    
085    /**
086     * Has section type filled in?
087     * @return true, if there is a section instructional type filled in
088     */
089    public boolean hasSectionType() { return iType != null && !iType.isEmpty(); }
090    
091    /**
092     * Section name that was provided in the constructor
093     * @return section name
094     */
095    public String getSectionName() { return iName; }
096    
097    /**
098     * Section time that was provided in the constructor
099     * @return section time
100     */
101    public TimeLocation getTime() { return iTime; }
102    
103    /**
104     * Has section time filled in?
105     * @return true if section is assigned in time
106     */
107    public boolean hasTime() { return getTime() != null && getTime().getDayCode() != 0; }
108    
109    /**
110     * Section room (or rooms)
111     * @return section room
112     */
113    public String getRoom() { return iRoom; }
114    
115    /**
116     * Has section room filled in?
117     * @return true if section is assigned in space
118     */
119    public boolean hasRoom() { return iRoom != null && !iRoom.isEmpty(); }
120    
121    /**
122     * Are time overlaps with other sections and with prohibited time preferences allowed? If true, the number of overlapping time slots should be minimized instead.
123     * @return true if other sections can overlap with this section or if the student can teach this section even when he/she is unavailable
124     */
125    public boolean isAllowOverlap() { return iAllowOverlap; }
126    
127    /**
128     * Is common part of the course (e.g., a lecture)? It is possible to either require all assignments of a course that are given to the same instructor to have
129     * to share the common part (when {@link TeachingRequest#getSameCommonPreference()} is true) or to minimize the different sections that are not shared among all the assignments
130     * (using {@link DifferentLecture} criterion and {@link TeachingRequest#nrSameLectures(TeachingRequest)}). 
131     * @return true if this section forms a common part of the course
132     */
133    public boolean isCommon() { return iCommon; }
134    
135    /**
136     * Check if this section overlaps in time with some other section
137     * @param section the other section
138     * @return true, if neither of the two sections allow for overlap and they are assigned in overlapping times (see {@link TimeLocation#hasIntersection(TimeLocation)})
139     */
140    public boolean isOverlapping(Section section) {
141        if (isAllowOverlap() || section.isAllowOverlap()) return false;
142        if (getTime() == null || section.getTime() == null) return false;
143        return getTime().hasIntersection(section.getTime());
144    }
145    
146    /**
147     * Check if this section overlaps in time with at least one of the given sections
148     * @param sections the other sections
149     * @return true, if there is a section among the sections that overlaps in time with this section (see {@link Section#isOverlapping(Section)})
150     */
151    public boolean isOverlapping(Collection<Section> sections) {
152        if (isAllowOverlap()) return false;
153        if (getTime() == null) return false;
154        if (sections.contains(this)) return false;
155        for (Section section : sections) {
156            if (section.isAllowOverlap()) continue;
157            if (section.getTime() == null) continue;
158            if (getTime().hasIntersection(section.getTime())) return true;
159        }
160        return false;
161    }
162    
163    /**
164     * Check if this section is back to back with some other section
165     * @param section the other section
166     * @return true, if this section is back-to-back with the other section
167     */
168    public boolean isBackToBack(Section section) {
169        if (getTime() == null || section.getTime() == null) return false;
170        return getTime().shareWeeks(section.getTime()) && getTime().shareDays(section.getTime()) && 
171                (getTime().getStartSlot() + getTime().getLength() == section.getTime().getStartSlot() || section.getTime().getStartSlot() + section.getTime().getLength() == getTime().getStartSlot());
172    }
173    
174    /**
175     * Check if this section has the same days as some other section
176     * @param section the other section
177     * @return 0 if all the days are different, 1 if all the days are the same
178     */
179    public double percSameDays(Section section) {
180        if (getTime() == null || section.getTime() == null) return 0;
181        double ret = 0.0;
182        for (int dayCode: Constants.DAY_CODES)
183            if ((getTime().getDayCode() & dayCode) != 0 && (section.getTime().getDayCode() & dayCode) != 0) ret ++;
184        return ret / Math.min(getTime().getNrMeetings(), section.getTime().getNrMeetings());
185    }
186    
187    /**
188     * Check if this section is placed in the same room as the other section
189     * @param section the other section
190     * @return true, if both sections have no room or if they have the same room
191     */
192    public boolean isSameRoom(Section section) {
193        return hasRoom() == section.hasRoom() && (!hasRoom() || getRoom().equals(section.getRoom()));
194    }
195
196    /**
197     * Check if this section has the same instructional type as the other section
198     * @param section the other section
199     * @return true, if both sections have no instructional type or if they have the same instructional type
200     */
201    public boolean isSameSectionType(Section section) {
202        return hasSectionType() == section.hasSectionType() && (!hasSectionType() || getSectionType().equals(section.getSectionType()));
203    }
204    
205    /**
206     * Check if this section is back-to-back with some other section in the list
207     * @param sections the other sections
208     * @param diffRoomWeight different room penalty (should be between 1 and 0)
209     * @param diffTypeWeight different instructional type penalty (should be between 1 and 0)
210     * @return 1.0 if there is a section in the list that is back-to-back and in the same room and with the same type, 0.0 if there is no back-to-back section in the list, etc.
211     * If there are multiple back-to-back sections, the best back-to-back value is returned.
212     */
213    public double countBackToBacks(Collection<Section> sections, double diffRoomWeight, double diffTypeWeight) {
214        if (sections.contains(this)) return 0.0;
215        double btb = 0;
216        for (Section section : sections)
217            if (isBackToBack(section)) {
218                double w = 1.0;
219                if (!isSameRoom(section)) w *= diffRoomWeight;
220                if (!isSameSectionType(section)) w *= diffTypeWeight;
221                if (w > btb) btb = w;
222            }
223        return btb;
224    }
225    
226    /**
227     * Check if this section has the same days with some other section in the list
228     * @param sections the other sections
229     * @param diffRoomWeight different room penalty (should be between 1 and 0)
230     * @param diffTypeWeight different instructional type penalty (should be between 1 and 0)
231     * @return 1.0 if there is a section in the list that has the same days and in the same room and with the same type, 0.0 if there is no same days section in the list, etc.
232     * If there are multiple same days sections, the best same days value is returned.
233     */
234    public double countSameDays(Collection<Section> sections, double diffRoomWeight, double diffTypeWeight) {
235        if (sections.contains(this)) return 0.0;
236        double sd = 0;
237        for (Section section : sections) {
238            double w = percSameDays(section);
239            if (!isSameRoom(section)) w *= diffRoomWeight;
240            if (!isSameSectionType(section)) w *= diffTypeWeight;
241            if (w > sd) sd = w;
242        }
243        return sd;
244    }
245    
246    /**
247     * Check if this section has the same room with some other section in the list
248     * @param sections the other sections
249     * @param diffTypeWeight different instructional type penalty (should be between 1 and 0)
250     * @return 1.0 if there is a section in the list that has the same room and with the same type, 0.0 if there is no same room section in the list, etc.
251     * If there are multiple same room sections, the best same room value is returned.
252     */
253    public double countSameRooms(Collection<Section> sections, double diffTypeWeight) {
254        if (sections.contains(this)) return 0.0;
255        double sr = 0;
256        for (Section section : sections) {
257            if (isSameRoom(section)) {
258                double w = 1.0;
259                if (!isSameSectionType(section)) w *= diffTypeWeight;
260                if (w > sr) sr = w;
261            }
262        }
263        return sr;
264    }
265    
266    /**
267     * If this section can overlap in time with the other section, compute the number of overlapping time slots
268     * @param section the other section
269     * @return number of shared days times number of shared slots (a day)
270     */
271    public int share(Section section) {
272        if (getTime() != null && section.getTime() != null && (isAllowOverlap() || section.isAllowOverlap()) && getTime().hasIntersection(section.getTime()))
273            return getTime().nrSharedDays(section.getTime()) * getTime().nrSharedHours(section.getTime());
274        else
275            return 0;
276    }
277    
278    /**
279     * Compute the number of overlapping time slots between this section and the given time
280     * @param time the given time
281     * @return number of shared days times number of shared slots (a day)
282     */
283    public int share(TimeLocation time) {
284        if (getTime() != null && time != null && getTime().hasIntersection(time))
285            return getTime().nrSharedDays(time) * getTime().nrSharedHours(time);
286        else
287            return 0;
288    }
289    
290    /**
291     * If this section can overlap in time with any of the given section, compute the number of overlapping time slots
292     * @param sections the other sections
293     * @return number of shared days times number of shared slots (a day)
294     */
295    public int share(Collection<Section> sections) {
296        if (sections.contains(this)) return 0;
297        int ret = 0;
298        for (Section section : sections)
299            ret += share(section);
300        return ret;
301    }
302    
303    /**
304     * Format the time statement
305     * @param useAmPm use 12-hours or 24-hours format
306     * @return &lt;Days of week&gt; &lt;Start time&gt; - &lt;End time&gt;
307     */
308    public String getTimeName(boolean useAmPm) {
309        if (getTime() == null || getTime().getDayCode() == 0) return "-";
310        return getTime().getDayHeader() + " " + getTime().getStartTimeHeader(useAmPm) + " - " + getTime().getEndTimeHeader(useAmPm);
311    }
312    
313    @Override
314    public String toString() {
315        return (getExternalId() != null ? (getSectionType() == null ? "" : getSectionType() + " ") + getExternalId() :
316            getSectionName() != null ? getSectionName() : getTime() != null ? getTime().getName(true) : "S" + getSectionId());
317    }
318    
319    @Override
320    public int hashCode() {
321        return getSectionId().hashCode();
322    }
323    
324    @Override
325    public boolean equals(Object o) {
326        if (o == null || !(o instanceof Section)) return false;
327        Section s = (Section)o;
328        return getSectionId().equals(s.getSectionId());
329    }
330}