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 workPeriodsOfPartTimeEmployees[][];
    private BoolVar workPeriodsOfFullTimeEmployees[][];

    public BoolVar allWorkPeriods[];

    public IntVar workingPeriodsPerPartTimeEmployees[];
    public IntVar partTimeEmployeesPerWorkPeriods[];
    public IntVar workingPeriodsPerFullTimeEmployees[];
    public IntVar fullTimeEmployeesPerWorkPeriods[];
    public IntVar employeesPerWorkPeriods[];

    public Model chocoModelInitialSchedules;
    public IntVar TotalEmployeesSalary;
    public IntVar scheduleProfit;

    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.totalWorkedPeriodsInSchedule
                / myScheduleParameters.minWorkingPeriodsOfPartTimeEmployeesPerSchedule);
        this.maxFullTimeEmployee = (int) Math.ceil((double) myScheduleParameters.totalWorkedPeriodsInSchedule
                / myScheduleParameters.workingPeriodsOfFullTimeEmployeesPerSchedule);

    }

    private void createScheduleVariables() {

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

        // 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];
            }
        }*/

        // Variable pour faire le compte du nombre d'heures des employes
        this.workingPeriodsPerPartTimeEmployees = chocoModelInitialSchedules.intVarArray(this.maxPartTimeEmployee, 0, this.myScheduleParameters.workPeriodsPerSchedule, true);
        this.workingPeriodsPerFullTimeEmployees = chocoModelInitialSchedules.intVarArray(this.maxFullTimeEmployee, 0, this.myScheduleParameters.workPeriodsPerSchedule, true);

        // Creer la transpose des horaires pour compter les elements des colonnes
        this.transposeWorkPeriodsSchedulesOfPartTimeEmployees = chocoModelInitialSchedules.boolVarMatrix(this.myScheduleParameters.workPeriodsPerSchedule, this.maxPartTimeEmployee);
        for (int workPeriod = 0; workPeriod < this.myScheduleParameters.workPeriodsPerSchedule; workPeriod++) {
            for (int employee = 0; employee < this.maxPartTimeEmployee; employee++) {
                this.transposeWorkPeriodsSchedulesOfPartTimeEmployees[workPeriod][employee]
                        = this.workPeriodsSchedulesOfPartTimeEmployees[employee][workPeriod];
            }
        }
        this.transposeWorkPeriodsSchedulesOfFullTimeEmployees = chocoModelInitialSchedules.boolVarMatrix(this.myScheduleParameters.workPeriodsPerSchedule, this.maxFullTimeEmployee);
        for (int workPeriod = 0; workPeriod < this.myScheduleParameters.workPeriodsPerSchedule; 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.workPeriodsPerSchedule, 0, this.maxPartTimeEmployee, true);
        this.fullTimeEmployeesPerWorkPeriods = chocoModelInitialSchedules.intVarArray(this.myScheduleParameters.workPeriodsPerSchedule, 0, this.maxFullTimeEmployee, true);
        this.employeesPerWorkPeriods = chocoModelInitialSchedules.intVarArray(this.myScheduleParameters.workPeriodsPerSchedule,
                0, this.maxPartTimeEmployee + this.maxFullTimeEmployee, true);

        // Variable pour les périodes de travail par jour des travailleurs à temps partiel
        this.workPeriodsOfPartTimeEmployees = chocoModelInitialSchedules.boolVarMatrix(this.maxPartTimeEmployee * this.myScheduleParameters.daysPerSchedule
                , this.myScheduleParameters.workPeriodsPerDay);

        for (int employee = 0; employee < this.maxPartTimeEmployee; employee++) {
            for (int day = 0; day < this.myScheduleParameters.daysPerSchedule; day++) {
                System.arraycopy(this.workPeriodsSchedulesOfPartTimeEmployees[employee],
                        day * this.myScheduleParameters.workPeriodsPerDay + 0,
                        this.workPeriodsOfPartTimeEmployees[employee * this.myScheduleParameters.daysPerSchedule + day],
                        0, this.myScheduleParameters.workPeriodsPerDay);

            }
        }
    }

    private void createModelConstraints() {

        // Constrainte 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();
        }

        // Constraintes pour compter le nombre d'heures travaillees par les differents employes
        for (int employee = 0; employee < this.maxPartTimeEmployee; employee++) {
            chocoModelInitialSchedules.count(chocoModelInitialSchedules.intVar(1),
                    this.workPeriodsSchedulesOfPartTimeEmployees[employee],
                    this.workingPeriodsPerPartTimeEmployees[employee]).post();
        }
        for (int employee = 0; employee < this.maxFullTimeEmployee; employee++) {
            chocoModelInitialSchedules.count(chocoModelInitialSchedules.intVar(1),
                    this.workPeriodsSchedulesOfFullTimeEmployees[employee],
                    this.workingPeriodsPerFullTimeEmployees[employee]).post();
        }

        // Constraintes pour borner le nombre d'heures travaillees par les employes a temps partiel
        for (int employee = 0; employee < this.maxPartTimeEmployee; employee++) {
            BoolVar isWorkingEmployee = chocoModelInitialSchedules.arithm(
                    this.workingPeriodsPerPartTimeEmployees[employee], ">", 0).reify();
            chocoModelInitialSchedules.ifThen(isWorkingEmployee,
                    chocoModelInitialSchedules.arithm(
                            this.workingPeriodsPerPartTimeEmployees[employee], ">=",
                            this.myScheduleParameters.minWorkingPeriodsOfPartTimeEmployeesPerSchedule));
            chocoModelInitialSchedules.arithm(this.workingPeriodsPerPartTimeEmployees[employee], "<=",
                    this.myScheduleParameters.maxWorkingPeriodsOfPartTimeEmployeesPerSchedule).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.workPeriodsPerSchedule; 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();
            chocoModelInitialSchedules.arithm(this.partTimeEmployeesPerWorkPeriods[workPeriod], "+",
                    this.fullTimeEmployeesPerWorkPeriods[workPeriod], "=",
                    this.employeesPerWorkPeriods[workPeriod]).post();
        }

        // Contrainte pour forcer des heures consécutives par jour pour les employés à temps partiel

        for (int employee = 0; employee < this.maxPartTimeEmployee; employee++) {
            for (int day = 0; day < this.myScheduleParameters.daysPerSchedule; day++) {
                chocoModelInitialSchedules.table(
                        this.workPeriodsOfPartTimeEmployees[employee * this.myScheduleParameters.daysPerSchedule + day],
                        this.myScheduleParameters.ValidPartTimeEmployeeShiftTuples).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() {

        // Calcul du nombre de périodes travaillées par chaque type d'employés dans l'horaire

        IntVar[] NumberOfPartTimeEmployeesPerPeriod =
                chocoModelInitialSchedules.intVarArray(this.myScheduleParameters.workPeriodsPerSchedule,
                        0,
                        this.maxPartTimeEmployee, true);
        IntVar[] NumberOfFullTimeEmployeesPerPeriod =
                chocoModelInitialSchedules.intVarArray(this.myScheduleParameters.workPeriodsPerSchedule,
                        0,
                        this.maxFullTimeEmployee, true);

        for (int workPeriod = 0; workPeriod < this.myScheduleParameters.workPeriodsPerSchedule; workPeriod++) {
            chocoModelInitialSchedules.sum(transposeWorkPeriodsSchedulesOfPartTimeEmployees[workPeriod],
                    "=", NumberOfPartTimeEmployeesPerPeriod[workPeriod]).post();
            chocoModelInitialSchedules.sum(transposeWorkPeriodsSchedulesOfFullTimeEmployees[workPeriod],
                    "=", NumberOfFullTimeEmployeesPerPeriod[workPeriod]).post();
        }

        IntVar NumberOfPartTimeEmployeesPeriods =
                chocoModelInitialSchedules.intVar(
                        0, this.maxPartTimeEmployee * this.myScheduleParameters.workPeriodsPerSchedule, true);
        IntVar NumberOfFullTimeEmployeesPeriods =
                chocoModelInitialSchedules.intVar(
                        0, this.maxPartTimeEmployee * this.myScheduleParameters.workPeriodsPerSchedule, true);

        chocoModelInitialSchedules.sum(NumberOfPartTimeEmployeesPerPeriod, "=", NumberOfPartTimeEmployeesPeriods).post();
        chocoModelInitialSchedules.sum(NumberOfFullTimeEmployeesPerPeriod, "=", NumberOfFullTimeEmployeesPeriods).post();

        // Calcul du salaire versé
        IntVar PartTimeEmployeesSalary =
                chocoModelInitialSchedules.intVar(1000, this.maxPartTimeEmployee *
                        this.myScheduleParameters.workPeriodsPerSchedule *
                        this.myScheduleParameters.hourlyRateOfPartTimeEmployees, true);

        IntVar FullTimeEmployeesSalary =
                chocoModelInitialSchedules.intVar(1000, this.maxFullTimeEmployee *
                        this.myScheduleParameters.workPeriodsPerSchedule *
                        this.myScheduleParameters.regularHourlyRateOfFullTimeEmployees, true);

        this.TotalEmployeesSalary = chocoModelInitialSchedules.intVar(0, this.maxPartTimeEmployee *
                this.myScheduleParameters.workPeriodsPerSchedule *
                this.myScheduleParameters.hourlyRateOfPartTimeEmployees +
                this.maxFullTimeEmployee *
                        this.myScheduleParameters.workPeriodsPerSchedule *
                        this.myScheduleParameters.regularHourlyRateOfFullTimeEmployees, true);

        IntVar HourlyRateOfPartTimeEmployees =
                chocoModelInitialSchedules.intVar(this.myScheduleParameters.hourlyRateOfPartTimeEmployees);
        IntVar regularHourlyRateOfFullTimeEmployees =
                chocoModelInitialSchedules.intVar(this.myScheduleParameters.regularHourlyRateOfFullTimeEmployees);

        chocoModelInitialSchedules.arithm(NumberOfPartTimeEmployeesPeriods, "*", HourlyRateOfPartTimeEmployees, "=", PartTimeEmployeesSalary).post();
        chocoModelInitialSchedules.arithm(NumberOfFullTimeEmployeesPeriods, "*", regularHourlyRateOfFullTimeEmployees, "=", FullTimeEmployeesSalary).post();

        chocoModelInitialSchedules.arithm(PartTimeEmployeesSalary, "+", FullTimeEmployeesSalary, "=", this.TotalEmployeesSalary).post();

        //TODO: Inclure les frais fixes par employé


//		this.scheduleProfit = chocoModelInitialSchedules.intVar("TOTAL_COST", 0, 20 * N * p, true);
//		chocoModelInitialSchedules.scalar(perte , coeffs , "=", this.scheduleProfit).post();
    }

}
