package ttsolver.constraint;

import ifs.model.*;
import ifs.util.*;
import java.util.*;
import ttsolver.model.*;

/**
 * Group constraint.
 * <br>
 * This constraint expresses relations between several classes, e.g., that two sections of the same lecture can not
 * be taught at the same time, or that some classes have to be taught one immediately after another. It can be either
 * hard or soft.
 * <br><br>
 * Following constraints are now supported:
 * <table border='1'><tr><th>Constraint</th><thComment</th></tr>
 * <tr><td>SAME_TIME</td><td>Same time: given classes have to be taught in the same hours. If the classes are of different length, the smaller one cannot start before the longer one and it cannot end after the longer one.</td></tr>
 * <tr><td>SAME_DAYS</td><td>Same days: given classes have to be taught in the same day. If the classes are of different time patterns, the days of one class have to form a subset of the days of the other class.</td></tr>
 * <tr><td>BTB</td><td>Back-to-back constraint: given classes have to be taught in the same room and they have to follow one strictly after another.</td></tr>
 * <tr><td>BTB_TIME</td><td>Back-to-back constraint: given classes have to follow one strictly after another, but they can be taught in different rooms.</td></tr>
 * <tr><td>DIFF_TIME</td><td>Different time: given classes cannot overlap in time.</td></tr>
 * <tr><td>NHB(1), NHB(1.5), NHB(2), ... NHB(8)</td><td>Number of hours between: between the given classes, the exact number of hours have to be kept.</td></tr>
 * <tr><td>SAME_START</td><td>Same starting hour: given classes have to start in the same hour.</td></tr>
 * <tr><td>SAME_ROOM</td><td>Same room: given classes have to be placed in the same room.</td></tr>
 * <tr><td>NHB_GTE(1)</td><td>Greater than or equal to 1 hour between: between the given classes, the number of hours have to be one or more.</td></tr>
 * <tr><td>NHB_LT(6)</td><td>Less than 6 hours between: between the given classes, the number of hours have to be less than six.</td></tr>
 * </table>
 *
 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomas Muller</a>
 * @version 1.0
 */

public class GroupConstraint extends Constraint {
    private long iId;
    private int iPreference;
    private int iType;
    private boolean iIsRequired;
    private boolean iIsProhibited;
    private Counter iGlobalPreference = null;
    private int iLastPreference = 0;
    
    /** Same time: given classes have to be taught in the same hours. If the classes are of different length, the smaller one cannot start before the longer one and it cannot end after the longer one.*/
    public static int TYPE_SAME_TIME = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("SAME_TIME").getId();
    /** Same days: given classes have to be taught in the same day. If the classes are of different time patterns, the days of one class have to form a subset of the days of the other class. */
    public static int TYPE_SAME_DAYS  = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("SAME_DAYS").getId();
    /** Back-to-back constraint: given classes have to be taught in the same room and they have to follow one strictly after another. */
    public static int TYPE_BTB       = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("BTB").getId();
    /** Back-to-back constraint: given classes have to follow one strictly after another, but they can be taught in different rooms. */
    public static int TYPE_BTB_TIME  = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("BTB_TIME").getId();
    /** Different time: given classes cannot overlap in time. */
    public static int TYPE_DIFF_TIME = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("DIFF_TIME").getId();
    /** One hour between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_1     = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(1)").getId();
    /** Two hours between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_2     = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(2)").getId();
    /** Three hours between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_3     = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(3)").getId();
    /** Four hours between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_4     = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(4)").getId();
    /** Five hours between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_5     = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(5)").getId();
    /** Six hours between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_6     = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(6)").getId();
    /** Seven hours between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_7     = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(7)").getId();
    /** Eight hours between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_8     = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(8)").getId();
    /** Same room: given classes have to placed in the same room.*/
    public static int TYPE_SAME_START = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("SAME_START").getId();
    /** Same room: given classes have to placed in the same room.*/
    public static int TYPE_SAME_ROOM = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("SAME_ROOM").getId();
    /** Greater than or equal to 1 hour between: between the given classes, the number of hours have to be one or more.*/
    public static int TYPE_NHB_GTE_1 = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB_GTE(1)").getId();
    /** Less than 6 hours between: between the given classes, the number of hours have to be less than six. */
    public static int TYPE_NHB_LT_6  = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB_LT(6)").getId();
    /** One and half hour between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_1_5   = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(1.5)").getId();
    /** Four and half hours between: between the given classes, the exact number of hours have to be kept.*/
    public static int TYPE_NHB_4_5   = edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType("NHB(4.5)").getId();
    
