package ttsolver.heuristics;

import ifs.extension.*;
import ifs.heuristics.*;
import ifs.model.*;
import ifs.solution.*;
import ifs.solver.*;
import ifs.util.*;
import ifs.perturbations.*;

import java.util.*;

import ttsolver.constraint.*;
import ttsolver.model.*;
import ttsolver.*;
import edu.purdue.smas.timetable.serverfwk.ParameterDefinition;

/**
 * Placement (value) selection.
 * <br><br>
 * We have implemented a hierarchical handling of the value selection criteria (see {@link HeuristicSelector}).
 * <br><br>
 * The value selection heuristics also allow for random selection of a value with a given probability 
 * (random walk, e.g., 2%) and, in the case of MPP, to select the initial value (if it exists) with a given probability (e.g., 70%).
 * <br><br>
 * Parameters (general):
 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
 * <tr><td>Placement.RandomWalkProb</td><td>{@link Double}</td><td>Random walk probability</td></tr>
 * <tr><td>Placement.GoodSelectionProb</td><td>{@link Double}</td><td>Good value (not removed from domain) selection probability (MAC related)</td></tr>
 * <tr><td>Placement.TabuLength</td><td>{@link Integer}</td><td>Tabu-list length (-1 means do not use tabu-list)</td></tr>
 * <tr><td>Placement.MPP_InitialProb</td><td>{@link Double}</td><td>MPP initial selection probability </td></tr>
 * <tr><td>Placement.MPP_Limit</td><td>{@link Integer}</td><td>MPP: limit on the number of perturbations (-1 for no limit)</td></tr>
 * <tr><td>Placement.MPP_PenaltyLimit</td><td>{@link Double}</td><td>MPP: limit on the perturbations penalty (-1 for no limit)</td></tr>
 * </table>
 * <br>
 * Parameters (for each level of selection):
 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
 * <tr><td>Placement.NrAssignmentsWeight1<br>Placement.NrAssignmentsWeight2<br>Placement.NrAssignmentsWeight3</td><td>{@link Double}</td><td>Number of previous assignments of the value weight</td></tr>
 * <tr><td>Placement.NrConflictsWeight1,2,3</td><td>{@link Double}</td><td>Number of conflicts weight</td></tr>
 * <tr><td>Placement.WeightedConflictsWeight1,2,3</td><td>{@link Double}</td><td>Weighted conflicts weight (Conflict-based Statistics related)</td></tr>
 * <tr><td>Placement.NrPotentialConflictsWeight1,2,3</td><td>{@link Double}</td><td>Number of potential conflicts weight (Conflict-based Statistics related)</td></tr>
 * <tr><td>Placement.MPP_DeltaInitialAssignmentWeight1,2,3</td><td>{@link Double}</td><td>Delta initial assigments weight (MPP, violated initials related)</td></tr>
 * <tr><td>Placement.NrHardStudConfsWeight1,2,3</td><td>{@link Double}</td><td>Hard student conflicts weight (student conflicts between single-section classes)</td></tr>
 * <tr><td>Placement.NrStudConfsWeight1,2,3</td><td>{@link Double}</td><td>Student conflicts weight</td></tr>
 * <tr><td>Placement.TimePreferenceWeight1,2,3</td><td>{@link Double}</td><td>Time preference weight</td></tr>
 * <tr><td>Placement.DeltaTimePreferenceWeight1,2,3</td><td>{@link Double}</td><td>Time preference delta weight (difference between before and after assignemnt of the value)</td></tr>
 * <tr><td>Placement.ConstrPreferenceWeight1,2,3</td><td>{@link Double}</td><td>Constraint preference weight</td></tr>
 * <tr><td>Placement.RoomPreferenceWeight1,2,3</td><td>{@link Double}</td><td>Room preference weight</td></tr>
 * <tr><td>Placement.UselessSlotsWeight1,2,3</td><td>{@link Double}</td><td>Useless slot weight</td></tr>
 * <tr><td>Placement.TooBigRoomWeight1,2,3</td><td>{@link Double}</td><td>Too big room weight</td></tr>
 * <tr><td>Placement.DistanceInstructorPreferenceWeight1,2,3</td><td>{@link Double}</td><td>Distance (of the rooms of the back-to-back classes) based instructor preferences weight</td></tr>
 * <tr><td>Placement.DeptSpreadPenaltyWeight1,2,3</td><td>{@link Double}</td><td>Department spreading: penalty of when a slot over initial allowance is used</td></tr>
 * <tr><td>Placement.ThresholdKoef1,2</td><td>{@link Double}</td><td>Threshold koeficient of the level</td></tr>
 * </table>
 * 
 * @see PlacementSelection
 * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomas Muller</a>
 * @version 1.0
 */

public class PlacementSelection implements ValueSelection {
    static final int NR_LEVELS = 3;
    private static final double PRECISION = 1.0;
    private static final boolean USE_THRESHOLD = true;
    private boolean iUseThreshold = USE_THRESHOLD;
    
