package ttsolver.constraint;

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

/**
 * Instructor constraint.
 * <br>
 * Classes with this instructor can not overlap in time. Also, for back-to-back classes, 
 * there is the following reasoning:<ul>
 * <li>if the distance is equal or below {@link InstructorConstraint#sNoPreferenceLimit} .. no preference
 * <li>if the distance is above {@link InstructorConstraint#sNoPreferenceLimit} and below {@link InstructorConstraint#sDiscouragedLimit} .. constraint is discouraged (soft, preference = 1)
 * <li>if the distance is above {@link InstructorConstraint#sDiscouragedLimit} and below {@link InstructorConstraint#sProhibitedLimit} .. constraint is strongly discouraged (soft, preference = 2)
 * <li>if the distance is above {@link InstructorConstraint#sProhibitedLimit} .. constraint is prohibited (hard)
 * </ul>
 *
 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomas Muller</a>
 * @version 1.0
 */

public class InstructorConstraint extends ResourceConstraint {
    /** Back-to-back classes: maximal distance for no prefernce */
    public static double sNoPreferenceLimit = 0.0;
    /** Back-to-back classes: maximal distance for discouraged prefernce */
    public static double sDiscouragedLimit = 5.0;
    /** Back-to-back classes: maximal distance for strongly discouraged prefernce (everything above is prohibited) */
    public static double sProhibitedLimit = 20.0;
    
    /** Constructor
     * @param id instructor id
     * @param name instructor name
     * @param slotsPerDay number of slots per day
     * @param days number of days
     */
    public InstructorConstraint(String id, String name, int slotsPerDay, int days) {
        super(id,name,slotsPerDay,days);
    }

    /** Back-to-back preference of two placements (3 means prohibited) */
    public static int getDistancePreference(Placement p1, Placement p2) {
        if (!Placement.shareDays(p1,p2)) return 0;
        int s1 = p1.getTimeLocation().getStartSlot() % edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY;
        int s2 = p2.getTimeLocation().getStartSlot() % edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY;
        if (s1+p1.getTimeLocation().getLength()!=s2 &&
        s2+p2.getTimeLocation().getLength()!=s1) return 0;
        double distance = Placement.getDistance(p1,p2);
        if (distance==sNoPreferenceLimit) return 0;
        if (distance<=sDiscouragedLimit) return 1;
        if (distance<=sProhibitedLimit) return 2;
        return 3;
    }
    
    public void computeConflicts(Value value, Set conflicts) {
        super.computeConflicts(value, conflicts);
        Lecture lecture = (Lecture) value.variable();
        Placement placement = (Placement) value;
        int[] startSlots = placement.getTimeLocation().getStartSlots();
        for (int i=0;i<startSlots.length;i++) {
            int prevSlot = startSlots[i]-1;
            if (prevSlot>=0 && (prevSlot/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) == (startSlots[i]/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)) {
                Lecture conf = iResource[prevSlot];
                if (conf!=null && Placement.getDistance(placement,(Placement)conf.getAssignment())>sProhibitedLimit) conflicts.add(conf.getAssignment());
            }
            int nextSlot = startSlots[i]+placement.getTimeLocation().getLength();
            if ((nextSlot/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) == (startSlots[i]/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)) {
                Lecture conf = iResource[nextSlot];
                if (conf!=null && Placement.getDistance(placement,(Placement)conf.getAssignment())>sProhibitedLimit) conflicts.add(conf.getAssignment());
            }
        }
    }
    
    public boolean inConflict(Value value) {
        if (super.inConflict(value)) return true;
        Lecture lecture = (Lecture) value.variable();
        Placement placement = (Placement) value;
        int[] startSlots = placement.getTimeLocation().getStartSlots();
        for (int i=0;i<startSlots.length;i++) {
            int prevSlot = startSlots[i]-1;
            if (prevSlot>=0 && (prevSlot/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) == (startSlots[i]/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)) {
                Lecture conf = iResource[prevSlot];
                if (conf!=null && Placement.getDistance(placement,(Placement)conf.getAssignment())>sProhibitedLimit) return true;
            }
            int nextSlot = startSlots[i]+placement.getTimeLocation().getLength();
            if ((nextSlot/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) == (startSlots[i]/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)) {
                Lecture conf = iResource[nextSlot];
                if (conf!=null && Placement.getDistance(placement,(Placement)conf.getAssignment())>sProhibitedLimit) return true;
            }
        }
        return false;
    }
    
    public boolean isConsistent(Value value1, Value value2) {
        if (!super.isConsistent(value1,value2)) return false;
        return getDistancePreference((Placement)value1,(Placement)value2)==3;
    }
    
    /** Back-to-back preference of the given placement */
    public int getPreference(Value value) {
        Lecture lecture = (Lecture)value.variable();
        Placement placement = (Placement)value;
        int pref = 0;
        int[] startSlots = placement.getTimeLocation().getStartSlots();
        for (int i=0;i<startSlots.length;i++) {
            int prevSlot = startSlots[i]-1;
            if (prevSlot>=0 && (prevSlot/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) == (startSlots[i]/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)) {
                Lecture conf = iResource[prevSlot];
                double dist = (conf==null?0.0:Placement.getDistance(placement,(Placement)conf.getAssignment()));
                if (dist>sNoPreferenceLimit && dist<=sProhibitedLimit) pref++;
                if (dist>sDiscouragedLimit && dist<=sProhibitedLimit) pref++;
            }
            int nextSlot = startSlots[i]+placement.getTimeLocation().getLength();
            if ((nextSlot/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY) == (startSlots[i]/edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)) {
                Lecture conf = iResource[nextSlot];
                double dist = (conf==null?0.0:Placement.getDistance(placement,(Placement)conf.getAssignment()));
                if (dist>sNoPreferenceLimit && dist<=sProhibitedLimit) pref++;
                if (dist>sDiscouragedLimit && dist<=sProhibitedLimit) pref++;
            }
        }
        return pref;
    }
    
    /** Overall back-to-back preference of this instructor */
    public int getPreference() {
        int pref = 0;
        for (int slot=1;slot<iResource.length;) {
            if ((slot%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)==0 || iResource[slot]==null) {
                slot++; continue;
            }
            if (iResource[slot-1]!=null) {
                double dist = Placement.getDistance((Placement)iResource[slot-1].getAssignment(),(Placement)iResource[slot].getAssignment());
                if (dist>sNoPreferenceLimit && dist<=sProhibitedLimit) pref++;
                if (dist>sDiscouragedLimit && dist<=sProhibitedLimit) pref++;
            }
            slot += ((Placement)iResource[slot].getAssignment()).getTimeLocation().getLength();
        }
        return pref;
    }

    /** Worst back-to-back preference of this instructor */
    public int getWorstPreference() {
        int pref = 0;
        for (int slot=1;slot<iResource.length;) {
            if ((slot%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)==0 || iResource[slot]==null) {
                slot++; continue;
            }
            if (iResource[slot-1]!=null) pref+=2;
            slot += ((Placement)iResource[slot].getAssignment()).getTimeLocation().getLength();
        }
        return pref;
    }
}
