001package net.sf.cpsolver.coursett.model;
002
003import java.util.BitSet;
004import java.util.Enumeration;
005
006import net.sf.cpsolver.coursett.Constants;
007import net.sf.cpsolver.ifs.util.ToolBox;
008
009/**
010 * Time part of placement.
011 * 
012 * @version CourseTT 1.2 (University Course Timetabling)<br>
013 *          Copyright (C) 2006 - 2010 Tomáš Müller<br>
014 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
015 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
016 * <br>
017 *          This library is free software; you can redistribute it and/or modify
018 *          it under the terms of the GNU Lesser General Public License as
019 *          published by the Free Software Foundation; either version 3 of the
020 *          License, or (at your option) any later version. <br>
021 * <br>
022 *          This library is distributed in the hope that it will be useful, but
023 *          WITHOUT ANY WARRANTY; without even the implied warranty of
024 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
025 *          Lesser General Public License for more details. <br>
026 * <br>
027 *          You should have received a copy of the GNU Lesser General Public
028 *          License along with this library; if not see
029 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
030 */
031
032public class TimeLocation {
033    private int iStartSlot;
034
035    private int iPreference;
036    private double iNormalizedPreference;
037
038    private Long iTimePatternId = null;
039    private int iHashCode;
040
041    private int iDayCode;
042    private int iLength;
043    private int iNrMeetings;
044    private int iBreakTime;
045
046    private BitSet iWeekCode;
047    private Long iDatePatternId = null;
048    private String iDatePatternName = null;
049    private int iDatePreference;
050
051    /**
052     * Constructor
053     * 
054     * @param dayCode
055     *            days (combination of 1 for Monday, 2 for Tuesday, ...)
056     * @param startTime
057     *            start slot
058     * @param length
059     *            number of slots
060     * @param pref
061     *            time preference
062     */
063    public TimeLocation(int dayCode, int startTime, int length, int pref, double normPref, int datePatternPreference,
064            Long datePatternId, String datePatternName, BitSet weekCode, int breakTime) {
065        iPreference = pref;
066        iNormalizedPreference = normPref;
067        iStartSlot = startTime;
068        iDayCode = dayCode;
069        iLength = length;
070        iBreakTime = breakTime;
071        iNrMeetings = 0;
072        for (int i = 0; i < Constants.DAY_CODES.length; i++) {
073            if ((iDayCode & Constants.DAY_CODES[i]) == 0)
074                continue;
075            iNrMeetings++;
076        }
077        iHashCode = combine(combine(iDayCode, iStartSlot), iLength);
078        iDatePatternName = datePatternName;
079        iWeekCode = weekCode;
080        iDatePatternId = datePatternId;
081        if (iDatePatternName == null)
082            iDatePatternName = "not set";
083        iDatePreference = datePatternPreference;
084        if (iWeekCode == null) {
085            iWeekCode = new BitSet(366);
086            for (int i = 0; i <= 365; i++)
087                iWeekCode.set(i);
088        }
089    }
090    
091    public TimeLocation(int dayCode, int startTime, int length, int pref, double normPref, Long datePatternId,
092            String datePatternName, BitSet weekCode, int breakTime) {
093        this(dayCode, startTime, length, pref, normPref, 0, datePatternId, datePatternName, weekCode, breakTime);
094    }
095
096    /** Number of meetings */
097    public int getNrMeetings() {
098        return iNrMeetings;
099    }
100
101    public int getBreakTime() {
102        return iBreakTime;
103    }
104
105    public void setBreakTime(int breakTime) {
106        iBreakTime = breakTime;
107    }
108
109    private static int combine(int a, int b) {
110        int ret = 0;
111        for (int i = 0; i < 15; i++)
112            ret = ret | ((a & (1 << i)) << i) | ((b & (1 << i)) << (i + 1));
113        return ret;
114    }
115
116    /** Days (combination of 1 for Monday, 2 for Tuesday, ...) */
117    public int getDayCode() {
118        return iDayCode;
119    }
120
121    /** Days for printing purposes */
122    public String getDayHeader() {
123        StringBuffer sb = new StringBuffer();
124        for (int i = 0; i < Constants.DAY_CODES.length; i++)
125            if ((iDayCode & Constants.DAY_CODES[i]) != 0)
126                sb.append(Constants.DAY_NAMES_SHORT[i]);
127        return sb.toString();
128    }
129
130    /** Start time for printing purposes */
131    public String getStartTimeHeader() {
132        int min = iStartSlot * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
133        int h = min / 60;
134        int m = min % 60;
135        return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a");
136    }
137
138    /** End time for printing purposes */
139    public String getEndTimeHeader() {
140        int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN - getBreakTime();
141        int m = min % 60;
142        int h = min / 60;
143        return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a");
144    }
145
146    /** End time for printing purposes */
147    public String getEndTimeHeaderNoAdj() {
148        int min = (iStartSlot + iLength) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
149        int m = min % 60;
150        int h = min / 60;
151        return (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a");
152    }
153
154    /** Start slot */
155    public int getStartSlot() {
156        return iStartSlot;
157    }
158
159    /** Used slots in a day (combination of 1..first, 2..second,...) */
160
161    /** true if days overlap */
162    public boolean shareDays(TimeLocation anotherLocation) {
163        return ((iDayCode & anotherLocation.iDayCode) != 0);
164    }
165
166    /** number of overlapping days */
167    public int nrSharedDays(TimeLocation anotherLocation) {
168        int ret = 0;
169        for (int i = 0; i < Constants.NR_DAYS; i++) {
170            if ((iDayCode & Constants.DAY_CODES[i]) == 0)
171                continue;
172            if ((anotherLocation.iDayCode & Constants.DAY_CODES[i]) == 0)
173                continue;
174            ret++;
175        }
176        return ret;
177    }
178
179    /** true if hours overlap */
180    public boolean shareHours(TimeLocation anotherLocation) {
181        return (iStartSlot + iLength > anotherLocation.iStartSlot)
182                && (anotherLocation.iStartSlot + anotherLocation.iLength > iStartSlot);
183    }
184
185    /** number of overlapping time slots (ignoring days) */
186    public int nrSharedHours(TimeLocation anotherLocation) {
187        int end = Math.min(iStartSlot + iLength, anotherLocation.iStartSlot + anotherLocation.iLength);
188        int start = Math.max(iStartSlot, anotherLocation.iStartSlot);
189        return (end < start ? 0 : end - start);
190    }
191
192    /** true if weeks overlap */
193    public boolean shareWeeks(TimeLocation anotherLocation) {
194        return iWeekCode.intersects(anotherLocation.iWeekCode);
195    }
196
197    /** true if weeks overlap */
198    public boolean shareWeeks(BitSet weekCode) {
199        return iWeekCode.intersects(weekCode);
200    }
201
202    public boolean hasDay(int day) {
203        return iWeekCode.get(day);
204    }
205
206    /** true if overlap */
207    public boolean hasIntersection(TimeLocation anotherLocation) {
208        return shareDays(anotherLocation) && shareHours(anotherLocation) && shareWeeks(anotherLocation);
209    }
210
211    /** Used slots */
212    public IntEnumeration getSlots() {
213        return new SlotsEnum();
214    }
215
216    /** Used start slots (for each meeting) */
217    public IntEnumeration getStartSlots() {
218        return new StartSlotsEnum();
219    }
220
221    /** Days */
222    public IntEnumeration getDays() {
223        return new DaysEnum();
224    }
225
226    private int[] iDaysCache = null;
227    public int[] getDaysArray() {
228        if (iDaysCache == null) {
229            iDaysCache = new int[getNrMeetings()];
230            int i = 0;
231            for (Enumeration<Integer> e = getDays(); e.hasMoreElements();)
232                iDaysCache[i++] = e.nextElement();
233        }
234        return iDaysCache;
235    }
236
237    /** Text representation */
238    public String getName() {
239        return getDayHeader() + " " + getStartTimeHeader();
240    }
241
242    public String getLongName() {
243        return getDayHeader() + " " + getStartTimeHeader() + " - " + getEndTimeHeader() + " " + getDatePatternName();
244    }
245
246    public String getLongNameNoAdj() {
247        return getDayHeader() + " " + getStartTimeHeader() + " - " + getEndTimeHeaderNoAdj() + " "
248                + getDatePatternName();
249    }
250
251    /** Preference */
252    public int getPreference() {
253        return iPreference;
254    }
255
256    public void setPreference(int preference) {
257        iPreference = preference;
258    }
259
260    /** Length */
261    public int getLength() {
262        return iLength;
263    }
264
265    /** Length */
266    public int getNrSlotsPerMeeting() {
267        return iLength;
268    }
269
270    /** Normalized preference */
271    public double getNormalizedPreference() {
272        return iNormalizedPreference;
273    }
274
275    public void setNormalizedPreference(double normalizedPreference) {
276        iNormalizedPreference = normalizedPreference;
277    }
278
279    /** Time pattern model (can be null) */
280    public Long getTimePatternId() {
281        return iTimePatternId;
282    }
283
284    public Long getDatePatternId() {
285        return iDatePatternId;
286    }
287
288    public void setTimePatternId(Long timePatternId) {
289        iTimePatternId = timePatternId;
290    }
291
292    public BitSet getWeekCode() {
293        return iWeekCode;
294    }
295
296    public String getDatePatternName() {
297        return iDatePatternName;
298    }
299
300    public void setDatePattern(Long datePatternId, String datePatternName, BitSet weekCode) {
301        iDatePatternId = datePatternId;
302        iDatePatternName = datePatternName;
303        iWeekCode = weekCode;
304    }
305    
306    public int getDatePatternPreference() {
307        return iDatePreference;
308    }
309
310    @Override
311    public String toString() {
312        return getName() + " (" + iNormalizedPreference + ")";
313    }
314
315    @Override
316    public int hashCode() {
317        return iHashCode;
318    }
319
320    private class StartSlotsEnum implements IntEnumeration {
321        int day = -1;
322        boolean hasNext = false;
323
324        private StartSlotsEnum() {
325            hasNext = nextDay();
326        }
327
328        boolean nextDay() {
329            do {
330                day++;
331                if (day >= Constants.DAY_CODES.length)
332                    return false;
333            } while ((Constants.DAY_CODES[day] & iDayCode) == 0);
334            return true;
335        }
336
337        @Override
338        public boolean hasMoreElements() {
339            return hasNext;
340        }
341
342        @Override
343        public Integer nextElement() {
344            int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot;
345            hasNext = nextDay();
346            return slot;
347        }
348        
349        @Deprecated
350        @Override
351        public Integer nextInt() {
352            return nextElement();
353        }
354    }
355
356    private class DaysEnum extends StartSlotsEnum {
357        private DaysEnum() {
358            super();
359        }
360
361        @Override
362        public Integer nextElement() {
363            int ret = day;
364            hasNext = nextDay();
365            return ret;
366        }
367    }
368
369    private class SlotsEnum extends StartSlotsEnum {
370        int pos = 0;
371
372        private SlotsEnum() {
373            super();
374        }
375
376        private boolean nextSlot() {
377            if (pos + 1 < iLength) {
378                pos++;
379                return true;
380            }
381            if (nextDay()) {
382                pos = 0;
383                return true;
384            }
385            return false;
386        }
387
388        @Override
389        public Integer nextElement() {
390            int slot = (day * Constants.SLOTS_PER_DAY) + iStartSlot + pos;
391            hasNext = nextSlot();
392            return slot;
393        }
394    }
395
396    @Override
397    public boolean equals(Object o) {
398        if (o == null || !(o instanceof TimeLocation))
399            return false;
400        TimeLocation t = (TimeLocation) o;
401        if (getStartSlot() != t.getStartSlot())
402            return false;
403        if (getLength() != t.getLength())
404            return false;
405        if (getDayCode() != t.getDayCode())
406            return false;
407        return ToolBox.equals(getTimePatternId(), t.getTimePatternId())
408                && ToolBox.equals(getDatePatternId(), t.getDatePatternId());
409    }
410
411    public int getNrWeeks() {
412        return getNrWeeks(0, iWeekCode.size() - 1);
413    }
414
415    public int getNrWeeks(int startDay, int endDay) {
416        /*
417         * BitSet x = new BitSet(1+(endDay-startDay)/Constants.NR_DAYS); for
418         * (int i=iWeekCode.nextSetBit(startDay); i<=endDay && i>=0;
419         * i=iWeekCode.nextSetBit(i+1)) x.set((i-startDay)/Constants.NR_DAYS);
420         * return x.cardinality();
421         */
422        int card = iWeekCode.get(startDay, endDay).cardinality();
423        if (card == 0)
424            return 0;
425        if (card <= 7)
426            return 1;
427        return (5 + card) / 6;
428    }
429    
430    public interface IntEnumeration extends Enumeration<Integer> {
431        @Deprecated
432        public Integer nextInt();
433    }
434    
435    private Integer iFirstMeeting = null;
436    public int getFirstMeeting(int dayOfWeekOffset) {
437        if (iFirstMeeting == null) {
438            int idx = -1;
439            while ((idx = getWeekCode().nextSetBit(1 + idx)) >= 0) {
440                int dow = (idx + dayOfWeekOffset) % 7;
441                if ((getDayCode() & Constants.DAY_CODES[dow]) != 0) break;
442            }
443            iFirstMeeting = idx;
444        }
445        return iFirstMeeting;
446    }
447}