    /** Constructor
     * @param id constraint id
     * @param type constraint type (e.g, {@link GroupConstraint#TYPE_SAME_TIME})
     * @param preference time preferent ("R" for required, "P" for prohibited, "-2", "-1", "1", "2" for soft preference)
     */
    public GroupConstraint(long id, int type, String preference) {
        iId=id;
        iType=type;
        iIsRequired=preference.equals("R");
        iIsProhibited=preference.equals("P");
        iPreference=(iIsRequired?0:iIsProhibited?0:Integer.parseInt(preference));
    }
    
    /** Constructor
     * @param id constraint id
     * @param type constraint type (e.g, "SAME_TIME")
     * @param preference time preferent ("R" for required, "P" for prohibited, "-2", "-1", "1", "2" for soft preference)
     */
    public GroupConstraint(long id, String type, String preference) {
        this(id, edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType(type).getId(),preference);
    }
    
    /** Constraint id */
    public long getId() { return iId; }
    /** Constraint type (e.g, {@link GroupConstraint#TYPE_SAME_TIME} */
    public int getType() { return iType; }
    /** Constraint type (e.g, "SAME_TIME") */
    public String getTypeStr() { return edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType(iType).getType(); }
    /** Is constraint required */
    public boolean isRequired() { return iIsRequired; }
    /** Is constraint prohibited */
    public boolean isProhibited() { return iIsProhibited; }
    /** Prolog reference: "R" for required, "P" for prohibited", "-2",.."2" for preference */
    public String getPrologPreference() { return (iIsRequired?"R":iIsProhibited?"P":String.valueOf(iPreference)); }
    /** Global preference counter */
    public void setGlobalPreference(Counter globalPreference) { iGlobalPreference = globalPreference; }
    /** Global preference counter */
    public Counter getGlobalPreference() { return iGlobalPreference;}
    
    
    public boolean isConsistent(Value value1, Value value2) {
        if (!isHard()) return true;
        if (!isSatisfiedPair((Lecture)value1.variable(), (Placement)value1, (Lecture)value2.variable(), (Placement)value2))
            return false;
        if (isBackToBack(getType())) {
            Hashtable assignments = new Hashtable();
            assignments.put(value1.variable(), value1);
            assignments.put(value2.variable(), value2);
            if (!isSatisfiedSeq(assignments, false, null))
                return false;
        }
        return true;
    }
    
    public void computeConflicts(Value value, Set conflicts) {
        if (!isHard()) return;
        for (Enumeration e=variables().elements();e.hasMoreElements();) {
            Variable v = (Variable)e.nextElement();
            if (v.equals(value.variable())) continue; //ignore this variable
            if (v.getAssignment()==null) continue; //there is an unassigned variable -- great, still a chance to get violated
            if (!isSatisfiedPair((Lecture)v, (Placement)v.getAssignment(), (Lecture)value.variable(), (Placement)value))
                conflicts.add(v.getAssignment());
        }
        Hashtable assignments = new Hashtable();
        assignments.put(value.variable(), value);
        isSatisfiedSeq(assignments, true, conflicts);
    }
    
    public boolean inConflict(Value value) {
        if (!isHard()) return false;
        for (Enumeration e=variables().elements();e.hasMoreElements();) {
            Variable v = (Variable)e.nextElement();
            if (v.equals(value.variable())) continue; //ignore this variable
            if (v.getAssignment()==null) continue; //there is an unassigned variable -- great, still a chance to get violated
            if (!isSatisfiedPair((Lecture)v, (Placement)v.getAssignment(), (Lecture)value.variable(), (Placement)value))
                return true;
        }
        Hashtable assignments = new Hashtable();
        assignments.put(value.variable(), value);
        return isSatisfiedSeq(assignments, true, null);
    }
    
