001package net.sf.cpsolver.exam.model;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.List;
006import java.util.Set;
007
008import net.sf.cpsolver.ifs.model.Constraint;
009import net.sf.cpsolver.ifs.model.ConstraintListener;
010import net.sf.cpsolver.ifs.util.DistanceMetric;
011
012/**
013 * A room. Only one exam can use a room at a time (period). <br>
014 * <br>
015 * 
016 * @version ExamTT 1.2 (Examination Timetabling)<br>
017 *          Copyright (C) 2008 - 2010 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 ExamRoom extends Constraint<Exam, ExamPlacement> {
036    private List<ExamPlacement>[] iTable;
037    private boolean[] iAvailable;
038    private int[] iPenalty;
039    private String iName;
040    private int iSize, iAltSize;
041    private Double iCoordX, iCoordY;
042
043    /**
044     * Constructor
045     * 
046     * @param model
047     *            examination timetabling model
048     * @param id
049     *            unique id
050     * @param size
051     *            room (normal) seating capacity
052     * @param altSize
053     *            room alternating seating capacity (to be used when
054     *            {@link Exam#hasAltSeating()} is true)
055     * @param coordX
056     *            x coordinate
057     * @param coordY
058     *            y coordinate
059     */
060    @SuppressWarnings("unchecked")
061    public ExamRoom(ExamModel model, long id, String name, int size, int altSize, Double coordX, Double coordY) {
062        super();
063        iAssignedVariables = null;
064        iId = id;
065        iName = name;
066        iCoordX = coordX;
067        iCoordY = coordY;
068        iSize = size;
069        iAltSize = altSize;
070        iTable = new List[model.getNrPeriods()];
071        iAvailable = new boolean[model.getNrPeriods()];
072        iPenalty = new int[model.getNrPeriods()];
073        for (int i = 0; i < iTable.length; i++) {
074            iTable[i] = new ArrayList<ExamPlacement>();
075            iAvailable[i] = true;
076            iPenalty[i] = 0;
077        }
078    }
079
080    /**
081     * Distance between two rooms. See {@link DistanceMetric}
082     * 
083     * @param other
084     *            another room
085     * @return distance between this and the given room
086     */
087    public double getDistanceInMeters(ExamRoom other) {
088        return ((ExamModel)getModel()).getDistanceMetric().getDistanceInMeters(getId(), getCoordX(), getCoordY(), other.getId(), other.getCoordX(), other.getCoordY());
089    }
090
091    /**
092     * Normal seating capacity (to be used when {@link Exam#hasAltSeating()} is
093     * false)
094     */
095    public int getSize() {
096        return iSize;
097    }
098
099    /**
100     * Alternating seating capacity (to be used when
101     * {@link Exam#hasAltSeating()} is true)
102     */
103    public int getAltSize() {
104        return iAltSize;
105    }
106
107    /**
108     * X coordinate
109     */
110    public Double getCoordX() {
111        return iCoordX;
112    }
113
114    /**
115     * Y coordinate
116     */
117    public Double getCoordY() {
118        return iCoordY;
119    }
120
121    /**
122     * An exam placed at the given period.
123     * 
124     * @param period
125     *            a period
126     * @return a placement of an exam in this room at the given period, null if
127     *         unused
128     * @deprecated If room sharing is allowed, this method only returns first exam. Use {@link ExamRoom#getPlacements(ExamPeriod)} instead.
129     */
130    @Deprecated
131    public ExamPlacement getPlacement(ExamPeriod period) {
132        return (iTable[period.getIndex()].isEmpty() ? null : iTable[period.getIndex()].iterator().next());
133    }
134    
135    /**
136     * Exams placed at the given period
137     * 
138     * @param period
139     *            a period
140     * @return a placement of an exam in this room at the given period, null if
141     *         unused (multiple placements can be returned if the room is shared between
142     *         two or more exams)
143     */
144    public List<ExamPlacement> getPlacements(ExamPeriod period) {
145        return iTable[period.getIndex()];
146    }
147
148    /**
149     * True if the room is available (for examination timetabling) during the
150     * given period
151     * 
152     * @param period
153     *            a period
154     * @return true if an exam can be scheduled into this room at the given
155     *         period, false if otherwise
156     */
157    public boolean isAvailable(ExamPeriod period) {
158        return iAvailable[period.getIndex()];
159    }
160
161    public boolean isAvailable(int period) {
162        return iAvailable[period];
163    }
164    
165    /**
166     * True if the room is available during at least one period,
167     * @return true if there is an examination period at which the room is available
168     */
169    public boolean isAvailable() {
170        for (boolean a: iAvailable)
171            if (a) return true;
172        return false;
173    }
174
175    /**
176     * Set whether the room is available (for examination timetabling) during
177     * the given period
178     * 
179     * @param period
180     *            a period
181     * @param available
182     *            true if an exam can be scheduled into this room at the given
183     *            period, false if otherwise
184     */
185    public void setAvailable(ExamPeriod period, boolean available) {
186        iAvailable[period.getIndex()] = available;
187    }
188
189    public void setAvailable(int period, boolean available) {
190        iAvailable[period] = available;
191    }
192
193    /** Return room penalty for given period */
194    public int getPenalty(ExamPeriod period) {
195        return iPenalty[period.getIndex()];
196    }
197
198    public int getPenalty(int period) {
199        return iPenalty[period];
200    }
201
202    /** Set room penalty for given period */
203    public void setPenalty(ExamPeriod period, int penalty) {
204        iPenalty[period.getIndex()] = penalty;
205    }
206
207    public void setPenalty(int period, int penalty) {
208        iPenalty[period] = penalty;
209    }
210    
211    
212    public ExamRoomSharing getRoomSharing() {
213        return ((ExamModel)getModel()).getRoomSharing();
214    }
215
216    /**
217     * Compute conflicts between the given assignment of an exam and all the
218     * current assignments (of this room)
219     */
220    @Override
221    public void computeConflicts(ExamPlacement p, Set<ExamPlacement> conflicts) {
222        if (!p.contains(this)) return;
223        
224        if (getRoomSharing() == null) {
225            for (ExamPlacement conflict: iTable[p.getPeriod().getIndex()])
226                if (!conflict.variable().equals(p.variable()))
227                    conflicts.add(conflict);
228        } else {
229            getRoomSharing().computeConflicts(p, iTable[p.getPeriod().getIndex()], this, conflicts);
230        }
231    }
232
233    /**
234     * Checks whether there is a conflict between the given assignment of an
235     * exam and all the current assignments (of this room)
236     */
237    @Override
238    public boolean inConflict(ExamPlacement p) {
239        if (!p.contains(this)) return false;
240        
241        if (getRoomSharing() == null) {
242            for (ExamPlacement conflict: iTable[p.getPeriod().getIndex()])
243                if (!conflict.variable().equals(p.variable())) return true;
244            return false;
245        } else {
246            return getRoomSharing().inConflict(p, iTable[p.getPeriod().getIndex()], this);
247        }
248    }
249
250    /**
251     * False if the given two assignments are using this room at the same period
252     */
253    @Override
254    public boolean isConsistent(ExamPlacement p1, ExamPlacement p2) {
255        return (p1.getPeriod() != p2.getPeriod() || !p1.contains(this) || !p2.contains(this));
256    }
257
258    /**
259     * An exam was assigned, update room assignment table
260     */
261    @Override
262    public void assigned(long iteration, ExamPlacement p) {
263        if (p.contains(this) && !iTable[p.getPeriod().getIndex()].isEmpty()) {
264            HashSet<ExamPlacement> confs = new HashSet<ExamPlacement>();
265            computeConflicts(p, confs);
266            for (ExamPlacement conf: confs)
267                conf.variable().unassign(iteration);
268            if (iConstraintListeners != null) {
269                for (ConstraintListener<ExamPlacement> listener : iConstraintListeners)
270                    listener.constraintAfterAssigned(iteration, this, p, confs);
271            }
272        }
273    }
274
275    /**
276     * An exam was assigned, update room assignment table
277     */
278    public void afterAssigned(long iteration, ExamPlacement p) {
279        if (p.contains(this))
280            iTable[p.getPeriod().getIndex()].add(p);
281    }
282
283    /**
284     * An exam was unassigned, update room assignment table
285     */
286    @Override
287    public void unassigned(long iteration, ExamPlacement p) {
288        // super.unassigned(iteration, p);
289    }
290
291    /**
292     * An exam was unassigned, update room assignment table
293     */
294    public void afterUnassigned(long iteration, ExamPlacement p) {
295        if (p.contains(this)) {
296            iTable[p.getPeriod().getIndex()].remove(p);
297        }
298    }
299
300    /**
301     * Checks two rooms for equality
302     */
303    @Override
304    public boolean equals(Object o) {
305        if (o == null || !(o instanceof ExamRoom))
306            return false;
307        ExamRoom r = (ExamRoom) o;
308        return getId() == r.getId();
309    }
310
311    /**
312     * Hash code
313     */
314    @Override
315    public int hashCode() {
316        return (int) (getId() ^ (getId() >>> 32));
317    }
318
319    /**
320     * Room name
321     */
322    @Override
323    public String getName() {
324        return (hasName() ? iName : String.valueOf(getId()));
325    }
326
327    /**
328     * Room name
329     */
330    public boolean hasName() {
331        return (iName != null && iName.length() > 0);
332    }
333
334    /**
335     * Room unique id
336     */
337    @Override
338    public String toString() {
339        return getName();
340    }
341
342    /**
343     * Compare two rooms (by unique id)
344     */
345    @Override
346    public int compareTo(Constraint<Exam, ExamPlacement> o) {
347        return toString().compareTo(o.toString());
348    }
349}