package ttsolver.heuristics;

import ifs.solution.*;
import ifs.util.*;
import java.util.*;
import ttsolver.*;
import edu.purdue.smas.timetable.serverfwk.ParameterDefinition;

/**
 * Timetable (solution) comparator.
 * <br><br>
 * The quality of a solution is expressed as a weighted sum combining soft time and classroom preferences, satisfied soft 
 * group constrains and the total number of student conflicts. This allows us to express the importance of different 
 * types of soft constraints.
 * <br><br>
 * The solution comparator prefers a more complete solution (with a smaller number of unassigned variables) and a solution 
 * with a smaller number of perturbations among solutions with the same number of unassigned variables. If both solutions 
 * have the same number of unassigned variables and perturbations, the solution of better quality is selected. 
 * <br><br>
 * Parameters:
 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
 * <tr><td>Comparator.HardStudentConflictWeight</td><td>{@link Double}</td><td>Weight of hard student conflict (conflict between single-section classes)</td></tr>
 * <tr><td>Comparator.StudentConflictWeight</td><td>{@link Double}</td><td>Weight of student conflict</td></tr>
 * <tr><td>Comparator.TimePreferenceWeight</td><td>{@link Double}</td><td>Time preferences weight</td></tr>
 * <tr><td>Comparator.ContrPreferenceWeight</td><td>{@link Double}</td><td>Group constraint preferences weight</td></tr>
 * <tr><td>Comparator.RoomPreferenceWeight</td><td>{@link Double}</td><td>Room preferences weight</td></tr>
 * <tr><td>Comparator.UselessSlotWeight</td><td>{@link Double}</td><td>Useless slots weight</td></tr>
 * <tr><td>Comparator.TooBigRoomWeight</td><td>{@link Double}</td><td>Too big room weight</td></tr>
 * <tr><td>Comparator.DistanceInstructorPreferenceWeight</td><td>{@link Double}</td><td>Distance (of the rooms of the back-to-back classes) based instructor preferences weight</td></tr>
 * <tr><td>Comparator.PerturbationPenaltyWeight</td><td>{@link Double}</td><td>Perturbation penalty (see {@link UniversalPerturbationsCounter})</td></tr>
 * <tr><td>Comparator.DeptSpreadPenaltyWeight</td><td>{@link Double}</td><td>Department balancing penalty (see {@link ttsolver.constraint.DepartmentSpreadConstraint})</td></tr>
 * </table>
 *
 *
 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomas Muller</a>
 * @version 1.0
 */

public class TimetableComparator implements SolutionComparator {
    protected static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(TimetableComparator.class);
    
    private double iEmptySingleSlotWeight;
    public static final String USELESS_SLOT_WEIGHT="Comparator.UselessSlotWeight";
    private double iTimePreferencesWeight;
    public static final String TIME_PREFERENCE_WEIGHT="Comparator.TimePreferenceWeight";
    private double iStudentConflictWeight;
    public static final String STUDENT_CONFLICT_WEIGHT="Comparator.StudentConflictWeight";
    private double iRoomPreferencesWeight;
    public static final String ROOM_PREFERENCE_WEIGHT="Comparator.RoomPreferenceWeight";
    private double iConstrPreferencesWeight;
    public static final String CONSTR_PREFERENCE_WEIGHT="Comparator.ContrPreferenceWeight";
    private double iHardStudentConflictWeight;
    public static final String HARD_STUDENT_CONFLICT_WEIGHT="Comparator.HardStudentConflictWeight";
    private double iTooBigRoomWeight;
    public static final String TOO_BIG_ROOM_WEIGHT = "Comparator.TooBigRoomWeight";
    private boolean iCompareMpp;
    public static final String DISTANCE_INSTRUCTOR_PREFERENCE_WEIGHT = "Comparator.DistanceInstructorPreferenceWeight";
    private double iDistanceInstructorPreferenceWeight;
    public static final String PERTURBATION_PENALTY_WEIGHT = "Comparator.PerturbationPenaltyWeight";
    private double iPerturbationPenaltyWeight;
    public static final String DEPT_SPREAD_PENALTY_WEIGHT = "Comparator.DeptSpreadPenaltyWeight";
    private double iDeptSpreadPenaltyWeight;
    