    /** Constraint preference (0 if prohibited or reqired) */
    public int getPreference() { 
        return iPreference;
    }
    
    /** Current constraint preference (0 if prohibited or reqired, depends on current satisfaction of the constraint) */
    public int getCurrentPreference() { 
        if (isHard()) return 0; //no preference
        if (countAssignedVariables()<2) return 0;
        if (iPreference<0) { //preference version (violated -> 0, satisfied -> preference)
            for (Enumeration e1=variables().elements();e1.hasMoreElements();) {
                Variable v1 = (Variable)e1.nextElement();
                if (v1.getAssignment()==null) continue;
                for (Enumeration e2=variables().elements();e2.hasMoreElements();) {
                    Variable v2 = (Variable)e2.nextElement();
                    if (v2.getAssignment()==null) continue;
                    if (v1.equals(v2)) continue;
                    if (!isSatisfiedPair((Lecture)v1, (Placement)v1.getAssignment(), (Lecture)v2, (Placement)v2.getAssignment()))
                        return 0;
                }
            }
            if (!isSatisfiedSeq(null, true, null))
                return 0;
            return iPreference;
        } else { //discouraged version (violated -> prefernce, satisfied -> 0)
            for (Enumeration e1=variables().elements();e1.hasMoreElements();) {
                Variable v1 = (Variable)e1.nextElement();
                if (v1.getAssignment()==null) continue;
                for (Enumeration e2=variables().elements();e2.hasMoreElements();) {
                    Variable v2 = (Variable)e2.nextElement();
                    if (v2.getAssignment()==null) continue;
                    if (v1.equals(v2)) continue;
                    if (!isSatisfiedPair((Lecture)v1, (Placement)v1.getAssignment(), (Lecture)v2, (Placement)v2.getAssignment()))
                        return iPreference;
                }
            }
            if (!isSatisfiedSeq(null, true, null))
                return iPreference;
            return 0;
        }
    }
    
    /** Current constraint preference (if given placement is assigned) */
    public int getCurrentPreference(Placement placement) { 
        if (isHard()) return 0; //no preference
        if (iPreference<0) { //preference version
            for (Enumeration e1=variables().elements();e1.hasMoreElements();) {
                Variable v1 = (Variable)e1.nextElement();
                if (v1.getAssignment()==null) continue;
                if (v1.equals(placement.variable())) continue;
                if (!isSatisfiedPair((Lecture)v1, (Placement)v1.getAssignment(), (Lecture)placement.variable(), placement))
                        return 0;
            }
            if (isBackToBack(getType())) {
                Hashtable assignment = new Hashtable();
                assignment.put(placement.variable(), placement);
                if (!isSatisfiedSeq(assignment, true, null))
                    return 0;
            }
            return iPreference;
        } else { //discouraged version
            for (Enumeration e1=variables().elements();e1.hasMoreElements();) {
                Variable v1 = (Variable)e1.nextElement();
                if (v1.getAssignment()==null) continue;
                if (v1.equals(placement.variable())) continue;
                if (!isSatisfiedPair((Lecture)v1, (Placement)v1.getAssignment(), (Lecture)placement.variable(), placement))
                        return iPreference;
            }
            if (isBackToBack(getType())) {
                Hashtable assignment = new Hashtable();
                assignment.put(placement.variable(), placement);
                if (!isSatisfiedSeq(assignment, true, null))
                    return iPreference;
            }
            return 0;
        }
    }
    
    public void unassigned(long iteration, Value value) {
        super.unassigned(iteration, value);
        if (iIsRequired || iIsProhibited) return;
        iGlobalPreference.dec(iLastPreference);
        iLastPreference = getCurrentPreference();
        iGlobalPreference.inc(iLastPreference);
    }
    
    public void assigned(long iteration, Value value) {
        super.assigned(iteration, value);
        if (iIsRequired || iIsProhibited) return;
        iGlobalPreference.dec(iLastPreference);
        iLastPreference = getCurrentPreference();
        iGlobalPreference.inc(iLastPreference);
    }
    
    public String toString() {
        return "GroupConstraint{id="+iId+", type="+edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType(iType).getType()+", preference="+getPrologPreference()+", variables="+variables().size()+", lectures"+ToolBox.col2string(variables(),4)+"\n      }";
    }
    
