package InitialSchedules;

import org.chocosolver.solver.Model;
import org.chocosolver.solver.variables.BoolVar;
import org.chocosolver.solver.variables.IntVar;

public class ModelInitialSchedules {

    //	private final int HEURISTIQUE_DEFAUT = 0;
//	private final int HEURISTIQUE_DOMOVERWDEG = 1;
//	private final int HEURISTIQUE_IMPACT_BASED_SEARCH = 2;
//	private final int HEURISTIQUE_ACTIVITY = 3;
//	private final String COHERENCE_BORNES = "BC";
//	private final String COHERENCE_DOMAINES = "AC";
//	private final int RESTART_AUCUN = 0;
//	private final int RESTART_LUBY = 1;
//	private final int RESTART_GEOMETRIQUE = 2;
    public ParametersInitialSchedules myScheduleParameters;

    public int maxPartTimeEmployee;
    public int maxFullTimeEmployee;

    public BoolVar workPeriodsSchedulesOfPartTimeEmployees[][];
    private BoolVar transposeWorkPeriodsSchedulesOfPartTimeEmployees[][];
    public BoolVar workPeriodsSchedulesOfFullTimeEmployees[][];
    private BoolVar transposeWorkPeriodsSchedulesOfFullTimeEmployees[][];

    private BoolVar allWorkPeriods[];

    private IntVar partTimeEmployeesPerWorkPeriods[];
    private IntVar fullTimeEmployeesPerWorkPeriods[];

    public Model chocoModelInitialSchedules;
    public IntVar totalEmployeesCost;

    public ModelInitialSchedules(ParametersInitialSchedules myScheduleParameters) {

        this.myScheduleParameters = myScheduleParameters;

        this.chocoModelInitialSchedules = new Model("Model Initial Schedules");

        this.createEmployeesVariables();
        this.createScheduleVariables();

        this.createModelConstraints();

        this.createModelObjectiveFunction();

    }

    private void createEmployeesVariables() {

        this.maxPartTimeEmployee = (int) Math.ceil((double) myScheduleParameters.getTotalWorkedPeriodsInSchedule()
                / myScheduleParameters.getMinWorkingPeriodsOfPartTimeEmployeesPerSchedule());
        this.maxFullTimeEmployee = (int) Math.ceil((double) myScheduleParameters.getTotalWorkedPeriodsInSchedule()
                / myScheduleParameters.getWorkingPeriodsOfFullTimeEmployeesPerSchedule());

    }

    private void createScheduleVariables() {

        // Variable pour l'horaire des employes
        this.workPeriodsSchedulesOfFullTimeEmployees = chocoModelInitialSchedules.boolVarMatrix(this.maxFullTimeEmployee, this.myScheduleParameters.getWorkPeriodsPerSchedule());
        this.workPeriodsSchedulesOfPartTimeEmployees = chocoModelInitialSchedules.boolVarMatrix(this.maxPartTimeEmployee, this.myScheduleParameters.getWorkPeriodsPerSchedule());

        // Vecteur de toutes les variables
        /*this.allWorkPeriods = chocoModelInitialSchedules.boolVarArray((this.maxPartTimeEmployee+this.maxFullTimeEmployee)*this.myScheduleParameters.workPeriodsPerSchedule);

        for (int employee = 0; employee < this.maxPartTimeEmployee; employee++) {
            for(int workPeriod = 0; workPeriod < this.myScheduleParameters.workPeriodsPerSchedule; workPeriod++){
                this.allWorkPeriods[employee*this.myScheduleParameters.workPeriodsPerSchedule+workPeriod] =
                        this.workPeriodsSchedulesOfPartTimeEmployees[employee][workPeriod];
            }
        }
        for (int employee = 0; employee < this.maxFullTimeEmployee; employee++) {
            for(int workPeriod = 0; workPeriod < this.myScheduleParameters.workPeriodsPerSchedule; workPeriod++){
                this.allWorkPeriods[this.maxPartTimeEmployee*this.myScheduleParameters.workPeriodsPerSchedule+
                        employee*this.myScheduleParameters.workPeriodsPerSchedule+workPeriod] =
                        this.workPeriodsSchedulesOfFullTimeEmployees[employee][workPeriod];
            }
        }*/

        // Creer la transpose des horaires pour compter les elements des colonnes
        this.transposeWorkPeriodsSchedulesOfPartTimeEmployees = chocoModelInitialSchedules.boolVarMatrix(this.myScheduleParameters.getWorkPeriodsPerSchedule(), this.maxPartTimeEmployee);
        for (int workPeriod = 0; workPeriod < this.myScheduleParameters.getWorkPeriodsPerSchedule(); workPeriod++) {
            for (int employee = 0; employee < this.maxPartTimeEmployee; employee++) {
                this.transposeWorkPeriodsSchedulesOfPartTimeEmployees[workPeriod][employee]
                        = this.workPeriodsSchedulesOfPartTimeEmployees[employee][workPeriod];
            }
        }
        this.transposeWorkPeriodsSchedulesOfFullTimeEmployees = chocoModelInitialSchedules.boolVarMatrix(this.myScheduleParameters.getWorkPeriodsPerSchedule(), this.maxFullTimeEmployee);
        for (int workPeriod = 0; workPeriod < this.myScheduleParameters.getWorkPeriodsPerSchedule(); workPeriod++) {
            for (int employee = 0; employee < this.maxFullTimeEmployee; employee++) {
                this.transposeWorkPeriodsSchedulesOfFullTimeEmployees[workPeriod][employee]
                        = this.workPeriodsSchedulesOfFullTimeEmployees[employee][workPeriod];
            }
        }

        // Variable pour faire le compte du nombre d'employes par periode de travail
        this.partTimeEmployeesPerWorkPeriods = chocoModelInitialSchedules.intVarArray(this.myScheduleParameters.getWorkPeriodsPerSchedule(), 0, this.maxPartTimeEmployee, true);
        this.fullTimeEmployeesPerWorkPeriods = chocoModelInitialSchedules.intVarArray(this.myScheduleParameters.getWorkPeriodsPerSchedule(), 0, this.maxFullTimeEmployee, true);

    }