    public static Collection parameters() {
        Vector ret = new FastVector();
        
        ParameterDefinition.Dependency swDep = new ParameterDefinition.Dependency("General.SwitchStudents","true");
        ParameterDefinition.Dependency deptSpreadDep = new ParameterDefinition.Dependency("General.UseDepartmentSpreadConstraints","false");
        
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",USELESS_SLOT_WEIGHT, "Useless slots", ParameterDefinition.TYPE_DOUBLE, "0.0"));
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",TIME_PREFERENCE_WEIGHT, "Time preferences", ParameterDefinition.TYPE_DOUBLE, "1.0"));
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",ROOM_PREFERENCE_WEIGHT, "Room preferences", ParameterDefinition.TYPE_DOUBLE, "0.1"));
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",STUDENT_CONFLICT_WEIGHT, "Student conflicts", ParameterDefinition.TYPE_DOUBLE, "0.2"));
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",HARD_STUDENT_CONFLICT_WEIGHT, "Hard student conflicts", ParameterDefinition.TYPE_DOUBLE, "1.0").addDependency(swDep));
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",CONSTR_PREFERENCE_WEIGHT, "Group constraint preferences", ParameterDefinition.TYPE_DOUBLE, "1.0"));
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",TOO_BIG_ROOM_WEIGHT, "Too big rooms", ParameterDefinition.TYPE_DOUBLE, "0.0"));
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",DISTANCE_INSTRUCTOR_PREFERENCE_WEIGHT, "Distance instructor preferences", ParameterDefinition.TYPE_DOUBLE, "1.0"));
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",PERTURBATION_PENALTY_WEIGHT, "Perturbation penalty", ParameterDefinition.TYPE_DOUBLE, "1.0"));
        ret.addElement(new ParameterDefinition("Solution Comparator - Weights",DEPT_SPREAD_PENALTY_WEIGHT, "Deparment balancing penalty", ParameterDefinition.TYPE_DOUBLE, "1.0").addDependency(deptSpreadDep));

        return ret;
    }

    public TimetableComparator(DataProperties properties) {
        iEmptySingleSlotWeight = properties.getPropertyDouble(USELESS_SLOT_WEIGHT,0.0);
        iTimePreferencesWeight = properties.getPropertyDouble(TIME_PREFERENCE_WEIGHT,1.0);
        iRoomPreferencesWeight = properties.getPropertyDouble(ROOM_PREFERENCE_WEIGHT,0.1);
        iConstrPreferencesWeight = properties.getPropertyDouble(CONSTR_PREFERENCE_WEIGHT,1.0);
        if (properties.getPropertyBoolean("General.SwitchStudents",true)) {
            iHardStudentConflictWeight = properties.getPropertyDouble(HARD_STUDENT_CONFLICT_WEIGHT,1.0);
            iStudentConflictWeight = properties.getPropertyDouble(STUDENT_CONFLICT_WEIGHT,0.2);
        } else {
            iHardStudentConflictWeight = 0.0;
            iStudentConflictWeight = properties.getPropertyDouble(STUDENT_CONFLICT_WEIGHT,1.0);
        }
        iDistanceInstructorPreferenceWeight = properties.getPropertyDouble(DISTANCE_INSTRUCTOR_PREFERENCE_WEIGHT, 1.0);
        iTooBigRoomWeight = properties.getPropertyDouble(TOO_BIG_ROOM_WEIGHT,0.0);
        iCompareMpp = properties.getPropertyBoolean("General.MPP", false);
        iPerturbationPenaltyWeight = properties.getPropertyDouble(PERTURBATION_PENALTY_WEIGHT,1.0);
        iDeptSpreadPenaltyWeight = properties.getPropertyDouble(DEPT_SPREAD_PENALTY_WEIGHT,1.0);
    }