    public boolean isHard() {return iIsRequired || iIsProhibited; }
    
    public String getName() {
        StringBuffer varNames = new StringBuffer();
        for (Enumeration e=variables().elements();e.hasMoreElements();)
            varNames.append(varNames.length()>0?", ":"").append(((Variable)e.nextElement()).getName());
        return edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType(iType).getDescription()+" ["+varNames+"]";
    }
    public String getDescription() { return edu.purdue.smas.timetable.data.Preferences.getPreference(getPrologPreference()).getName().toLowerCase(); }
    
    
    private static int getGapMin(int type) {
        if (type==TYPE_BTB) return 0;
        else if (type==TYPE_BTB_TIME) return 0;
        else if (type==TYPE_NHB_1) return 2;
        else if (type==TYPE_NHB_1_5) return 3;
        else if (type==TYPE_NHB_2) return 4;
        else if (type==TYPE_NHB_3) return 6;
        else if (type==TYPE_NHB_4) return 8;
        else if (type==TYPE_NHB_4_5) return 9;
        else if (type==TYPE_NHB_5) return 10;
        else if (type==TYPE_NHB_6) return 12;
        else if (type==TYPE_NHB_7) return 14;
        else if (type==TYPE_NHB_8) return 16;
        else if (type==TYPE_NHB_GTE_1) return 1;
        else if (type==TYPE_NHB_LT_6) return 0;
        return -1;
    }
    
    private static int getGapMax(int type) {
        if (type==TYPE_BTB) return 0;
        else if (type==TYPE_BTB_TIME) return 0;
        else if (type==TYPE_NHB_1) return 2;
        else if (type==TYPE_NHB_1_5) return 3;
        else if (type==TYPE_NHB_2) return 4;
        else if (type==TYPE_NHB_3) return 6;
        else if (type==TYPE_NHB_4) return 8;
        else if (type==TYPE_NHB_4_5) return 9;
        else if (type==TYPE_NHB_5) return 10;
        else if (type==TYPE_NHB_6) return 12;
        else if (type==TYPE_NHB_7) return 14;
        else if (type==TYPE_NHB_8) return 16;
        else if (type==TYPE_NHB_GTE_1) return edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY;
        else if (type==TYPE_NHB_LT_6) return 11;
        return -1;
    }
    
    private static boolean isBackToBack(int type) {
        if (type==TYPE_BTB) return true;
        if (type==TYPE_BTB_TIME) return true;
        if (type==TYPE_NHB_1) return true;
        if (type==TYPE_NHB_1_5) return true;
        if (type==TYPE_NHB_2) return true;
        if (type==TYPE_NHB_3) return true;
        if (type==TYPE_NHB_4) return true;
        if (type==TYPE_NHB_4_5) return true;
        if (type==TYPE_NHB_5) return true;
        if (type==TYPE_NHB_6) return true;
        if (type==TYPE_NHB_7) return true;
        if (type==TYPE_NHB_8) return true;
        if (type==TYPE_NHB_GTE_1) return true;
        if (type==TYPE_NHB_LT_6) return true;
        return false;
    }
    
    private static boolean isBackToBackTime(int type) {
        if (type==TYPE_BTB_TIME) return true;
        if (type==TYPE_NHB_1) return true;
        if (type==TYPE_NHB_1_5) return true;
        if (type==TYPE_NHB_2) return true;
        if (type==TYPE_NHB_3) return true;
        if (type==TYPE_NHB_4) return true;
        if (type==TYPE_NHB_4_5) return true;
        if (type==TYPE_NHB_5) return true;
        if (type==TYPE_NHB_6) return true;
        if (type==TYPE_NHB_7) return true;
        if (type==TYPE_NHB_8) return true;
        if (type==TYPE_NHB_GTE_1) return true;
        if (type==TYPE_NHB_LT_6) return true;
        return false;
    }
    
    private static boolean sameRoom(int type) {
        if (type==TYPE_SAME_ROOM) return true;
        return false;
    }
    
    private static boolean sameStartHour(int type) {
        return (type==TYPE_SAME_START);
    }
    
