/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package SchedulesRecovery;

import AbsenceSchedules.AbsenceSchedulesArray;
import ScheduleUtil.EmployeeCostCalculator;
import ScheduleUtil.SchedulesArray;

import java.util.*;

/**
 *
 * @author frabe
 */
@SuppressWarnings("unchecked")
public class RecoveredSchedulesArray extends SchedulesArray{

    public final AbsenceSchedulesArray absenceSchedulesArray;
    
    public RecoveredSchedulesArray(AbsenceSchedulesArray absenceSchedulesArray){
        super(absenceSchedulesArray);
        this.absenceSchedulesArray = absenceSchedulesArray;
        recoverAbsenceScheduleOptimally();
    }
    
    private void recoverAbsenceScheduleOptimally() {
        boolean[][] initialRecoveredScheduleOfPartTimeEmployees = this.getDeepCopyEmployeesSchedules(this.absenceSchedulesArray.partTimeSchedules);
        boolean[][] initialRecoveredScheduleOfFullTimeEmployees = this.getDeepCopyEmployeesSchedules(this.absenceSchedulesArray.fullTimeSchedules);  
        boolean[][] currentRecoveredScheduleOfPartTimeEmployees = this.getDeepCopyEmployeesSchedules(this.absenceSchedulesArray.partTimeSchedules);
        boolean[][] currentRecoveredScheduleOfFullTimeEmployees = this.getDeepCopyEmployeesSchedules(this.absenceSchedulesArray.fullTimeSchedules);
       
        int initialRemainingRecoveringAction = this.absenceSchedulesArray.numberOfRecoveringActionsToPerform;
        int currentRemainingRecoveringAction = this.absenceSchedulesArray.numberOfRecoveringActionsToPerform;
       
        boolean[][] alreadyRecoveredWorkPeriodByPartTimeEmployees = new boolean[this.getNumberPartTimeEmployee()][initialRemainingRecoveringAction];
        boolean[][] alreadyRecoveredWorkPeriodByFullTimeEmployees = new boolean[this.getNumberFullTimeEmployee()][initialRemainingRecoveringAction];
        initializeRecoveredWorkPeriodMatrix (alreadyRecoveredWorkPeriodByPartTimeEmployees, alreadyRecoveredWorkPeriodByFullTimeEmployees);

        RecoveringActionComparator recoveringActionCostComparator = new RecoveringActionComparator();
        
        boolean isSolutionFound = false;
        boolean isAbsenceFound = initialRemainingRecoveringAction != 0;

        int numberBackTrack = 1;
        // Fouilles en profondeur en privilegiant les actions avec cout minimum. On marque les noeuds dejà visites pour avoir des fouilles differentes
        while ( !isSolutionFound && isAbsenceFound ) {
            PriorityQueue<RecoveringAction> recoveringActionsOrderedByCost =
                    getPossibleRecoveringActions(currentRecoveredScheduleOfPartTimeEmployees, currentRecoveredScheduleOfFullTimeEmployees, 
                    alreadyRecoveredWorkPeriodByPartTimeEmployees, alreadyRecoveredWorkPeriodByFullTimeEmployees, currentRemainingRecoveringAction, recoveringActionCostComparator);
            if (recoveringActionsOrderedByCost.size() > 0){
                RecoveringAction currentRecoveringAction;
                if (recoveringActionsOrderedByCost.size() == 1){
                    currentRecoveringAction = recoveringActionsOrderedByCost.poll();
                } else {
                    // On prend une des actions ayant un cout minimal.
                    currentRecoveringAction = chooseMinimalCostRecoveringActionRandomly(recoveringActionsOrderedByCost); 
                }
                
                if (currentRecoveringAction.getClass().getName().equals("SchedulesRecovery.RecoveringActionPartTimeEmployee")) {
                    currentRecoveredScheduleOfPartTimeEmployees[currentRecoveringAction.employee][currentRecoveringAction.workPeriod] = true;    
                    alreadyRecoveredWorkPeriodByPartTimeEmployees[currentRecoveringAction.employee][currentRemainingRecoveringAction-1] = true;
                } else if (currentRecoveringAction.getClass().getName().equals("SchedulesRecovery.RecoveringActionFullTimeEmployee")) {
                    currentRecoveredScheduleOfFullTimeEmployees[currentRecoveringAction.employee][currentRecoveringAction.workPeriod] = true;   
                    alreadyRecoveredWorkPeriodByPartTimeEmployees[currentRecoveringAction.employee][currentRemainingRecoveringAction-1] = true;
                }
                
                currentRemainingRecoveringAction--;

            } else if (numberBackTrack % (this.getNumberFullTimeEmployee() + this.getNumberPartTimeEmployee()) == 0) { 
                //Si le nombre de retour au sommet atteint le nombre total d'employes, on reinitialize les matrices de noeuds visites. 
                initializeRecoveredWorkPeriodMatrix (alreadyRecoveredWorkPeriodByPartTimeEmployees, alreadyRecoveredWorkPeriodByFullTimeEmployees);
            } else if (numberBackTrack > 10000) {
                isSolutionFound = false;
                System.out.println("No solution Found");
                break;
            } else{
                // Lorsqu'on est bloque dans l'arbre de recherche, on revient au sommet de l'arbre et on recommence une nouvelle fouille.
                currentRemainingRecoveringAction = initialRemainingRecoveringAction;
                currentRecoveredScheduleOfPartTimeEmployees = this.getDeepCopyEmployeesSchedules(initialRecoveredScheduleOfPartTimeEmployees);
                currentRecoveredScheduleOfFullTimeEmployees = this.getDeepCopyEmployeesSchedules(initialRecoveredScheduleOfFullTimeEmployees);
                numberBackTrack++;
            }
            
            if (currentRemainingRecoveringAction == 0){
                isSolutionFound = true;
            }

        }

        if (isSolutionFound || !isAbsenceFound) {
            this.fullTimeSchedules = currentRecoveredScheduleOfFullTimeEmployees;
            this.partTimeSchedules = currentRecoveredScheduleOfPartTimeEmployees;
            this.totalScheduleCost = EmployeeCostCalculator.getFullRecoveredScheduleCost(this, myParametersInitialSchedules);
        } else {
            this.totalScheduleCost = Integer.MAX_VALUE;
        }
        
    } 
    