    private double iGoodSelectionProb;
    public static final String GOOD_SELECTION_PROB = "Placement.GoodSelectionProb";
    private double iRandomWalkProb;
    public static final String RW_SELECTION_PROB = "Placement.RandomWalkProb";
    private double iInitialSelectionProb;
    public static final String INITIAL_SELECTION_PROB = "Placement.MPP_InitialProb";
    private int iMPPLimit;
    public static final String NR_MPP_LIMIT = "Placement.MPP_Limit";
    private double iMPPPenaltyLimit;
    public static final String NR_MPP_PENALTY_LIMIT = "Placement.MPP_PenaltyLimit";
    
    private double[] iNrConflictsWeight = new double[NR_LEVELS];
    public static final String NR_CONFLICTS_WEIGHT = "Placement.NrConflictsWeight";
    private double[] iNrPotentialConflictsWeight = new double[NR_LEVELS];
    public static final String NR_POTENTIAL_CONFLICTS_WEIGHT = "Placement.NrPotentialConflictsWeight";
    private double[] iNrWeightedConflictsWeight = new double[NR_LEVELS];
    public static final String WEIGHTED_CONFLICTS_WEIGHT = "Placement.WeightedConflictsWeight";
    private double[] iDeltaTimePreferenceWeight = new double[NR_LEVELS];
    public static final String DELTA_TIME_PREFERENCE_WEIGHT = "Placement.DeltaTimePreferenceWeight";
    private double[] iPerturbationPenaltyWeight = new double[NR_LEVELS];
    public static final String DELTA_INITIAL_ASSIGNMENT_WEIGHT = "Placement.MPP_DeltaInitialAssignmentWeight";
    private double[] iNrStudentConflictsWeight = new double[NR_LEVELS];
    public static final String NR_STUDENT_CONF_WEIGHT = "Placement.NrStudConfsWeight";
    private double[] iNrHardStudentConflictsWeight = new double[NR_LEVELS];
    public static final String NR_HARD_STUDENT_CONF_WEIGHT = "Placement.NrHardStudConfsWeight";
    private double[] iUselessSlotsWeight = new double[NR_LEVELS];
    public static final String USELESS_SLOTS_WEIGHT = "Placement.UselessSlotsWeight";
    private double[] iSumConstrPreferencesWeight = new double[NR_LEVELS];
    public static final String SUM_CONSTR_PREFERENCE_WEIGHT = "Placement.ConstrPreferenceWeight";
    private double[] iSumRoomPreferencesWeight = new double[NR_LEVELS];
    public static final String SUM_ROOM_PREFERENCE_WEIGHT = "Placement.RoomPreferenceWeight";
    private double[] iSumTimePreferencesWeight = new double[NR_LEVELS];
    public static final String SUM_TIME_PREFERENCE_WEIGHT = "Placement.TimePreferenceWeight";
    private double[] iNrAssignmentsWeight = new double[NR_LEVELS];
    public static final String NR_ASSIGNMENTS_WEIGHT = "Placement.NrAssignmentsWeight";
    private double[] iThresholdKoef = new double[NR_LEVELS];
    public static final String NR_THRESHOLD_KOEF = "Placement.ThresholdKoef";
    private double[] iTooBigRoomWeight = new double[NR_LEVELS];
    public static final String TOO_BIG_ROOM_WEIGHT = "Placement.TooBigRoomWeight";
    private double[] iDeptSpreadWeight = new double[NR_LEVELS];
    public static final String DEPT_SPREAD_WEIGHT = "Placement.DeptSpreadPenaltyWeight";
    private double[] iDistanceInstructorPreferenceWeight = new double[NR_LEVELS];
    public static final String DISTANCE_INSTRUCTOR_PREFERENCE_WEIGHT = "Placement.DistanceInstructorPreferenceWeight";

    private int       iTabuSize                  = 0;
    private ArrayList iTabu                      = null;
    private int       iTabuPos                   = 0;
    public static final String TABU_LENGTH       = "Placement.TabuLength";

    private ConflictStatistics iStat = null;
    private MacPropagation iProp = null;
    private ViolatedInitials iViolatedInitials = null;
    private PerturbationsCounter iPerturbationsCounter = null;
    
    private boolean iRW = false;
    private boolean iMPP = false;
    private boolean iSW = false;