    private static boolean sameHours(int type) {
        return (type==TYPE_SAME_TIME);
    }
    
    private static boolean sameDays(int type) {
        if (type==TYPE_SAME_DAYS) return true;
        return false;
    }
    
    private static boolean notOverlap(int type) {
        return (type==TYPE_DIFF_TIME);
    }
    
    private static boolean sameDays(int[] days1, int[] days2) {
        if (days2.length<days1.length) return sameDays(days2,days1);
        int i2=0;
        for (int i1=0;i1<days1.length;i1++) {
            int d1 = days1[i1] / edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY;
            while (true) {
                if (i2==days2.length) return false;
                int d2 = days2[i2] / edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY;
                if (d1==d2) break;
                i2++;
                if (i2==days2.length) return false;
            }
            i2++;
        }
        return true;
    }
    
    private static boolean sameHours(int start1, int len1, int start2, int len2) {
        if (len1>len2) return sameHours(start2, len2, start1, len1);
        start1 %= edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY;
        start2 %= edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY;
        return (start1>=start2 && start1+len1<=start2+len2);
    }
    
    private static boolean canFill(int totalGap, int gapMin, int gapMax, Vector lengths) {
        if (gapMin<=totalGap && totalGap<=gapMax) return true;
        if (totalGap<2*gapMin) return false;
        for (int i=0;i<lengths.size();i++) {
            int length = ((Integer)lengths.elementAt(i)).intValue(); lengths.removeElementAt(i);
            for (int gap=gapMin;gap<=gapMax;gap++)
                if (canFill(totalGap-gap-length,gapMin, gapMax, lengths)) return true;
            lengths.insertElementAt(new Integer(length), i);
        }
        return false;
    }
    
    
    private boolean isSatisfiedSeq(Hashtable assignments, boolean considerCurrentAssignments, Set conflicts) {
        int gapMin = getGapMin(getType());
        int gapMax = getGapMax(getType());
        if (gapMin<0 || gapMax<0) return true;
        
        Vector lengths = new FastVector();
        
        Placement [] res = new Placement[edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY];
        for (int i=0;i<edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY;i++) res[i]=null;
        
        Vector unAssignedLectures = new Vector();
        int nrLectures=0;
        String roomId = null;
        
        for (Enumeration e=variables().elements();e.hasMoreElements();) {
            Lecture lecture = (Lecture)e.nextElement();
            Placement placement = (Placement)(assignments==null?null:assignments.get(lecture));
            if (placement==null && considerCurrentAssignments) placement = (Placement)lecture.getAssignment();
            if (placement==null) {
                lengths.addElement(new Integer(((Placement)lecture.values().firstElement()).getTimeLocation().getLength()));
            } else {
                int pos=placement.getTimeLocation().getSlots()[0] % edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY;
                int length=placement.getTimeLocation().getLength();
                for (int j=pos;j<pos+length;j++) {
                    if (res[j]!=null) {
                        if (conflicts==null) return false;
                        if (!assignments.containsKey(lecture))
                            conflicts.add(placement);
                        else if (!assignments.containsKey(res[j].variable()))
                            conflicts.add(res[j]);
                    }
                }
                for (int j=pos;j<pos+length;j++) res[j]=placement;
                nrLectures++;
            }
        }
        if (nrLectures<=1) return true;
        
        if (iIsRequired || (!iIsProhibited && iPreference<0)) {
            int i=0;
            Placement p=res[i];
            while (p==null) p=res[++i];
            i+=res[i].getTimeLocation().getLength();
            nrLectures--;
            while (nrLectures>0) {
                int gap=0;
                while (i<edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY && res[i]==null) { gap++; i++; }
                if (i==edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) break;
                if (!canFill(gap, gapMin, gapMax, lengths)) {
                    if (conflicts==null) return false;
                    if (assignments==null || !assignments.containsKey(p.variable()))
                        conflicts.add(p);
                    else if (!assignments.containsKey(res[i].variable()))
                        conflicts.add(res[i]);
                }
                p=res[i];
                i+=res[i].getTimeLocation().getLength();
                nrLectures--;
            }
        } else if (iIsProhibited || (!iIsRequired && iPreference>0)) {
            int i=0;
            Placement p=res[i];
            while (p==null) p=res[++i];
            i+=res[i].getTimeLocation().getLength();
            nrLectures--;
            while (nrLectures>0) {
                int gap=0;
                while (i<edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY && res[i]==null) { gap++; i++; }
                if (i==edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) break;
                if ((gapMin==0 || !canFill(gap, 0, gapMin-1, lengths)) &&
                        (gapMax>=edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY || !canFill(gap, gapMax+1, edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY, lengths))) {
                    if (conflicts==null) return false;
                    if (assignments==null || !assignments.containsKey(p.variable()))
                        conflicts.add(p);
                    else if (!assignments.containsKey(res[i].variable()))
                        conflicts.add(res[i]);
                }
                p=res[i];
                i+=res[i].getTimeLocation().getLength();
                nrLectures--;
            }
        }
        return true;
    }
    