    private void createModelConstraints() {

        // Constraintes tableau pour choisir un type d'horaire dans les horaires possibles
        for (int employee = 0; employee < this.maxFullTimeEmployee; employee++) {
            chocoModelInitialSchedules.table(this.workPeriodsSchedulesOfFullTimeEmployees[employee],
                    this.myScheduleParameters.enumerationWorkPeriodsSchedulesOfFullTimeEmployees).post();
        }
        for (int employee = 0; employee < this.maxPartTimeEmployee; employee++) {
            chocoModelInitialSchedules.table(this.workPeriodsSchedulesOfPartTimeEmployees[employee],
                    this.myScheduleParameters.enumerationWorkPeriodsSchedulesOfPartTimeEmployees).post();
        }

        // Constraintes pour compter le nombre d'employes par periode de travail et s'assurer qu'il
        // satisfait la demande en employes
        for (int workPeriod = 0; workPeriod < this.myScheduleParameters.getWorkPeriodsPerSchedule(); workPeriod++) {
            chocoModelInitialSchedules.count(chocoModelInitialSchedules.intVar(1),
                    transposeWorkPeriodsSchedulesOfPartTimeEmployees[workPeriod],
                    this.partTimeEmployeesPerWorkPeriods[workPeriod]).post();
            chocoModelInitialSchedules.count(chocoModelInitialSchedules.intVar(1),
                    transposeWorkPeriodsSchedulesOfFullTimeEmployees[workPeriod],
                    this.fullTimeEmployeesPerWorkPeriods[workPeriod]).post();
            chocoModelInitialSchedules.arithm(this.partTimeEmployeesPerWorkPeriods[workPeriod], "+",
                    this.fullTimeEmployeesPerWorkPeriods[workPeriod], "=",
                    this.myScheduleParameters.requiredWorkforce[workPeriod]).post();
        }

        // Contrainte de bris de symétrie:
        // chaque horaire d'employé est léxicographiquement inférieure à la suivante, par type d'employé
        // Ne fonctionne pas avec l'heuristique par défaut
//        for (int employee = 0; employee < (this.maxPartTimeEmployee-1); employee++){
//            chocoModelInitialSchedules.lexLessEq(this.workPeriodsSchedulesOfPartTimeEmployees[employee],
//                    this.workPeriodsSchedulesOfPartTimeEmployees[employee+1]).post();
//        }
        for (int employee = 0; employee < (this.maxFullTimeEmployee-1); employee++){
            chocoModelInitialSchedules.lexLessEq(this.workPeriodsSchedulesOfFullTimeEmployees[employee],
                    this.workPeriodsSchedulesOfFullTimeEmployees[employee+1]).post();
        }

    }

