import java.nio.file.*;
import java.io.IOException;
import java.util.*;
import org.chocosolver.solver.exception.ContradictionException;
import org.chocosolver.solver.Model;
import org.chocosolver.solver.variables.IntVar;
import org.chocosolver.solver.variables.BoolVar;
import org.chocosolver.solver.Solver;
import org.chocosolver.solver.search.strategy.Search;
import org.chocosolver.solver.search.limits.FailCounter;
import org.chocosolver.solver.constraints.nary.automata.FA.FiniteAutomaton;
import org.chocosolver.solver.search.strategy.selectors.variables.ImpactBased;
import org.chocosolver.solver.Solution;

public class ProductionHoraire {
    public static final int N_PERIODE = 16;
    public static final int VALEUR_PERTE = 20;
    public static void main(String[] args) {

	String instancePath = args[0];
	
	// lecture des lignes du fichier d'instance
	String[] variablesInstance = null;
	String[] strLigne2 = null;
	String[] strLigne3 = null;    
	try {
	    List<String> lignesInstance = Files.readAllLines(Paths.get(instancePath));
	    variablesInstance = lignesInstance.get(0).split("\\s+");
	    strLigne2 = lignesInstance.get(1).split("\\s+");
	    strLigne3 = lignesInstance.get(2).split("\\s+");
	}
	catch (IOException e){
	    System.err.println("Caught IOException: " + e.getMessage());
	}
	catch (Exception e){
	    System.err.println("Caught Exception: " + e.getMessage());
	}
	
	// traitement de la 1re ligne
	Integer N = Integer.valueOf(variablesInstance[0]);
	Integer MIN_H = Integer.valueOf(variablesInstance[1]);
	Integer MAX_H = Integer.valueOf(variablesInstance[2]);
	Integer MIN_PERIODE = Integer.valueOf(variablesInstance[3]);
	
	// traitement de la 2e ligne
	List<Integer> nbEmployesRequis = new ArrayList<>();
	for (String s: strLigne2) {
	    Integer i = Integer.valueOf(s);
	    nbEmployesRequis.add(i);
	}
	// traitement de la 3e ligne
	List<Integer> nbEmployesSouhaite = new ArrayList<>();
	for (String s: strLigne3) {
	    Integer i = Integer.valueOf(s);
	    nbEmployesSouhaite.add(i);
	}
	
	// Vérification du fichier d'instance
	System.out.println("### Debut Validation Lecture Instance ###");
	System.out.println("N="+N.toString());
	System.out.println("MIN_H="+MIN_H.toString());
	System.out.println("MAX_H="+MAX_H.toString());
	System.out.println("MIN_PERIODE="+MIN_PERIODE.toString());
	System.out.println("nbEmployesRequis="+nbEmployesRequis.toString());
	System.out.println("nbEmployesSouhaite="+nbEmployesSouhaite.toString());
	System.out.println("### Fin Validation Lecture Instance ###");

	Model model = new Model("Production Horaire");

	// Création d'une matrice E de dimensions N x N_PERIODE de variables e(i,j) dont les domaines sont des variables binaires.
	// E(i,j)=0 signifie que l'employé i est au repos à la période j.
	// E(i,j)=1 signifie que l'employé i travaille à la période j.

	BoolVar[][] LignesE = model.boolVarMatrix("E",N,N_PERIODE);

	// Transposition de la matrice E
	BoolVar[][] ColonnesE = new BoolVar[N_PERIODE][N];
        for (Integer i = 0; i < N_PERIODE; i++) {
            for (Integer j = 0; j < N; j++) {
                ColonnesE[i][j] = LignesE[j][i];
            }
        }

	// Vecteur contenant toutes les variables de la matrice dans un seul vecteur
        BoolVar[] toutesLesVariables = new BoolVar[N * N_PERIODE];
        for (Integer i = 0; i < N * N_PERIODE; i++) {
            toutesLesVariables[i] = LignesE[i / N_PERIODE][i % N];
        }

	// Création de la contrainte du nombre requis d'employés

	System.out.println("### Création: contrainte du nombre requis d'employés ###");

	IntVar[] IVnbEmployesRequis  = new IntVar[N_PERIODE];
	for (Integer i=0; i < N_PERIODE; i++) {
	    IVnbEmployesRequis[i] = model.intVar(nbEmployesRequis.get(i));
	    model.sum(ColonnesE[i], ">=", IVnbEmployesRequis[i]).post();
	}

	// Création de l'automate fini pour la contrainte du motif d'horaire

	System.out.println("### Création: contrainte du motif d'horaire ###");

	Integer MAX_PERIODE = MAX_H-MIN_PERIODE-1;
	Integer MAX_PERIODE_REPOS = N_PERIODE-MIN_H-1;

	String strAutomaton = "0{0,"+
	    MAX_PERIODE_REPOS.toString()+
	    "}1{"+
	    MIN_PERIODE.toString()+
	    ","+
	    MAX_PERIODE.toString()+
	    "}01{"+
	    MIN_PERIODE.toString()+
	    ","+
	    MAX_PERIODE.toString()+
	    "}0{0,"+
	    MAX_PERIODE_REPOS.toString()+
	    "}";
	
	System.out.println("Expression régulière:"+strAutomaton);
	FiniteAutomaton FA = new FiniteAutomaton(strAutomaton);
	
	// Constantes pour la contrainte du nombre minimum et maximum d'heures

	IntVar MIN_H_TRAVAILLE  = model.intVar(MIN_H-1);
	IntVar MAX_H_TRAVAILLE  = model.intVar(MAX_H-1);
	
	// Contrainte du motif d'horaire et du nombre minimum et maximum d'heures

	for (Integer i=0; i<N; i++){
	    model.regular(LignesE[i],FA).post();
	    model.sum(LignesE[i], ">=", MIN_H_TRAVAILLE).post();
	    model.sum(LignesE[i], "<=", MAX_H_TRAVAILLE).post();
	}

	// Optimisation du nombre souhaité d'employés

	System.out.println("### Création: Optimisation ###");

	IntVar[] IVnbEmployesSouhaite  = new IntVar[N_PERIODE];
	IntVar[] IVnbEmployesEffectif  = new IntVar[N_PERIODE];
	IntVar[] IVnbEmployesDistance = new IntVar[N_PERIODE];
	
	for (Integer i=0; i < N_PERIODE; i++) {
	    IVnbEmployesEffectif[i] = model.intVar("nbEmployesEffectif",0,N);
	    IVnbEmployesSouhaite[i] = model.intVar("nbEmployesSouhaite"+i.toString(),
						   nbEmployesSouhaite.get(i));
	    IVnbEmployesDistance[i] = model.intVar("nbEmployesDistance",0,N);
	    model.sum(ColonnesE[i],"=",IVnbEmployesEffectif[i]).post();
	    model.distance(IVnbEmployesEffectif[i],
			   IVnbEmployesSouhaite[i],
			   "=",
			   IVnbEmployesDistance[i]).post();
	}
	
	IntVar PERTE = model.intVar("perte",0,N*N_PERIODE);
	model.sum(IVnbEmployesDistance,"=",PERTE).post();
	
	//model.setObjective(Model.MINIMIZE, PERTE);

	// Creation du solveur

	System.out.println("### Création: Solveur ###");
	
        Solver solver = model.getSolver();

	try {
	    solver.propagate();
	}
	catch (ContradictionException e) {
	    System.err.println("Caught ContradictionException: " + e.getMessage());
	    solver.getEngine().flush();
	}
	catch (Exception e){
	    System.err.println("Caught Exception: " + e.getMessage());
	}
	
	// Résolution du modèle

	System.out.println("### Résolution ###");

	Solution maSolution = solver.findOptimalSolution(PERTE,Model.MINIMIZE);

	// Affichage des résultats
	
	System.out.println("### Meilleure Solution ###");

	for (Integer i = 0; i < N; i++) {
	    System.out.print("Employé "+i+": ");
            for (Integer j = 0; j < N_PERIODE; j++) {
		System.out.print(maSolution.getIntVal(LignesE[i][j]));
		System.out.print("  ");
	    }
	    System.out.println("");
	}

	System.out.println("\nEmployés Totaux");
	System.out.print("           ");
	for (Integer j = 0; j < N_PERIODE; j++){
	    System.out.print(maSolution.getIntVal(IVnbEmployesEffectif[j]));
	    System.out.print("  ");
	}

	System.out.println("\nEmployés Souhaités");
	System.out.print("           ");
	for (Integer j = 0; j < N_PERIODE; j++){
	    System.out.print(maSolution.getIntVal(IVnbEmployesSouhaite[j]));
	    System.out.print("  ");
	}
	System.out.println("");

	Integer PERTE_OPT = solver.getBestSolutionValue().intValue()*VALEUR_PERTE;
	System.out.println("\nPerte optimale:"+PERTE_OPT.toString()+"$\n");

	System.out.println("### Statistiques ###");
	
        solver.printStatistics();
    }
}
