/*
 * 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 java.util.*;
import org.javatuples.Quartet;
import ScheduleUtil.*;
import static ScheduleUtil.EmployeeCostCalculator.*;

/**
 *
 * @author frabe
 */
public class RecoveredSchedulesArray extends SchedulesArray{
    
    AbsenceSchedulesArray absenceSchedulesArray;
    
    public RecoveredSchedulesArray(AbsenceSchedulesArray absenceSchedulesArray){
        super(absenceSchedulesArray);
        this.absenceSchedulesArray = absenceSchedulesArray;
        recoverAbsenceScheduleOptimally();
    }
    
    private void recoverAbsenceScheduleOptimally() {
        boolean[][] currentRecoveredScheduleOfPartTimeEmployees = this.getDeepCopyEmployeesSchedules(this.absenceSchedulesArray.partTimeSchedules);
        boolean[][] currentRecoveredScheduleOfFullTimeEmployees = this.getDeepCopyEmployeesSchedules(this.absenceSchedulesArray.fullTimeSchedules);      
       
        boolean[][] alreadyRecoveredWorkPeriodByPartTimeEmployees = new boolean[this.getNumberPartTimeEmployee()][myParametersInitialSchedules.getWorkPeriodsPerSchedule()];
        boolean[][] alreadyRecoveredWorkPeriodByFullTimeEmployees = new boolean[this.getNumberFullTimeEmployee()][myParametersInitialSchedules.getWorkPeriodsPerSchedule()];
        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);
        }
        
        int totalEmployeesCost = EmployeeCostCalculator.getFullScheduleCost(this.absenceSchedulesArray, myParametersInitialSchedules);
        int bestScheduleCost = Integer.MAX_VALUE;
        int remainingRecoveringAction = this.absenceSchedulesArray.numberOfRecoveringActionsToPerform;
        int currentOptimisticTotalEmployeeCost = totalEmployeesCost + remainingRecoveringAction * myParametersInitialSchedules.getMinimumWorkingPeriodCost();
        int optimisticTotalEmployeeCost = currentOptimisticTotalEmployeeCost;

        ScheduleStateComparator quartetComparator = new ScheduleStateComparator();
        RecoveringActionComparator recoveringActionCostComparator = new RecoveringActionComparator();
        PriorityQueue< Quartet<boolean[][], boolean[][], Integer, Integer> > scheduleStatesOrderedByOptimisticCost = new PriorityQueue<>(quartetComparator);
        RecoveringAction currentRecoveringAction = null;
        
        boolean isSolutionFound = true;
        // Fouille en profondeur avec calcul de cout optimiste base sur le taux horaire regulier des employes a temps plein.
        while ( bestScheduleCost > currentOptimisticTotalEmployeeCost ) {
            PriorityQueue<RecoveringAction> recoveringActionsOrderedByCost =
                    getPossibleRecoveringActions(currentRecoveredScheduleOfPartTimeEmployees, currentRecoveredScheduleOfFullTimeEmployees, 
                    alreadyRecoveredWorkPeriodByPartTimeEmployees, alreadyRecoveredWorkPeriodByFullTimeEmployees, recoveringActionCostComparator);
            if (recoveringActionsOrderedByCost.size() > 0){
                boolean[][] recoveredScheduleOfPartTimeEmployeesAfterAction = this.getDeepCopyEmployeesSchedules(currentRecoveredScheduleOfPartTimeEmployees);
                boolean[][] recoveredScheduleOfFullTimeEmployeesAfterAction = this.getDeepCopyEmployeesSchedules(currentRecoveredScheduleOfFullTimeEmployees);
                currentRecoveringAction = recoveringActionsOrderedByCost.poll();
                if (currentRecoveringAction.getClass().getName().equals("SchedulesRecovery.RecoveringActionPartTimeEmployee")) {
                    recoveredScheduleOfPartTimeEmployeesAfterAction[currentRecoveringAction.employee][currentRecoveringAction.workPeriod] = true;    
                    alreadyRecoveredWorkPeriodByPartTimeEmployees[currentRecoveringAction.employee][currentRecoveringAction.workPeriod] = true;
                } else if (currentRecoveringAction.getClass().getName().equals("SchedulesRecovery.RecoveringActionFullTimeEmployee")) {
                    recoveredScheduleOfFullTimeEmployeesAfterAction[currentRecoveringAction.employee][currentRecoveringAction.workPeriod] = true;   
                    alreadyRecoveredWorkPeriodByFullTimeEmployees[currentRecoveringAction.employee][currentRecoveringAction.workPeriod] = true;
                }

                optimisticTotalEmployeeCost = currentOptimisticTotalEmployeeCost - myParametersInitialSchedules.getMinimumWorkingPeriodCost() + currentRecoveringAction.recoveringActionCost;
                Quartet<boolean[][], boolean[][], Integer, Integer> scheduleState = Quartet.with
                    (recoveredScheduleOfPartTimeEmployeesAfterAction, recoveredScheduleOfFullTimeEmployeesAfterAction, optimisticTotalEmployeeCost, remainingRecoveringAction - 1);
                scheduleStatesOrderedByOptimisticCost.add(scheduleState);
            }
            if (remainingRecoveringAction - 1 == 0){
                bestScheduleCost = Math.min(bestScheduleCost, optimisticTotalEmployeeCost);
            }
            if (scheduleStatesOrderedByOptimisticCost.size() > 0) {
                Quartet<boolean[][], boolean[][], Integer, Integer> currentScheduleState =  scheduleStatesOrderedByOptimisticCost.poll();
                currentRecoveredScheduleOfPartTimeEmployees = currentScheduleState.getValue0();
                currentRecoveredScheduleOfFullTimeEmployees = currentScheduleState.getValue1();
                currentOptimisticTotalEmployeeCost = currentScheduleState.getValue2();
                remainingRecoveringAction = currentScheduleState.getValue3();
            } else {
                isSolutionFound = false;
                break;
            }
        }
        
        if (isSolutionFound) {
            this.fullTimeSchedules = currentRecoveredScheduleOfFullTimeEmployees;
            this.partTimeSchedules = currentRecoveredScheduleOfPartTimeEmployees;
            this.totalScheduleCost = EmployeeCostCalculator.getFullScheduleCost(this, myParametersInitialSchedules);
            assert(this.totalScheduleCost == bestScheduleCost);
        } else {
            this.totalScheduleCost = Integer.MAX_VALUE;
            System.out.println(" No Solution Found ");
        }
        
        
    } 
    
    private PriorityQueue<RecoveringAction> getPossibleRecoveringActions(boolean[][] currentPartTimeSchedule, boolean[][] currentFullTimeSchedule, boolean[][] alreadyRecoveredWorkPeriodByPartTimeEmployees, boolean[][] alreadyRecoveredWorkPeriodByFullTimeEmployees, Comparator recoveringActionCostComparator) {
        PriorityQueue<RecoveringAction> recoveringActionsOrderedByCost = new PriorityQueue<>(recoveringActionCostComparator);
        int workPeriod = findEarlierWorkPeriodToRecover(currentPartTimeSchedule, currentFullTimeSchedule);
        
        if (workPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule()) {
            for (int partTimeEmployee = 0 ; partTimeEmployee < this.getNumberPartTimeEmployee() ; partTimeEmployee++ ) {
                if ( isPartTimeEmployeeAvailableForAbsenceRecovering(currentPartTimeSchedule, partTimeEmployee, workPeriod) &&  !alreadyRecoveredWorkPeriodByPartTimeEmployees[partTimeEmployee][workPeriod]){
                    RecoveringActionPartTimeEmployee recoveringAction = 
                            new RecoveringActionPartTimeEmployee(partTimeEmployee, workPeriod);
                    recoveringAction.calculateRecoveringActionCost (currentPartTimeSchedule, myParametersInitialSchedules);
                    recoveringActionsOrderedByCost.add(recoveringAction);
                }
            }
            for (int fullTimeEmployee = 0 ; fullTimeEmployee < this.getNumberFullTimeEmployee() ; fullTimeEmployee++ ) {
                if ( isFullTimeEmployeeAvailableForAbsenceRecovering(currentFullTimeSchedule, fullTimeEmployee, workPeriod) &&  !alreadyRecoveredWorkPeriodByFullTimeEmployees[fullTimeEmployee][workPeriod] ){
                    RecoveringActionFullTimeEmployee recoveringAction = 
                            new RecoveringActionFullTimeEmployee(fullTimeEmployee, workPeriod);
                    recoveringAction.calculateRecoveringActionCost (currentFullTimeSchedule, myParametersInitialSchedules);
                    recoveringActionsOrderedByCost.add(recoveringAction);
                }
            }
        }
        return recoveringActionsOrderedByCost;
    }

    private int findEarlierWorkPeriodToRecover(boolean[][] currentPartTimeSchedule, boolean[][] currentFullTimeSchedule){
        int workingEmployees;
        for (int workPeriod = 0 ; workPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule() ; workPeriod++) {
            workingEmployees = 0;
            for (int partTimeEmployee = 0 ; partTimeEmployee < currentPartTimeSchedule.length ; partTimeEmployee++) {
                if ( currentPartTimeSchedule[partTimeEmployee][workPeriod] ) {
                    workingEmployees++;
                }
            }
            for (int fullTimeEmployee = 0 ; fullTimeEmployee < currentFullTimeSchedule.length ; fullTimeEmployee++) {
                if ( currentFullTimeSchedule[fullTimeEmployee][workPeriod] ) {
                    workingEmployees++;
                }
            }
            if (workingEmployees < this.myParametersInitialSchedules.getRequiredWorkForce(workPeriod) ) {
                return workPeriod;
            }
        }
        return myParametersInitialSchedules.getWorkPeriodsPerSchedule();
    }
    
    //Toutes les fonctions sont dedoublees.  On devrait faire du polymorphisme.
    private boolean isPartTimeEmployeeAvailableForAbsenceRecovering(boolean[][] currentPartTimeSchedule, int employee, int workPeriod){
        return !currentPartTimeSchedule[employee][workPeriod] && !absenceSchedulesArray.isPartTimeEmployeeAbsent(employee, workPeriod) && isValidPartTimeEmployeeWorkPeriod(currentPartTimeSchedule[employee], workPeriod);
    }
    
    private boolean isValidPartTimeEmployeeWorkPeriod(boolean[] currentPartTimeEmployeeSchedule, int workPeriod){
        return isValidWorkingPeriodsOfPartTimeEmployeePerSchedule(currentPartTimeEmployeeSchedule) &&
                isValidConsecutiveWorkingPeriodsOfPartTimeEmployee(currentPartTimeEmployeeSchedule, workPeriod) && 
                isValidConsecutiveNonWorkingPeriodsBetweenShiftWorksOfPartTimeEmployee(currentPartTimeEmployeeSchedule, workPeriod);
    }
    
    private boolean isValidWorkingPeriodsOfPartTimeEmployeePerSchedule(boolean[] currentPartTimeEmployeeSchedule){
        int employeeWorkingPeriods = 0;
        for ( int workPeriod = 0 ; workPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule() ; workPeriod++ ) {
            if (currentPartTimeEmployeeSchedule[workPeriod]){
                employeeWorkingPeriods +=1 ;
            }
        }
        return employeeWorkingPeriods < myParametersInitialSchedules.getMaxWorkingPeriodsOfPartTimeEmployeesPerSchedule();
    }

    private boolean isValidConsecutiveWorkingPeriodsOfPartTimeEmployee(boolean[] currentPartTimeEmployeeSchedule, int workPeriod){
        int consecutiveWorkingPeriods = 1;
        int compteurWorkPeriod = workPeriod - 1;
        while ( compteurWorkPeriod >= 0 && currentPartTimeEmployeeSchedule[compteurWorkPeriod] ){
            consecutiveWorkingPeriods += 1;
            compteurWorkPeriod--;
        }
        compteurWorkPeriod = workPeriod + 1;
        while ( compteurWorkPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule() && currentPartTimeEmployeeSchedule[compteurWorkPeriod] ){
            consecutiveWorkingPeriods += 1;
            compteurWorkPeriod++;
        }
        return consecutiveWorkingPeriods <= myParametersInitialSchedules.getMaxConsecutiveWorkingPeriodsOfPartTimeEmployeesPerShiftWork();
    }

    private boolean isValidConsecutiveNonWorkingPeriodsBetweenShiftWorksOfPartTimeEmployee(boolean[] currentPartTimeEmployeeSchedule, int workPeriod){
        int consecutivePreviousNonWorkingPeriods = 0;
        int compteurWorkPeriod = workPeriod - 1;
        while ( compteurWorkPeriod >= 0 && !currentPartTimeEmployeeSchedule[compteurWorkPeriod] ){
            consecutivePreviousNonWorkingPeriods += 1;
            compteurWorkPeriod--;
        }
        boolean validConsecutivePreviousNonWorkingPeriods = false;
        if (compteurWorkPeriod == -1) {
            validConsecutivePreviousNonWorkingPeriods = true;
        } else if (consecutivePreviousNonWorkingPeriods == 0) {
            validConsecutivePreviousNonWorkingPeriods = true;
        } else if (consecutivePreviousNonWorkingPeriods >= myParametersInitialSchedules.getMinConsecutiveNonWorkingPeriodsBetweenShiftWorksOfPartTimeEmployees()) {
            validConsecutivePreviousNonWorkingPeriods = true;
        }
        int consecutiveNextNonWorkingPeriods = 0;
        compteurWorkPeriod = workPeriod + 1;
        while ( compteurWorkPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule() && !currentPartTimeEmployeeSchedule[compteurWorkPeriod] ){
            consecutiveNextNonWorkingPeriods += 1;
            compteurWorkPeriod++;
        }
        boolean validConsecutiveNextNonWorkingPeriods = false;
        if (compteurWorkPeriod == myParametersInitialSchedules.getWorkPeriodsPerSchedule()) {
            validConsecutiveNextNonWorkingPeriods = true;
        } else if (consecutiveNextNonWorkingPeriods == 0) {
            validConsecutiveNextNonWorkingPeriods = true;
        } else if (consecutiveNextNonWorkingPeriods >= myParametersInitialSchedules.getMinConsecutiveNonWorkingPeriodsBetweenShiftWorksOfPartTimeEmployees()) {
            validConsecutiveNextNonWorkingPeriods = true;
        }
        return validConsecutivePreviousNonWorkingPeriods && validConsecutiveNextNonWorkingPeriods;
    }
    
    private boolean isFullTimeEmployeeAvailableForAbsenceRecovering(boolean[][] currentFullTimeSchedule, int employee, int workPeriod){
        return !currentFullTimeSchedule[employee][workPeriod] && !absenceSchedulesArray.isFullTimeEmployeeAbsent(employee, workPeriod) && isValidFullTimeEmployeeWorkPeriod(currentFullTimeSchedule[employee], workPeriod);
    }
    
    private boolean isValidFullTimeEmployeeWorkPeriod(boolean[] currentFullTimeEmployeeSchedule, int workPeriod){
        return isValidWorkingPeriodsOfFullTimeEmployeePerSchedule(currentFullTimeEmployeeSchedule) &&
                isValidConsecutiveWorkingPeriodsOfFullTimeEmployee(currentFullTimeEmployeeSchedule, workPeriod) && 
                isValidConsecutiveNonWorkingPeriodsBetweenShiftWorksOfFullTimeEmployee(currentFullTimeEmployeeSchedule, workPeriod);
    }
    
    private boolean isValidWorkingPeriodsOfFullTimeEmployeePerSchedule(boolean[] currentFullTimeEmployeeSchedule){
        int employeeWorkingPeriods = 0;
        for ( int workPeriod = 0 ; workPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule() ; workPeriod++ ) {
            if (currentFullTimeEmployeeSchedule[workPeriod]){
                employeeWorkingPeriods +=1 ;
            }
        }
        return employeeWorkingPeriods < myParametersInitialSchedules.getMaxWorkingPeriodsOfFullTimeEmployeesPerSchedule();
    }
    

    private boolean isValidConsecutiveWorkingPeriodsOfFullTimeEmployee(boolean[] currentFullTimeEmployeeSchedule, int workPeriod){
        int consecutiveWorkingPeriods = 1;
        int compteurWorkPeriod = workPeriod - 1;
        while ( compteurWorkPeriod >= 0 && currentFullTimeEmployeeSchedule[compteurWorkPeriod] ){
            consecutiveWorkingPeriods += 1;
            compteurWorkPeriod--;
        }
        compteurWorkPeriod = workPeriod + 1;
        while ( compteurWorkPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule() && currentFullTimeEmployeeSchedule[compteurWorkPeriod] ){
            consecutiveWorkingPeriods += 1;
            compteurWorkPeriod++;
        }
        return consecutiveWorkingPeriods <= myParametersInitialSchedules.getMaxConsecutiveWorkingPeriodsOfFullTimeEmployeesPerShiftWork();
    }

    private boolean isValidConsecutiveNonWorkingPeriodsBetweenShiftWorksOfFullTimeEmployee(boolean[] currentFullTimeEmployeeSchedule, int workPeriod){
        int consecutivePreviousNonWorkingPeriods = 0;
        int compteurWorkPeriod = workPeriod - 1;
        while ( compteurWorkPeriod >= 0 && !currentFullTimeEmployeeSchedule[compteurWorkPeriod] ){
            consecutivePreviousNonWorkingPeriods += 1;
            compteurWorkPeriod--;
        }
        boolean validConsecutivePreviousNonWorkingPeriods = false;
        if (compteurWorkPeriod == -1) {
            validConsecutivePreviousNonWorkingPeriods = true;
        } else if (consecutivePreviousNonWorkingPeriods == 0) {
            validConsecutivePreviousNonWorkingPeriods = true;
        } else if (consecutivePreviousNonWorkingPeriods >= myParametersInitialSchedules.getMinConsecutiveNonWorkingPeriodsBetweenShiftWorksOfFullTimeEmployees()) {
            validConsecutivePreviousNonWorkingPeriods = true;
        }
        int consecutiveNextNonWorkingPeriods = 0;
        compteurWorkPeriod = workPeriod + 1;
        while ( compteurWorkPeriod < myParametersInitialSchedules.getWorkPeriodsPerSchedule() && !currentFullTimeEmployeeSchedule[compteurWorkPeriod] ){
            consecutiveNextNonWorkingPeriods += 1;
            compteurWorkPeriod++;
        }
        boolean validConsecutiveNextNonWorkingPeriods = false;
        if (compteurWorkPeriod == myParametersInitialSchedules.getWorkPeriodsPerSchedule()) {
            validConsecutiveNextNonWorkingPeriods = true;
        } else if (consecutiveNextNonWorkingPeriods == 0) {
            validConsecutiveNextNonWorkingPeriods = true;
        } else if (consecutiveNextNonWorkingPeriods >= myParametersInitialSchedules.getMinConsecutiveNonWorkingPeriodsBetweenShiftWorksOfFullTimeEmployees()) {
            validConsecutiveNextNonWorkingPeriods = true;
        }
        return validConsecutivePreviousNonWorkingPeriods && validConsecutiveNextNonWorkingPeriods;
    }
}