    private void createModelObjectiveFunction() {

        IntVar numberOfPartTimeEmployeesWorkingPeriods =
                chocoModelInitialSchedules.intVar(
                        0, this.maxPartTimeEmployee * this.myScheduleParameters.getWorkPeriodsPerSchedule(), true);
        IntVar numberOfFullTimeEmployeesWorkingPeriods =
                chocoModelInitialSchedules.intVar(
                        0, this.maxPartTimeEmployee * this.myScheduleParameters.getWorkPeriodsPerSchedule(), true);

        chocoModelInitialSchedules.sum(this.partTimeEmployeesPerWorkPeriods, "=", numberOfPartTimeEmployeesWorkingPeriods).post();
        chocoModelInitialSchedules.sum(this.fullTimeEmployeesPerWorkPeriods, "=", numberOfFullTimeEmployeesWorkingPeriods).post();

        // Calcul du salaire versé
        IntVar partTimeEmployeesSalary =
                chocoModelInitialSchedules.intVar(0, this.maxPartTimeEmployee *
                        this.myScheduleParameters.getWorkPeriodsPerSchedule() *
                        this.myScheduleParameters.getWorkingPeriodCostOfPartTimeEmployeesPaidAtRegularHourlyRate(), true);

        IntVar fullTimeEmployeesSalary =
                chocoModelInitialSchedules.intVar(0, this.maxFullTimeEmployee *
                        this.myScheduleParameters.getWorkPeriodsPerSchedule() *
                        this.myScheduleParameters.getWorkingPeriodCostOfFullTimeEmployeesPaidAtRegularHourlyRate(), true);

        IntVar workingPeriodCostOfPartTimeEmployees =
                chocoModelInitialSchedules.intVar(this.myScheduleParameters.getWorkingPeriodCostOfPartTimeEmployeesPaidAtRegularHourlyRate());
        IntVar workingPeriodCostOfFullTimeEmployees =
                chocoModelInitialSchedules.intVar(this.myScheduleParameters.getWorkingPeriodCostOfFullTimeEmployeesPaidAtRegularHourlyRate());

        chocoModelInitialSchedules.arithm(numberOfPartTimeEmployeesWorkingPeriods, "*", workingPeriodCostOfPartTimeEmployees, "=", partTimeEmployeesSalary).post();
        chocoModelInitialSchedules.arithm(numberOfFullTimeEmployeesWorkingPeriods, "*", workingPeriodCostOfFullTimeEmployees, "=", fullTimeEmployeesSalary).post();

        // Compter le nombre d'heures travaillees par les differents employes pour determiner les employes actifs
        IntVar[] workingPeriodsPerPartTimeEmployees =
                chocoModelInitialSchedules.intVarArray(this.maxPartTimeEmployee,0,this.myScheduleParameters.getWorkPeriodsPerSchedule(), true);
        BoolVar[] isWorkingPartTimeEmployees = 
                chocoModelInitialSchedules.boolVarArray(this.maxPartTimeEmployee);
        for (int employee = 0; employee < this.maxPartTimeEmployee; employee++) {
            chocoModelInitialSchedules.sum(workPeriodsSchedulesOfPartTimeEmployees[employee],
                    "=", workingPeriodsPerPartTimeEmployees[employee]).post();
            BoolVar isWorkingEmployee = chocoModelInitialSchedules.arithm(
                    workingPeriodsPerPartTimeEmployees[employee], ">", 0).reify();
            chocoModelInitialSchedules.ifThen(isWorkingEmployee,
                    chocoModelInitialSchedules.arithm(isWorkingPartTimeEmployees[employee], "=", chocoModelInitialSchedules.boolVar(true)));
        }
        IntVar workingPartTimeEmployees = chocoModelInitialSchedules.intVar(0, this.maxPartTimeEmployee);
        chocoModelInitialSchedules.sum(isWorkingPartTimeEmployees, "=", workingPartTimeEmployees).post();
        
        IntVar[] workingPeriodsPerFullTimeEmployees =
                chocoModelInitialSchedules.intVarArray(this.maxFullTimeEmployee,0,this.myScheduleParameters.getWorkPeriodsPerSchedule(), true);
        BoolVar[] isWorkingFullTimeEmployees = 
                chocoModelInitialSchedules.boolVarArray(this.maxFullTimeEmployee);
        for (int employee = 0; employee < this.maxFullTimeEmployee; employee++) {
            chocoModelInitialSchedules.sum(workPeriodsSchedulesOfFullTimeEmployees[employee],
                    "=", workingPeriodsPerFullTimeEmployees[employee]).post();
            BoolVar isWorkingEmployee = chocoModelInitialSchedules.arithm(
                    workingPeriodsPerFullTimeEmployees[employee], ">", 0).reify();
            chocoModelInitialSchedules.ifThen(isWorkingEmployee,
                    chocoModelInitialSchedules.arithm(isWorkingFullTimeEmployees[employee], "=", chocoModelInitialSchedules.boolVar(true)));
        }
        IntVar workingFullTimeEmployees = chocoModelInitialSchedules.intVar(0, this.maxFullTimeEmployee);
        chocoModelInitialSchedules.sum(isWorkingFullTimeEmployees, "=", workingFullTimeEmployees).post();
        
        // Calcul des couts fixes
        IntVar partTimeEmployeesFixedCost =
                chocoModelInitialSchedules.intVar(0, this.maxPartTimeEmployee *
                        this.myScheduleParameters.getFixedCostOfPartTimeEmployeesPerSchedule(), true);

        IntVar fullTimeEmployeesFixedCost =
                chocoModelInitialSchedules.intVar(0, this.maxFullTimeEmployee *
                        this.myScheduleParameters.getFixedCostOfFullTimeEmployeesPerSchedule(), true);
        
        chocoModelInitialSchedules.arithm(workingPartTimeEmployees, "*", chocoModelInitialSchedules.intVar(this.myScheduleParameters.getFixedCostOfPartTimeEmployeesPerSchedule()),
                "=", partTimeEmployeesFixedCost).post();
        chocoModelInitialSchedules.arithm(workingPartTimeEmployees, "*", chocoModelInitialSchedules.intVar(this.myScheduleParameters.getFixedCostOfPartTimeEmployeesPerSchedule()),
                "=", partTimeEmployeesFixedCost).post();
        
        // Calcul des couts totaux
        IntVar partTimeEmployeesCost =
                chocoModelInitialSchedules.intVar(0, this.maxPartTimeEmployee *
                        this.myScheduleParameters.getFixedCostOfPartTimeEmployeesPerSchedule() +
                        this.maxPartTimeEmployee *
                        this.myScheduleParameters.getWorkPeriodsPerSchedule() *
                        this.myScheduleParameters.getWorkingPeriodCostOfPartTimeEmployeesPaidAtRegularHourlyRate(), true);

        IntVar fullTimeEmployeesCost =
                chocoModelInitialSchedules.intVar(0, this.maxFullTimeEmployee *
                        this.myScheduleParameters.getFixedCostOfFullTimeEmployeesPerSchedule() + 
                        this.maxFullTimeEmployee *
                        this.myScheduleParameters.getWorkPeriodsPerSchedule() *
                        this.myScheduleParameters.getWorkingPeriodCostOfFullTimeEmployeesPaidAtRegularHourlyRate(), true);
        
        chocoModelInitialSchedules.arithm(partTimeEmployeesFixedCost, "+", partTimeEmployeesSalary, "=", partTimeEmployeesCost).post();
        chocoModelInitialSchedules.arithm(fullTimeEmployeesFixedCost, "+", fullTimeEmployeesSalary, "=", fullTimeEmployeesCost).post();
        
        this.totalEmployeesCost = chocoModelInitialSchedules.intVar(0, this.maxPartTimeEmployee *
                        this.myScheduleParameters.getFixedCostOfPartTimeEmployeesPerSchedule() +
                        this.maxPartTimeEmployee *
                        this.myScheduleParameters.getWorkPeriodsPerSchedule() *
                        this.myScheduleParameters.getWorkingPeriodCostOfPartTimeEmployeesPaidAtRegularHourlyRate() + 
                        this.maxFullTimeEmployee *
                        this.myScheduleParameters.getFixedCostOfFullTimeEmployeesPerSchedule() + 
                        this.maxFullTimeEmployee *
                        this.myScheduleParameters.getWorkPeriodsPerSchedule() *
                        this.myScheduleParameters.getWorkingPeriodCostOfFullTimeEmployeesPaidAtRegularHourlyRate(), true);
        
        chocoModelInitialSchedules.arithm(partTimeEmployeesCost, "+", fullTimeEmployeesCost, "=", this.totalEmployeesCost).post();

    }

}