    private void initializeRecoveredWorkPeriodMatrix (boolean[][] alreadyRecoveredWorkPeriodByPartTimeEmployees, boolean[][] alreadyRecoveredWorkPeriodByFullTimeEmployees){
        for (int i = 0 ; i < this.getNumberPartTimeEmployee() ; i++) {
            Arrays.fill(alreadyRecoveredWorkPeriodByPartTimeEmployees[i], Boolean.FALSE);
        }
        for (int i = 0 ; i < this.getNumberFullTimeEmployee() ; i++) {
            Arrays.fill(alreadyRecoveredWorkPeriodByFullTimeEmployees[i], Boolean.FALSE);
        }
    }
    
    private RecoveringAction chooseMinimalCostRecoveringActionRandomly(PriorityQueue<RecoveringAction> recoveringActionsOrderedByCost){
        
        Random rand = new Random();
        RecoveringAction minimalCurrentRecoveringAction = recoveringActionsOrderedByCost.peek();
        int minimalCost = minimalCurrentRecoveringAction.recoveringActionCost;
        List<RecoveringAction> minimalCostRecoveringActions = new ArrayList<>();
        RecoveringAction currentRecoveringAction = recoveringActionsOrderedByCost.poll();
        
        while (currentRecoveringAction.recoveringActionCost == minimalCost)
        {
            minimalCostRecoveringActions.add(currentRecoveringAction);
            if (recoveringActionsOrderedByCost.isEmpty()) {
                break;
            } 
            currentRecoveringAction = recoveringActionsOrderedByCost.poll();
        }
        
        int index = rand.nextInt(minimalCostRecoveringActions.size());
        return minimalCostRecoveringActions.get(index);
    }
    
    private PriorityQueue<RecoveringAction> getPossibleRecoveringActions(boolean[][] currentPartTimeSchedule, boolean[][] currentFullTimeSchedule, 
            boolean[][] alreadyRecoveredWorkPeriodByPartTimeEmployees, boolean[][] alreadyRecoveredWorkPeriodByFullTimeEmployees, 
            int remainingRecoveringAction, Comparator recoveringActionCostComparator) {
        PriorityQueue<RecoveringAction> recoveringActionsOrderedByCost = new PriorityQueue<>(recoveringActionCostComparator);
        int workPeriod = findWorkPeriodToRecover(currentPartTimeSchedule, currentFullTimeSchedule);
        
        if (workPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule()) {
            for (int partTimeEmployee = 0 ; partTimeEmployee < this.getNumberPartTimeEmployee() ; partTimeEmployee++ ) {
                if ( ValidationRecoveringAction.isPartTimeEmployeeAvailableForAbsenceRecovering(currentPartTimeSchedule, myParametersInitialSchedules, 
                            absenceSchedulesArray, partTimeEmployee, workPeriod) 
                        && !alreadyRecoveredWorkPeriodByPartTimeEmployees[partTimeEmployee][remainingRecoveringAction-1]){
                    RecoveringActionPartTimeEmployee recoveringAction = 
                            new RecoveringActionPartTimeEmployee(partTimeEmployee, workPeriod);
                    recoveringAction.calculateRecoveringActionCost(currentPartTimeSchedule, myParametersInitialSchedules);
                    recoveringActionsOrderedByCost.add(recoveringAction);
                }
            }
            for (int fullTimeEmployee = 0 ; fullTimeEmployee < this.getNumberFullTimeEmployee() ; fullTimeEmployee++ ) {
                if ( ValidationRecoveringAction.isFullTimeEmployeeAvailableForAbsenceRecovering(currentFullTimeSchedule, myParametersInitialSchedules, 
                            absenceSchedulesArray, fullTimeEmployee, workPeriod) 
                        &&  !alreadyRecoveredWorkPeriodByFullTimeEmployees[fullTimeEmployee][remainingRecoveringAction-1] ){
                    RecoveringActionFullTimeEmployee recoveringAction = 
                            new RecoveringActionFullTimeEmployee(fullTimeEmployee, workPeriod);
                    recoveringAction.calculateRecoveringActionCost (currentFullTimeSchedule, absenceSchedulesArray, myParametersInitialSchedules);
                    recoveringActionsOrderedByCost.add(recoveringAction);
                }
            }
        }
        return recoveringActionsOrderedByCost;
    }

    private int findWorkPeriodToRecover(boolean[][] currentPartTimeSchedule, boolean[][] currentFullTimeSchedule){
        int workingEmployees;
        for (int workPeriod = 0 ; workPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule() ; workPeriod++) {
            workingEmployees = 0;
            for (boolean[] aCurrentPartTimeSchedule : currentPartTimeSchedule) {
                if (aCurrentPartTimeSchedule[workPeriod]) {
                    workingEmployees++;
                }
            }
            for (boolean[] aCurrentFullTimeSchedule : currentFullTimeSchedule) {
                if (aCurrentFullTimeSchedule[workPeriod]) {
                    workingEmployees++;
                }
            }
            if (workingEmployees < this.myParametersInitialSchedules.getRequiredWorkForce(workPeriod) ) {
                return workPeriod;
            }
        }
        return myParametersInitialSchedules.getWorkPeriodsPerSchedule();
    }
}