    private boolean isSatisfiedPair(Lecture lec1, Placement plc1, Lecture lec2, Placement plc2) {
        if (iIsRequired || (!iIsProhibited && iPreference<0)) {
            if (sameRoom(getType()) || (isBackToBack(getType()) && !isBackToBackTime(getType()))) {
                if (!plc1.getRoomLocation().getId().equals(plc2.getRoomLocation().getId()))
                    return false;
            }
            if (sameStartHour(getType())) {
                if ((plc1.getTimeLocation().getStartSlot()%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) !=
                        (plc2.getTimeLocation().getStartSlot()%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY))
                    return false;
            }
            if (sameHours(getType())) {
                if (!sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(),
                        plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()))
                    return false;
            }
            if (sameDays(getType()) || isBackToBack(getType())) {
                if (!sameDays(plc1.getTimeLocation().getStartSlots(), plc2.getTimeLocation().getStartSlots()))
                    return false;
            }
            if (notOverlap(getType())) {
                if (plc1.getTimeLocation().hasIntersection(plc2.getTimeLocation()))
                    return false;
            }
        } else if (iIsProhibited || (!iIsRequired && iPreference>0)) {
            if (sameRoom(getType())) {
                if (plc1.getRoomLocation().getId().equals(plc2.getRoomLocation().getId()))
                    return false;
            }
            if (sameHours(getType())) {
                if (plc1.getTimeLocation().shareHours(plc2.getTimeLocation()))
                    return false;
            }
            if (sameStartHour(getType())) {
                if ((plc1.getTimeLocation().getStartSlot()%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) ==
                        (plc2.getTimeLocation().getStartSlot()%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY))
                    return false;
            }
            if (sameDays(getType())) {
                if (plc1.getTimeLocation().shareDays(plc2.getTimeLocation()))
                    return false;
            }
            if (notOverlap(getType())) {
                if (!plc1.getTimeLocation().hasIntersection(plc2.getTimeLocation()))
                    return false;
            }
            if (isBackToBack(getType())) { //still same time
                if (!sameDays(plc1.getTimeLocation().getStartSlots(), plc2.getTimeLocation().getStartSlots()))
                    return false;
                if (!isBackToBackTime(getType())) { //still same room
                    if (!plc1.getRoomLocation().getId().equals(plc2.getRoomLocation().getId()))
                        return false;
                }
            }
        }
        return true;
    }
    
    /*
    public void getInfo(Dictionary info) {
        StringBuffer varNames = new StringBuffer();
        for (Enumeration e=variables().elements();e.hasMoreElements();) {
            Variable variable = (Variable)e.nextElement();
            varNames.append(varNames.length()>0?", ":"");
            varNames.append(variable.getName());
            if (variable.getAssignment()!=null)
                varNames.append(" "+variable.getAssignment().getName());
        }
        info.put("gc"+iId, edu.purdue.smas.timetable.data.GroupConstraintTypes.getGroupConstraintType(iType).getDescription()+" (pref="+getDescription()+" ("+iIsRequired+"/"+iIsProhibited+"/"+iPreference+")"+", current="+getCurrentPreference()+", vars=["+varNames+"])");
    }
     */
    
}