    public boolean isBetterThanBestSolution(Solution currentSolution) {
        if (currentSolution.getBestInfo()==null) return true;
        TimetableModel tm = (TimetableModel)currentSolution.getModel();
        int unassigned = tm.unassignedVariables().size();
        if (tm.getBestUnassignedVariables()!=unassigned)
            return tm.getBestUnassignedVariables()>unassigned;

        int tooBigBest=0, tooBigCurr=0;
        if (iTooBigRoomWeight!=0.0) {
            tooBigBest = tm.bestTooBigRooms();
            tooBigCurr = tm.countTooBigRooms();
        }
        long uselessSlotsBest=0, uselessSlotsCur=0;
        if (iEmptySingleSlotWeight!=0.0) {
            uselessSlotsBest = tm.bestUselessSlots();
            uselessSlotsCur = tm.getUselessSlots();
        }
        long hardSCBest = 0, hardSCCurr = 0;
        if (iHardStudentConflictWeight!=0.0) {
            hardSCBest = tm.bestHardStudentConflicts();
            hardSCCurr = tm.getHardStudentConflicts();
        }
        double pertBest=0.0, pertCurr=0.0;
        if (iCompareMpp && iPerturbationPenaltyWeight!=0.0) {
            pertCurr = currentSolution.getPerturbationsCounter().getPerturbationPenalty(currentSolution);
            pertBest = currentSolution.getBestPerturbationsPenalty();
        }
        double deptSpreadBest=0.0, deptSpread=0.0;
        if (iDeptSpreadPenaltyWeight!=0.0) {
            deptSpread = tm.getDepartmentSpreadPenalty();
            deptSpreadBest = tm.bestDepartmentSpreadPenalty();
        }
        
        double prefBest = (iEmptySingleSlotWeight*uselessSlotsBest)+
                       (iTimePreferencesWeight*tm.bestGlobalTimePreference())+
                       (iRoomPreferencesWeight*tm.bestGlobalRoomPreference())+
                       (iConstrPreferencesWeight*tm.bestGlobalGroupConstraintPreference())+
                       (iStudentConflictWeight*tm.bestViolatedStudentConflicts())+
                       (iHardStudentConflictWeight*hardSCBest)+
                       (iTooBigRoomWeight*tooBigBest)+
                       (iDistanceInstructorPreferenceWeight*tm.bestInstructorDistancePreference())+
                       (iPerturbationPenaltyWeight*pertBest)+
                       (iDeptSpreadPenaltyWeight*deptSpreadBest)
                       ;
        double prefCurr = (iEmptySingleSlotWeight*uselessSlotsCur)+
                       (iTimePreferencesWeight*tm.getGlobalTimePreference())+
                       (iRoomPreferencesWeight*tm.getGlobalRoomPreference())+
                       (iConstrPreferencesWeight*tm.getGlobalGroupConstraintPreference())+
                       (iStudentConflictWeight*tm.getViolatedStudentConflicts())+
                       (iHardStudentConflictWeight*hardSCCurr)+
                       (iTooBigRoomWeight*tooBigCurr)+
                       (iDistanceInstructorPreferenceWeight*tm.getInstructorDistancePreference())+
                       (iPerturbationPenaltyWeight*pertCurr)+
                       (iDeptSpreadPenaltyWeight*deptSpread)
                       ;
/*
        if (currentSolution.getModel().unassignedVariables().isEmpty() && prefCurr>=prefBest) {
            sLogger.info("Complete but worse (cur="+prefCurr+" vs. best="+prefBest+") solution "+currentSolution.getInfo()+" was found.");
        }
 */
        return (prefCurr<prefBest);
    }
    
}