    public static Vector parameters() {
        Vector ret = new FastVector();
        
        ParameterDefinition.Dependency mppDep = new ParameterDefinition.Dependency("General.MPP","true");
        //ParameterDefinition.Dependency rwDep = new ParameterDefinition.Dependency("General.RandomWalk","true");
        //ParameterDefinition.Dependency propDep = new ParameterDefinition.Dependency("General.MAC","true");
        ParameterDefinition.Dependency swDep = new ParameterDefinition.Dependency("General.SwitchStudents","true");
        ParameterDefinition.Dependency deptSpreadDep = new ParameterDefinition.Dependency("General.UseDepartmentSpreadConstraints","true");
        ParameterDefinition.Dependency autoconfigure = new ParameterDefinition.Dependency("Placement.AutoConfigure", "false");
        ParameterDefinition.Dependency useCBS = new ParameterDefinition.Dependency("ConflictStatistics.Enabled", "true");
        ParameterDefinition.Dependency notUseCBS = new ParameterDefinition.Dependency("ConflictStatistics.Enabled", "false");
        
        ret.addElement(new ParameterDefinition("Placement Selection",RW_SELECTION_PROB, "Random-walk probability", ParameterDefinition.TYPE_DOUBLE, "0.00"));
        ret.addElement(new ParameterDefinition("Placement Selection",INITIAL_SELECTION_PROB, "Select initial probability", ParameterDefinition.TYPE_DOUBLE, "0.75").addDependency(mppDep));
        //ret.addElement(new ParameterDefinition("Placement Selection",GOOD_SELECTION_PROB,"Good placement selection probability", ParameterDefinition.TYPE_DOUBLE, "1.00").addDependency(propDep));
        ret.addElement(new ParameterDefinition("Placement Selection",NR_MPP_LIMIT, "Added perturbations limit", ParameterDefinition.TYPE_INTEGER, "-1").addDependency(mppDep));
        ret.addElement(new ParameterDefinition("Placement Selection",NR_MPP_PENALTY_LIMIT, "Perturbations penalty limit", ParameterDefinition.TYPE_DOUBLE, "-1.0").addDependency(mppDep));
        //ret.addElement(new ParameterDefinition("Placement Selection",TABU_LENGTH, "Tabu-list length", ParameterDefinition.TYPE_INTEGER, "-1"));
        ret.addElement(new ParameterDefinition("Placement Selection", "Placement.AutoConfigure", "Automatic configuration", ParameterDefinition.TYPE_BOOLEAN, "true"));
        
        for (int level=0;level<NR_LEVELS;level++) {
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), NR_CONFLICTS_WEIGHT+(level+1), "Hard conflicts", ParameterDefinition.TYPE_DOUBLE, (level==0?"1.0":"0.0")).addDependency(autoconfigure).addDependency(notUseCBS));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), WEIGHTED_CONFLICTS_WEIGHT+(level+1), "Weighted hard conflicts (CBS)", ParameterDefinition.TYPE_DOUBLE, (level==0?"1.0":"0.0")).addDependency(autoconfigure).addDependency(useCBS));
            //ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), NR_POTENTIAL_CONFLICTS_WEIGHT+(level+1), "Potential hard conflicts", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), DELTA_TIME_PREFERENCE_WEIGHT+(level+1), "Time preferences delta", ParameterDefinition.TYPE_DOUBLE, (level==0?"0.5":"0.0")).addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), DELTA_INITIAL_ASSIGNMENT_WEIGHT+(level+1), "Perturbation penalty", ParameterDefinition.TYPE_DOUBLE, (level==0?"0.5":level==1?"1.0":"0.0")).addDependency(mppDep).addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), NR_STUDENT_CONF_WEIGHT+(level+1), "Student conflicts", ParameterDefinition.TYPE_DOUBLE, (level==0?"0.1":level==1?"0.2":"0.0")).addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), NR_HARD_STUDENT_CONF_WEIGHT+(level+1), "Hard student conflicts", ParameterDefinition.TYPE_DOUBLE, (level==0?"0.5":level==1?"1.0":"0.0")).addDependency(swDep).addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), USELESS_SLOTS_WEIGHT+(level+1), "Useless slots", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), SUM_CONSTR_PREFERENCE_WEIGHT+(level+1), "Group constraint preferences", ParameterDefinition.TYPE_DOUBLE, (level==0?"0.0":level==1?"1.0":"0.0")).addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), SUM_ROOM_PREFERENCE_WEIGHT+(level+1), "Room preferences", ParameterDefinition.TYPE_DOUBLE, (level==1?"0.1":"0.0")).addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), SUM_TIME_PREFERENCE_WEIGHT+(level+1), "Time preferences", ParameterDefinition.TYPE_DOUBLE, (level==1?"1.0":"0.0")).addDependency(autoconfigure));
            //ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), NR_ASSIGNMENTS_WEIGHT+(level+1), "Number of assignments", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), TOO_BIG_ROOM_WEIGHT+(level+1), "Too big rooms", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), DEPT_SPREAD_WEIGHT+(level+1), "Deparment balancing penalty", ParameterDefinition.TYPE_DOUBLE, (level==0?"0.5":level==1?"1.0":"0.0")).addDependency(deptSpreadDep).addDependency(autoconfigure));
            ret.addElement(new ParameterDefinition("Placement Selection - Weights Level "+(level+1), DISTANCE_INSTRUCTOR_PREFERENCE_WEIGHT+(level+1), "Distance instructor preference", ParameterDefinition.TYPE_DOUBLE, (level==0?"0.5":level==1?"1.0":"0.0")).addDependency(autoconfigure));
            if (USE_THRESHOLD) ret.addElement(new ParameterDefinition("Placement Selection", NR_THRESHOLD_KOEF+(level+1), "Threshold koeficient for level "+(level+1), ParameterDefinition.TYPE_DOUBLE, (level==0?"0.1":"0.0")).addDependency(autoconfigure));
        }

        return ret;
    }
    
    public void init(Solver solver) {
        for (Enumeration i=solver.getExtensions().elements();i.hasMoreElements();) {
            Extension extension = (Extension)i.nextElement();
            if (extension instanceof ConflictStatistics) 
                iStat = (ConflictStatistics) extension;
            if (extension instanceof MacPropagation)
                iProp = (MacPropagation)extension;
            if (extension instanceof ViolatedInitials)
                iViolatedInitials = (ViolatedInitials)extension;
        }
        iPerturbationsCounter = solver.getPerturbationsCounter();
    }

    public PlacementSelection(DataProperties properties) {
        iMPP                     = properties.getPropertyBoolean("General.MPP", false);
        iRW                      = properties.getPropertyBoolean("General.RandomWalk", true);
        iSW                      = properties.getPropertyBoolean("General.SwitchStudents",true);
        boolean autoconfigure = properties.getPropertyBoolean("Placement.AutoConfigure", false);
        iRandomWalkProb = (iRW?properties.getPropertyDouble(RW_SELECTION_PROB,0.00):0.0);
        iGoodSelectionProb = properties.getPropertyDouble(GOOD_SELECTION_PROB,1.00);
        iInitialSelectionProb = (iMPP?properties.getPropertyDouble(INITIAL_SELECTION_PROB, 0.75):0.0);
        iMPPLimit = (iMPP?properties.getPropertyInt(NR_MPP_LIMIT, -1):-1);
        iMPPPenaltyLimit = (iMPP?properties.getPropertyDouble(NR_MPP_PENALTY_LIMIT, -1.0):-1.0);
        iTabuSize = properties.getPropertyInt(TABU_LENGTH, -1);
        if (iTabuSize>0) iTabu=new ArrayList(iTabuSize);
        iUseThreshold = properties.getPropertyBoolean("Placement.UseThreshold", USE_THRESHOLD);
        
        for (int level=0; level<NR_LEVELS; level++) {
            iNrConflictsWeight[level] = properties.getPropertyDouble(NR_CONFLICTS_WEIGHT+(level+1),(level==0?1.0:0.0));
            iNrPotentialConflictsWeight[level] = properties.getPropertyDouble(NR_POTENTIAL_CONFLICTS_WEIGHT+(level+1),0.0);
            iNrWeightedConflictsWeight[level] = properties.getPropertyDouble(WEIGHTED_CONFLICTS_WEIGHT+(level+1),(level==0?1.0:0.0));
            iDeltaTimePreferenceWeight[level] = properties.getPropertyDouble(DELTA_TIME_PREFERENCE_WEIGHT+(level+1), (level==0?0.5:0.0));
            iPerturbationPenaltyWeight[level] = (iMPP?properties.getPropertyDouble(DELTA_INITIAL_ASSIGNMENT_WEIGHT+(level+1), (level==0?0.5:level==1?1.0:0.0)):0.0);
            iNrStudentConflictsWeight[level] = properties.getPropertyDouble(NR_STUDENT_CONF_WEIGHT+(level+1),(level==0?0.1:(level==1?0.2:0.0)));
            iNrHardStudentConflictsWeight[level] = (iSW?properties.getPropertyDouble(NR_HARD_STUDENT_CONF_WEIGHT+(level+1),(level==0?0.5:level==1?1.0:0.0)):0.0);
            iUselessSlotsWeight[level] = properties.getPropertyDouble(USELESS_SLOTS_WEIGHT+(level+1), 0.0);
            iSumConstrPreferencesWeight[level] = properties.getPropertyDouble(SUM_CONSTR_PREFERENCE_WEIGHT+(level+1), (level==0?0.5:level==1?1.0:0.0));
            iSumRoomPreferencesWeight[level] = properties.getPropertyDouble(SUM_ROOM_PREFERENCE_WEIGHT+(level+1), (level==1?0.1:0.0));
            iSumTimePreferencesWeight[level] = properties.getPropertyDouble(SUM_TIME_PREFERENCE_WEIGHT+(level+1), (level==1?1.0:0.0));
            iNrAssignmentsWeight[level] = properties.getPropertyDouble(NR_ASSIGNMENTS_WEIGHT+(level+1), 0.0);
            iThresholdKoef[level] = (USE_THRESHOLD?properties.getPropertyDouble(NR_THRESHOLD_KOEF+(level+1), (level==0?0.1:0.0)):0.0);
            iTooBigRoomWeight[level] = properties.getPropertyDouble(TOO_BIG_ROOM_WEIGHT+(level+1), 0.0);
            iDeptSpreadWeight[level] = properties.getPropertyDouble(DEPT_SPREAD_WEIGHT+(level+1), (level==0?0.5:level==1?1.0:0.0));
            iDistanceInstructorPreferenceWeight[level] = properties.getPropertyDouble(DISTANCE_INSTRUCTOR_PREFERENCE_WEIGHT+(level+1), (level==0?0.1:level==1?1.0:0.0));
        }
        
        if (autoconfigure) {
            iNrConflictsWeight[0] = 3.0;
            iNrPotentialConflictsWeight[0] = 0.0;
            iNrWeightedConflictsWeight[0] = 3.0;
            iDeltaTimePreferenceWeight[0] = properties.getPropertyDouble("Comparator.TimePreferenceWeight",1.0)/2.0;
            iNrAssignmentsWeight[0] = 0.0;
            iThresholdKoef[0] = 0.1;
            
            iNrStudentConflictsWeight[0] = properties.getPropertyDouble("Comparator.StudentConflictWeight",0.2);
            iNrHardStudentConflictsWeight[0] = properties.getPropertyDouble("Comparator.HardStudentConflictWeight",1.0);
            iUselessSlotsWeight[0] = properties.getPropertyDouble("Comparator.UselessSlotWeight",0.0);
            iSumConstrPreferencesWeight[0] = properties.getPropertyDouble("Comparator.ContrPreferenceWeight",1.0);
            iSumRoomPreferencesWeight[0] = properties.getPropertyDouble("Comparator.RoomPreferenceWeight",0.1);
            iSumTimePreferencesWeight[0] = properties.getPropertyDouble("Comparator.TimePreferenceWeight",1.0);
            iTooBigRoomWeight[0] = properties.getPropertyDouble("Comparator.TooBigRoomWeight",0.0);
            iDeptSpreadWeight[0] = properties.getPropertyDouble("Comparator.DeptSpreadPenaltyWeight",1.0);
            iDistanceInstructorPreferenceWeight[0] = properties.getPropertyDouble("Comparator.DistanceInstructorPreferenceWeight",1.0);
            iPerturbationPenaltyWeight[0] = (iMPP?properties.getPropertyDouble("Comparator.PerturbationPenaltyWeight",1.0):0.0);

            iNrConflictsWeight[1] = 0.0;
            iNrPotentialConflictsWeight[1] = 0.0;
            iNrWeightedConflictsWeight[1] = 0.0;
            iDeltaTimePreferenceWeight[1] = 0.0;
            iNrAssignmentsWeight[1] = 0.0;
            iThresholdKoef[1] = 0.0;
            
            iNrStudentConflictsWeight[1] = properties.getPropertyDouble("Comparator.StudentConflictWeight",0.2);
            iNrHardStudentConflictsWeight[1] = properties.getPropertyDouble("Comparator.HardStudentConflictWeight",1.0);
            iUselessSlotsWeight[1] = properties.getPropertyDouble("Comparator.UselessSlotWeight",0.0);
            iSumConstrPreferencesWeight[1] = properties.getPropertyDouble("Comparator.ContrPreferenceWeight",1.0);
            iSumRoomPreferencesWeight[1] = properties.getPropertyDouble("Comparator.RoomPreferenceWeight",0.1);
            iSumTimePreferencesWeight[1] = properties.getPropertyDouble("Comparator.TimePreferenceWeight",1.0);
            iTooBigRoomWeight[1] = properties.getPropertyDouble("Comparator.TooBigRoomWeight",0.0);
            iDeptSpreadWeight[1] = properties.getPropertyDouble("Comparator.DeptSpreadPenaltyWeight",1.0);
            iDistanceInstructorPreferenceWeight[1] = properties.getPropertyDouble("Comparator.DistanceInstructorPreferenceWeight",1.0);
            iPerturbationPenaltyWeight[1] = (iMPP?properties.getPropertyDouble("Comparator.PerturbationPenaltyWeight",1.0):0.0);

            iNrConflictsWeight[2] = 0.0;
            iNrPotentialConflictsWeight[2] = 0.0;
            iNrWeightedConflictsWeight[2] = 0.0;
            iDeltaTimePreferenceWeight[2] = 0.0;
            iPerturbationPenaltyWeight[2] = 0.0;
            iNrStudentConflictsWeight[2] = 0.0;
            iNrHardStudentConflictsWeight[2] = 0.0;
            iUselessSlotsWeight[2] = 0.0;
            iSumConstrPreferencesWeight[2] = 0.0;
            iSumRoomPreferencesWeight[2] = 0.0;
            iSumTimePreferencesWeight[2] = 0.0;
            iNrAssignmentsWeight[2] = 0.0;
            iThresholdKoef[2] = 0.0;
            iTooBigRoomWeight[2] = 0.0;
            iDeptSpreadWeight[2] = 0.0;
            iDistanceInstructorPreferenceWeight[2] = 0.0;
        }
    }
    
    public Value selectValue(Solution solution, Variable selectedVariable) {
        /*if (iMPPLimit>=0 && solution.getModel().unassignedVariables().isEmpty() && iMPPLimit>solution.getModel().perturbVariables().size()) {
            ToolBox.print("A complete solution with "+solution.getModel().perturbVariables().size()+" perturbances found");
            iMPPLimit = solution.getModel().perturbVariables().size()-1;
            ToolBox.print("MPP limit decreased to "+iMPPLimit);
        }*/
        if (selectedVariable.getInitialAssignment()!=null) {
            if (iMPPLimit>=0 && solution.getModel().perturbVariables().size()>=iMPPLimit) return checkValue(selectedVariable.getInitialAssignment());
            if (iMPPPenaltyLimit>=0.0 && solution.getPerturbationsCounter()!=null && solution.getPerturbationsCounter().getPerturbationPenalty(solution)>iMPPPenaltyLimit) return checkValue(selectedVariable.getInitialAssignment());
            if (selectedVariable.getInitialAssignment()!=null && ToolBox.random()<=iInitialSelectionProb) return checkValue(selectedVariable.getInitialAssignment());
        }
        
        Vector values = selectedVariable.values();
        if (iRW && ToolBox.random()<=iRandomWalkProb) return checkValue((Value)ToolBox.random(values));
        if (iProp!=null && selectedVariable.getAssignment()==null && ToolBox.random()<=iGoodSelectionProb) {
            Collection goodValues = iProp.goodValues(selectedVariable);
            if (!goodValues.isEmpty()) values=new FastVector(goodValues);
        }
        if (values.size()==1) return checkValue((Value)values.firstElement());
        
        long[] bestCost = new long[NR_LEVELS];
        Vector selectionValues = null;
        
        
        HeuristicSelector selector = (iUseThreshold?new HeuristicSelector(iThresholdKoef):null);
        for (Enumeration i1=values.elements();i1.hasMoreElements();) {
            Value value = (Value) i1.nextElement();
            if (iTabu!=null && iTabu.contains(value)) continue;
            if (selectedVariable.getAssignment()!=null && selectedVariable.getAssignment().equals(value)) continue;
            ParamRetriever paramRetriever = new ParamRetriever(solution, (Lecture)selectedVariable, (Placement)value);
            
            if (iUseThreshold) {
                double[] costs = new double[NR_LEVELS];
                for (int level=0;level<NR_LEVELS;level++) {
                    double cost =
                    (iNrConflictsWeight[level]==0.0?0.0:iNrConflictsWeight[level]*paramRetriever.nrContlicts())+
                    (iNrWeightedConflictsWeight[level]==0.0?0.0:iNrWeightedConflictsWeight[level]*paramRetriever.weightedConflicts())+
                    (iNrPotentialConflictsWeight[level]==0.0?0.0:iNrPotentialConflictsWeight[level]*paramRetriever.potentialConflicts(3))+
                    (iDeltaTimePreferenceWeight[level]==0.0?0.0:iDeltaTimePreferenceWeight[level]*paramRetriever.deltaTimePreference())+
                    (iPerturbationPenaltyWeight[level]==0.0?0.0:iPerturbationPenaltyWeight[level]*paramRetriever.perturbationsPenalty())+
                    (iNrStudentConflictsWeight[level]==0.0?0.0:iNrStudentConflictsWeight[level]*paramRetriever.sumStudentConflicts())+
                    (iNrHardStudentConflictsWeight[level]==0.0?0.0:iNrHardStudentConflictsWeight[level]*paramRetriever.sumHardStudentConflicts())+
                    (iUselessSlotsWeight[level]==0.0?0.0:iUselessSlotsWeight[level]*paramRetriever.emptySingleHalfHours())+
                    (iSumConstrPreferencesWeight[level]==0.0?0.0:iSumConstrPreferencesWeight[level]*paramRetriever.constrPreference())+
                    (iSumRoomPreferencesWeight[level]==0.0?0.0:iSumRoomPreferencesWeight[level]*paramRetriever.roomPreference())+
                    (iSumTimePreferencesWeight[level]==0.0?0.0:iSumTimePreferencesWeight[level]*paramRetriever.timePreference())+
                    (iNrAssignmentsWeight[level]==0.0?0.0:iNrAssignmentsWeight[level]*paramRetriever.nrAssignments())+
                    (iTooBigRoomWeight[level]==0.0?0.0:paramRetriever.tooBig()?iTooBigRoomWeight[level]:0.0)+
                    (iDeptSpreadWeight[level]==0.0?0.0:iDeptSpreadWeight[level]*paramRetriever.deptSpread())+
                    (iDistanceInstructorPreferenceWeight[level]==0.0?0.0:iDistanceInstructorPreferenceWeight[level]*paramRetriever.distanceInstructorPreference())
                    ;
                    costs[level]=cost;
                }
                selector.add(costs, value);
            } else {
                boolean fail = false;
                boolean best = false;
                for (int level=0;!fail && level<1;level++) {
                    long cost = Math.round( PRECISION * (
                    (iNrConflictsWeight[level]==0.0?0.0:iNrConflictsWeight[level]*paramRetriever.nrContlicts())+
                    (iNrWeightedConflictsWeight[level]==0.0?0.0:iNrWeightedConflictsWeight[level]*paramRetriever.weightedConflicts())+
                    (iNrPotentialConflictsWeight[level]==0.0?0.0:iNrPotentialConflictsWeight[level]*paramRetriever.potentialConflicts(0))+
                    (iDeltaTimePreferenceWeight[level]==0.0?0.0:iDeltaTimePreferenceWeight[level]*paramRetriever.deltaTimePreference())+
                    (iPerturbationPenaltyWeight[level]==0.0?0.0:iPerturbationPenaltyWeight[level]*paramRetriever.perturbationsPenalty())+
                    (iNrStudentConflictsWeight[level]==0.0?0.0:iNrStudentConflictsWeight[level]*paramRetriever.sumStudentConflicts())+
                    (iNrHardStudentConflictsWeight[level]==0.0?0.0:iNrHardStudentConflictsWeight[level]*paramRetriever.sumHardStudentConflicts())+
                    (iUselessSlotsWeight[level]==0.0?0.0:iUselessSlotsWeight[level]*paramRetriever.emptySingleHalfHours())+
                    (iSumConstrPreferencesWeight[level]==0.0?0.0:iSumConstrPreferencesWeight[level]*paramRetriever.constrPreference())+
                    (iSumRoomPreferencesWeight[level]==0.0?0.0:iSumRoomPreferencesWeight[level]*paramRetriever.roomPreference())+
                    (iSumTimePreferencesWeight[level]==0.0?0.0:iSumTimePreferencesWeight[level]*paramRetriever.timePreference())+
                    (iNrAssignmentsWeight[level]==0.0?0.0:iNrAssignmentsWeight[level]*paramRetriever.nrAssignments())+
                    (iTooBigRoomWeight[level]==0.0?0.0:paramRetriever.tooBig()?iTooBigRoomWeight[level]:0.0)+
                    (iDeptSpreadWeight[level]==0.0?0.0:iDeptSpreadWeight[level]*paramRetriever.deptSpread())+
                    (iDistanceInstructorPreferenceWeight[level]==0.0?0.0:iDistanceInstructorPreferenceWeight[level]*paramRetriever.distanceInstructorPreference())
/*                    (iNrConflictsWeight[level]*conflicts)+
                    (iNrWeightedConflictsWeight[level]*weightedConflicts)+
                    (iDeltaTimePreferenceWeight[level]*deltaTimePreference)+
                    (iDeltaInitialAssignmentsWeight[level]*deltaInitialAssignments)+
                    (iNrStudentConflictsWeight[level]*sumStudentConflicts)+
                    (iNrHardStudentConflictsWeight[level]*sumHardStudentConflicts)+
                    (iUselessSlotsWeight[level]*emptySingleHalfHours)+
                    (iSumConstrPreferencesWeight[level]*constrPreference)+
                    (iSumRoomPreferencesWeight[level]*roomPreference)+
                    (iSumTimePreferencesWeight[level]*timePrefernce)+
                    (iNrAssignmentsWeight[level]*nrAssignments)*/
                    ));
                    if (selectionValues!=null && !best) {
                        if (cost>bestCost[level]) { fail=true; }
                        if (cost<bestCost[level]) { bestCost[level]=cost; selectionValues.clear(); best=true; }
                    } else {
                        bestCost[level]=cost;
                    }
                }
                if (selectionValues==null) selectionValues = new FastVector(values.size());
                if (!fail) selectionValues.addElement(value);
            }
            
        }
        //ToolBox.print("Best "+selectionValues.size()+" locations for variable "+selectedVariable.getId()+" have "+bestConflicts+" conflicts ("+bestRemovals+" weighted) and "+bestStudentConflicts+" ("+bestOriginalStudentConflicts+" * "+bestKoef+" + "+bestPenalty+") preference.");
        Value selectedValue = null;
        if (iUseThreshold) {
            selectionValues = selector.selection();
            
            if (selectedVariable.getInitialAssignment()!=null) {
                for (Enumeration e=selectionValues.elements();e.hasMoreElements();) {
                    Value value = (Value)((HeuristicSelector.Element) e.nextElement()).getObject();
                    if (value.equals(selectedVariable.getInitialAssignment())) {
                        selectedValue = value;
                        break;
                    }
                }
                //&& selectionValues.contains(selectedVariable.getInitialAssignment())) return selectedVariable.getInitialAssignment();
            } 
            
            if (selectedValue==null) {
                HeuristicSelector.Element selection = (HeuristicSelector.Element)ToolBox.random(selectionValues);
                selectedValue = (Value)(selection==null?null:selection.getObject());
            }
        } else {
            if (selectedVariable.getInitialAssignment()!=null && selectionValues.contains(selectedVariable.getInitialAssignment())) return checkValue(selectedVariable.getInitialAssignment());
            selectedValue = (Value)ToolBox.random(selectionValues);
        }
        if (selectedValue!=null && iTabu!=null) {
            if (iTabu.size()==iTabuPos)
                iTabu.add(selectedValue);
            else
                iTabu.set(iTabuPos, selectedValue);
            iTabuPos = (iTabuPos + 1) % iTabuSize;
        }
        return checkValue(selectedValue);
    }
    
    private Value checkValue(Value aValue) {
        if (aValue==null) return null;
        DepartmentSpreadConstraint c = ((Lecture)aValue.variable()).getDeptSpreadConstraint();
        if (c!=null && c.inConflict(aValue)) c.incUnassignmentCounter(aValue);
        return aValue;
    }
    
    private class ParamRetriever {
        private Lecture iLecture;
        private Placement iPlacement;
        private Solution iSolution;
        private ParamRetriever(Solution solution, Lecture lecture, Placement placement) {
            iSolution = solution;
            iLecture = lecture;
            iPlacement = placement;
        }
        
        Collection iConf = null;
        private Collection conf() {
            if (iConf == null) iConf = iSolution.getModel().conflictValues(iPlacement);
            return iConf;
        }
        
        public long nrContlicts() {
            return conf().size();
        }
        
        private Double iWeightedConflicts = null;
        public double weightedConflicts() {
            if (iWeightedConflicts==null) 
                iWeightedConflicts = new Double(iStat==null?0.0:iStat.countRemovals(iSolution.getIteration(), conf(), iPlacement));
            return iWeightedConflicts.doubleValue();
        }
        
        private Double iPotentialConflicts = null;
        public double potentialConflicts(int limit) {
            if (iPotentialConflicts==null)
                iPotentialConflicts = new Double(iStat==null?0.0:iStat.countPotentialConflicts(iSolution.getIteration(),iPlacement, limit));
            return iPotentialConflicts.doubleValue();
        }
         
        Double iDeltaTimePreference = null;
        public double deltaTimePreference() {
            if (iDeltaTimePreference==null) {
                double deltaTimePreference = 0;
                for (Iterator it1=conf().iterator(); it1.hasNext(); ) {
                    Placement placement = (Placement)it1.next();
                    double timePref = placement.getTimeLocation().getNormalizedPreference();
                    deltaTimePreference -= timePref - ((Lecture)placement.variable()).getBestTimePreference();
                }
                deltaTimePreference += iPlacement.getTimeLocation().getNormalizedPreference() - iLecture.getBestTimePreference();
                iDeltaTimePreference = new Double(deltaTimePreference);
            }
            return iDeltaTimePreference.doubleValue();
        }
        
        Double iPerturbationsPenalty = null;
        public double perturbationsPenalty() {
            if (iPerturbationsPenalty==null) {
                iPerturbationsPenalty = new Double(iPerturbationsCounter.getPerturbationPenalty(iSolution,iPlacement,conf()));
            }
            return iPerturbationsPenalty.doubleValue();
        }
        
        Integer iSumStudentConflicts = null;
        public int sumStudentConflicts() {
            if (iSumStudentConflicts==null) 
                iSumStudentConflicts = new Integer(iLecture.countStudentConflicts(iPlacement));
            return iSumStudentConflicts.intValue();
        }
        
        Integer iConstrPreference = null;
        public int constrPreference() {
            if (iConstrPreference==null) {
                int constrPreference = 0;
                for (Enumeration i2=iLecture.softConstraints().elements();i2.hasMoreElements();) {
                    Constraint constraint = (Constraint)i2.nextElement();
                    if (constraint instanceof GroupConstraint) {
                        GroupConstraint gc = (GroupConstraint)constraint;
                        constrPreference += gc.getCurrentPreference(iPlacement);
                    }
                }
                iConstrPreference = new Integer(constrPreference);
            }
            return iConstrPreference.intValue();
        }
        
        Integer iEmptySingleHalfHours = null;
        public int emptySingleHalfHours() {
            if (iEmptySingleHalfHours==null) {
                int emptySingleHalfHours = 0;
                for (Enumeration i2=iLecture.hardConstraints().elements();i2.hasMoreElements();) {
                    Constraint constraint = (Constraint)i2.nextElement();
                    if (constraint instanceof RoomConstraint) {
                        RoomConstraint rc = (RoomConstraint)constraint;
                        for (int i=0; i<iPlacement.getTimeLocation().getStartSlots().length; i++) {
                            int startSlot = iPlacement.getTimeLocation().getStartSlots()[i];
                            int endSlot = startSlot + iPlacement.getTimeLocation().getLength() - 1;
                            if (((startSlot%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)>=2) && rc.getResource()[startSlot-1]==null && rc.getResource()[startSlot-2]!=null)
                                emptySingleHalfHours++;
                            if (((endSlot%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)<edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY-2) && rc.getResource()[startSlot+1]==null && rc.getResource()[startSlot+2]!=null)
                                emptySingleHalfHours++;
                        }
                    }
                }
                iEmptySingleHalfHours = new Integer(emptySingleHalfHours);
            }
            return iEmptySingleHalfHours.intValue();
        }
        
        Integer iSumHardStudentConflicts = null;
        public int sumHardStudentConflicts() {
            if (iSumHardStudentConflicts==null) {
                boolean haveAlternative = (iLecture.sameLectures()==null?false:iLecture.sameLectures().size()>1);
                int sumHardStudentConflicts = 0;
                if (!haveAlternative) for (Enumeration i2=iLecture.jenrlConstraints().elements();i2.hasMoreElements();) {
                    JenrlConstraint jenrl = (JenrlConstraint)i2.nextElement();
                    Vector anotherSameLectures = ((Lecture)jenrl.another(iLecture)).sameLectures();
                    if (anotherSameLectures==null || anotherSameLectures.size()==1)
                        sumHardStudentConflicts += jenrl.jenrl(iLecture, iPlacement);
                }
                iSumHardStudentConflicts = new Integer(sumHardStudentConflicts);
            }
            return iSumHardStudentConflicts.intValue();
        }

        public int roomPreference() {
            return iPlacement.getRoomLocation().getPreference();
        }
        
        public long nrAssignments() {
            return iPlacement.countAssignments();
        }
        
        public double timePreference() {
            return iPlacement.getTimeLocation().getNormalizedPreference();
        }
        
        public boolean tooBig() {
            return iPlacement.getRoomLocation().getRoomSize()>TimetableModel.getMaxCapacity(iLecture.countStudents());
        }
        
        Integer iDeptSpreadCache = null;
        public int deptSpread() {
            if (iLecture.getDeptSpreadConstraint()==null) return 0;
            if (iDeptSpreadCache==null) 
                iDeptSpreadCache = new Integer(iLecture.getDeptSpreadConstraint().getPenalty(iPlacement));
            return iDeptSpreadCache.intValue();
        }
        
        Integer iDistanceInstructorPreferenceCache = null;
        public int distanceInstructorPreference() {
            if (iDistanceInstructorPreferenceCache==null) {
                int pref = 0;
                if (iPlacement.getInstructorId()!=null) {
                    for (Enumeration i2=iLecture.constraints().elements();i2.hasMoreElements();) {
                        Constraint constraint = (Constraint)i2.nextElement();
                        if (constraint instanceof InstructorConstraint) {
                            pref += ((InstructorConstraint)constraint).getPreference(iPlacement);
                            break;
                        }
                    }
                }
                iDistanceInstructorPreferenceCache = new Integer(pref);
            }
            return iDistanceInstructorPreferenceCache.intValue();
        }
    }
}
