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