/*
 * Copyright (c) 2011, Patrik Dufresne. All rights reserved. Patrik Dufresne
 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.planimod.core;

import java.util.AbstractCollection;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.iterators.FilterIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.patrikdufresne.ilp.Constraint;
import com.patrikdufresne.ilp.IBranchingTechniqueLastAlwaysDown;
import com.patrikdufresne.ilp.IFeasibilityPumpHeuristic;
import com.patrikdufresne.ilp.ILPLogger;
import com.patrikdufresne.ilp.ILPPolicy;
import com.patrikdufresne.ilp.Linear;
import com.patrikdufresne.ilp.LinearProblem;
import com.patrikdufresne.ilp.Solver;
import com.patrikdufresne.ilp.SolverFactory;
import com.patrikdufresne.ilp.SolverOption;
import com.patrikdufresne.ilp.Variable;
import com.patrikdufresne.ilp.impl.GLPKSolverFactory;
import com.patrikdufresne.ilp.util.Bound;
import com.patrikdufresne.ilp.util.BoundSnapshot;
import com.patrikdufresne.ilp.util.ConstraintSnapshot;
import com.patrikdufresne.ilp.util.Constraints;
import com.patrikdufresne.ilp.util.Linears;
import com.patrikdufresne.ilp.util.ValueSnapshot;
import com.patrikdufresne.ilp.util.Variables;
import com.patrikdufresne.util.BidiMultiMap;
import com.planimod.core.comparators.EmployeeComparators;

/**
 * This class is used to create the problem structure and then solve it using a
 * Integer Linear Problem solver, like GLPK. To encapsulate each step of the
 * solving process, an abstract class {@link SolveRunnable} is used. Each
 * subclasses is then specialized to optimize one part of the problem and
 * maintain constrains when required.
 * 
 * @see SolveRunnable
 * @see LinearProblem
 * @author Patrik Dufresne
 */
@SuppressWarnings("unqualified-field-access")
class ProblemBuilder {

    /**
     * Abstract implementation to support a single constraint.
     */
    abstract class AbstractConstraintRunnable extends SolveRunnable {

        protected Constraint constraint;

        protected ConstraintSnapshot constraintSnapshot;

        /**
         * This implementation restore the constraint snapshot.
         */
        @Override
        public void engage() {
            if (this.constraintSnapshot == null) {
                throw new RuntimeException("snaptshot not available."); //$NON-NLS-1$
            }
            this.constraint = this.constraintSnapshot.restore(lp);
            this.constraintSnapshot = null;
        }

        /**
         * This implementation release the constraints and keep a reference of
         * it as a snapshot.
         */
        @Override
        public void release() {
            if (this.constraint == null) {
                throw new RuntimeException("constraint not available.");//$NON-NLS-1$
            }
            this.constraintSnapshot = Constraints.release(this.constraint);
            this.constraint = null;
        }

        /**
         * This implementation check if the constraint already exists.
         */
        @Override
        public void run() throws GeneratePlanifException {
            // Check if the constraint already exists.
            if (this.constraint != null) {
                throw new RuntimeException("constraint already exists"); //$NON-NLS-1$
            }
            super.run();
        }

        /**
         * This function release and dispose the constraint.
         */
        @Override
        protected void unfeasible() throws GeneratePlanifException {
            // Dispose the constraint
            if (this.constraint != null) {
                this.constraint.dispose();
                this.constraint = null;
            }
        }
    }

    /**
     * This runnable is used to check if their is a feasible solution with this
     * employee not working.
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class CheckEmployeeNotWorking extends SolveRunnable {

        /**
         * The employee index to avoid assignment.
         */
        private int i;

        /**
         * Bounds of the xi variable before running this class.
         */
        private Bound originalBound;

        /**
         * Bounds of the xi variable when release was called.
         */
        private Bound snapshotBound;

        /**
         * Create a new runnable
         * 
         * @param i the employee index to avoid making work.
         */
        public CheckEmployeeNotWorking(int i) {
            this.i = i;
            setFeasibilityPumpHeuristic(true);
        }

        /**
         * This implementation restore the snapshot bound.
         */
        @Override
        public void engage() {
            if (this.snapshotBound == null) {
                throw new RuntimeException("snapshot is not available."); //$NON-NLS-1$
            }
            // Restore the bounds. Probably [0..0] or [1..1]
            this.snapshotBound.restore(xi[i]);
            this.snapshotBound = null;
        }

        /**
         * This implementation does nothing.
         */
        @Override
        protected void feasible() {
            // Feasible solution with the employee not working.
            LOGGER.info("Employee " + employeeToString(i) + " is NOT required."); //$NON-NLS-1$ //$NON-NLS-2$
        }

        /**
         * Used to restore the original variable bounds.
         * 
         * @throws GeneratePlanifException
         */
        @Override
        public void release() {
            // Restore the variable bounds.
            if (this.originalBound == null) {
                throw new RuntimeException("original bound not available."); //$NON-NLS-1$
            }
            // Capture the bound
            this.snapshotBound = Bound.create(xi[i]);
            // Restore the original bound (should be [0..1]
            this.originalBound.restore(xi[i]);
        }

        @Override
        protected boolean setUp() {
            // Capture the original bounds
            if (this.originalBound == null) {
                this.originalBound =
                        new Bound(xi[i].getLowerBound(), xi[i].getUpperBound());
            }
            // Skip this runnable if the variable is not set
            if (xi[i] == null) {
                feasible = true;
                return false;
            }
            // Fix the variable to ZERO.
            xi[i].setLowerBound(ZERO);
            xi[i].setUpperBound(ZERO);
            // Check if the previous snapshot is not assigning the employee.
            if (snapshot != null && ZERO.equals(snapshot.get(xi[i]))) {
                LOGGER.debug("Employee {} " //$NON-NLS-1$
                        + " is already not working according to snapshot.", //$NON-NLS-1$
                        employeeToString(i));
                feasible = true;
                return false;
            }
            // Reset the objective function
            lp.setObjectiveLinear(null);
            return true;
        }

        /**
         * If it's impossible to assign all tasks, release the constraint.
         */
        @Override
        protected void unfeasible() {
            if (this.originalBound == null) {
                throw new RuntimeException("original bound not available."); //$NON-NLS-1$
            }
            // Restore the variable bounds
            this.originalBound.restore(xi[i]);
            LOGGER.info("Employee {} is required.", employeeToString(i)); //$NON-NLS-1$ 
        }
    }

    /**
     * This runnable is used to check feasibility of assigning employee to
     * preferred team.
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class CheckPreferredTeam extends SolveRunnable {

        /**
         * Bound snapshot used to restore the bound is the problem is
         * unfeasible.
         */
        private BoundSnapshot boundSnapshot;

        /**
         * The employee.
         */
        private int i;

        /**
         * The team.
         */
        private int j;

        /**
         * Create a new runnable to assign employee <code>i</code> to preferred
         * team <code>j</code>
         * 
         * @param i the employee
         * @param j the team
         * @param rtj True to assign the employee to tasks really in team.
         */
        public CheckPreferredTeam(int i, int j) {
            this.i = i;
            this.j = j;
            setFeasibilityPumpHeuristic(true);
        }

        @Override
        protected void feasible() {
            LOGGER.info("Employee {} assign to team {}.", //$NON-NLS-1$
                    employeeToString(i), teamToString(j));
        }

        /**
         * This impleentation is using the snapshot to determine if the employee
         * is laready assigned to the team. If so, it also check if the employee
         * if assign only to unswappable task.
         * <p>
         * If the employee is assign to swappable task, this runnable will try
         * to improve the current solution.
         */
        @Override
        protected boolean setUp() {
            // Check if employee can be assigned to team j
            if (xij[i] == null || xij[i][j] == null) {
                this.feasible = false;
                return false;
            }
            // Take bounds snapshot before changing them
            this.boundSnapshot = BoundSnapshot.create(variablesForEmployee(i));
            // Fix to zero (0) xik, xij and xi variables not corresponding to
            // the team for employee i. Be careful not to change fixed
            // variables.
            for (int j2 = 0; j2 < xij[this.i].length; j2++) {
                if (j2 != this.j) {
                    for (int k = 0; k < xijk[this.i][j2].length; k++) {
                        if (xijk[this.i][j2][k] != null
                                && !Variables.isFixed(xijk[this.i][j2][k])) {
                            xijk[this.i][j2][k].setLowerBound(ZERO);
                            xijk[this.i][j2][k].setUpperBound(ZERO);
                        }
                    }
                }
                if (xij[this.i][j2] != null && !Variables.isFixed(xij[i][j2])) {
                    xij[this.i][j2].setLowerBound(this.j == j2
                            ? ONE
                            : ZERO);
                    xij[this.i][j2].setUpperBound(this.j == j2
                            ? ONE
                            : ZERO);
                }
            }
            // Don't create any constraint to force the employee to work a
            // minimum number of time on the team. OItherwise it will break the
            // non-availability handler. See ticket #191.
            // Sets the objective
            lp.setObjectiveLinear(null);
            LOGGER.debug("Check feasiblility with employee {} " //$NON-NLS-1$
                    + "working on team {}.", employeeToString(i), //$NON-NLS-1$
                    teamToString(j));
            return true;
        }

        @Override
        protected void tearDown() throws GeneratePlanifException {
            // Nothing to do.
        }

        @Override
        protected void unfeasible() {
            if (this.boundSnapshot != null) {
                // Restore the bound snapshot.
                this.boundSnapshot.restore();
                this.boundSnapshot = null;
                LOGGER.info("Can't assign " //$NON-NLS-1$
                        + "employee {} to team {}", employeeToString(i), //$NON-NLS-1$
                        teamToString(j));
            }
        }
    }

    /**
     * This runnable check if the problem is feasible without any swappable
     * tasks.
     * 
     * <pre>
     * SUM from{i=0} to{n} SUM from{j=0} to{m} SUM from{k notin RTj} xijk ==0
     * </pre>
     * 
     * @author Patrik Dufresne
     */
    class CheckSwappableTask extends AbstractConstraintRunnable {

        /**
         * The constraint's name
         */
        private static final String CONSTRAINT_NAME =
                "SUM from{i=0} to{n} SUM from{j=0} to{m} SUM from{k notin RTj} xijk <=0"; //$NON-NLS-1$

        public CheckSwappableTask() {
            setFeasibilityPumpHeuristic(true);
        }

        @Override
        protected boolean setUp() {
            // Create a linear function to compute a value using the snapshot.
            Linear linear = lp.createLinear();
            int m = table.tj().size();
            int p = tasks.size();
            for (int k = 0; k < p; k++) {
                for (int j = 0; j < m; j++) {
                    if (!table.kInRTj(k, j)) {
                        for (int i = 0; i < xijk.length; i++) {
                            if (xijk[i][j][k] != null) {
                                linear.add(lp.createTerm(ONE, xijk[i][j][k]));
                            }
                        }
                    }
                }
            }
            // Create the constraint
            this.constraint = lp.addConstraint(CONSTRAINT_NAME, linear, null, ZERO);
            // If the linear function is empty, their is not need to optimize
            // the problem.
            // If the last snapshot was feasible without swappable tasks, we
            // don't need to optimize either.
            if (snapshot != null && Constraints.isSatisfied(this.constraint, snapshot)) {
                this.feasible = true;
                return false;
            }
            // Fix all swappable tasks variableto zero
            lp.setObjectiveLinear(null);
            // Check feasibility without swappable tasks
            LOGGER.info("Check feasibility without swappable tasks."); //$NON-NLS-1$
            return true;
        }
    }

    /**
     * This runnable is used to check if every tasks can be assigned to an
     * employee.
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class CheckTaskAssigned extends AbstractConstraintRunnable {

        /**
         * The constraint's name.
         */
        private static final String CONSTRAINT_NAME =
                "CheckTaskAssigned: SUM from{i=0} to{n} SUM from{k=0} to{p} xijk >= %d"; //$NON-NLS-1$

        /**
         * Default constructor.
         */
        public CheckTaskAssigned() {
            setFeasibilityPumpHeuristic(true);
        }

        /**
         * This implementation does nothing.
         */
        @Override
        protected void feasible() {
            // Every task may be assigned. There is no need to remove the
            // constraint.
            LOGGER.info("All task can be assigned."); //$NON-NLS-1$
        }

        @Override
        protected boolean setUp() {
            // Create a linear for the sum of every tasks
            Linear linear = lp.createLinear();
            for (int i = 0; i < xijk.length; i++) {
                for (int j = 0; j < xijk[i].length; j++) {
                    for (int k = 0; k < xijk[i][j].length; k++) {
                        if (xijk[i][j][k] != null) {
                            linear.add(lp.createTerm(ONE, xijk[i][j][k]));
                        }
                    }
                }
            }
            if (linear.size() == 0) {
                feasible = true;
                return false;
            }
            // Create the following constraint
            // SUM xijk >= nbTask
            Number number = Integer.valueOf(tasks.size());
            String name = String.format(CONSTRAINT_NAME, number);
            this.constraint = lp.addConstraint(name, linear, number, null);
            // Reset the objective function
            lp.setObjectiveLinear(null);
            LOGGER.debug("Check if every task can be assigned."); //$NON-NLS-1$
            return true;
        }

        /**
         * If it's impossible to assign all tasks, release the constraint.
         * 
         * @throws GeneratePlanifException
         */
        @Override
        protected void unfeasible() throws GeneratePlanifException {
            super.unfeasible();
            LOGGER.info("Can't assign all task."); //$NON-NLS-1$
        }
    }

    /**
     * Unmodifiable list converting boolean array into Integer list.
     * 
     * @author Patrik Dufresne
     */
    static class CoefficientList extends AbstractList<Integer> {

        private boolean reverse;

        private boolean[] table;

        public CoefficientList(boolean[] table) {
            this(table, false);
        }

        public CoefficientList(boolean[] table, boolean reverse) {
            this.table = table;
            this.reverse = reverse;
        }

        @Override
        public Integer get(int index) {
            if (this.reverse) {
                return this.table[index]
                        ? ZERO
                        : ONE;
            }
            return this.table[index]
                    ? ONE
                    : ZERO;
        }

        @Override
        public int size() {
            return this.table.length;
        }
    }

    /**
     * This runnable is used to find the maximum number of task that may be
     * assigned.
     * <p>
     * This implementation optimize the total number of tasks being assigned
     * whatever the task is. This solver is always feasible.
     * 
     * <pre>
     * max(SUM from{i=0} to {n} SUM from{j=0} to {m} SUM from{k=0} to {p} xijk)
     * </pre>
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class MaxAllTaskAssigned extends AbstractConstraintRunnable {

        /**
         * The constraint's name.
         */
        private static final String CONSTRAINT_NAME =
                "MaxAllTaskAssigned: SUM from{i=0} to {n} SUM from{j=0} to {m} SUM from{k=0} to {p} xijk >=%d"; //$NON-NLS-1$

        /**
         * The objective's name
         */
        private static final String OBJECTIVE_NAME =
                "max(SUM from{i=0} to {n} SUM from{j=0} to {m} SUM from{k=0} to {p} xijk)"; //$NON-NLS-1$

        /**
         * The linear being maximized.
         */
        private Linear linear;

        /**
         * Public constructor.
         */
        public MaxAllTaskAssigned() {
            setFeasibilityPumpHeuristic(true);
        }

        /**
         * Create a constraint to maintain the number of assignment.
         */
        @Override
        protected void feasible() {
            // Get objective value
            Number value = Integer.valueOf(lp.getObjectiveValue().intValue());
            LOGGER.info("Maximum number of task assigned: {}", value); //$NON-NLS-1$
            // Sets the number of tasks assigned as a constraint.
            String name = String.format(CONSTRAINT_NAME, value);
            this.constraint = lp.addConstraint(name, this.linear, value, null);
        }

        /**
         * This implementation create the linear to be maximized.
         */
        @Override
        protected boolean setUp() {
            LOGGER.debug("Maximize number of task assigned."); //$NON-NLS-1$
            // Create a linear
            this.linear = lp.createLinear();
            for (int i = 0; i < xijk.length; i++) {
                for (int j = 0; j < xijk[i].length; j++) {
                    for (int k = 0; k < xijk[i][j].length; k++) {
                        if (xijk[i][j][k] != null) {
                            this.linear.add(lp.createTerm(1, xijk[i][j][k]));
                        }
                    }
                }
            }
            // Set the linear as an objective
            lp.setObjectiveName(OBJECTIVE_NAME);
            lp.setObjectiveLinear(this.linear);
            lp.setObjectiveDirection(LinearProblem.MAXIMIZE);
            return true;
        }

        /**
         * Reset the objective function.
         */
        @Override
        protected void tearDown() {
            // Remove objective
            lp.setObjectiveName(null);
            lp.setObjectiveLinear(null);
        }

        /**
         * Should never be called.
         */
        @Override
        protected void unfeasible() throws GeneratePlanifException {
            // It's a maximization problem, it' must be feasible.
            throwProblemUnFeasible();
        }
    }

    /**
     * This runnable is used to assign the employee according to it's
     * preferences.
     * 
     * @author Patrik Dufresne
     */
    class MaxEmployeePreferences extends SolveRunnable {

        /**
         * This function is called to apply the non-availabilities for the given
         * employee.
         * 
         * @throws GeneratePlanifException
         */
        private void handleNonAvailabilities(int i) throws GeneratePlanifException {
            // Check if the employee is available
            Employee employee = employees.get(i);
            boolean notAvailable = false;
            for (int j = 0; j < xijk[i].length; j++) {
                for (int k = 0; k < xijk[i][j].length; k++) {
                    if (xijk[i][j][k] != null && !isAvailable(employee, tasks.get(k))) {
                        // Need to sets the variables to zero.
                        xijk[i][j][k].setLowerBound(ZERO);
                        xijk[i][j][k].setUpperBound(ZERO);
                        notAvailable = true;
                    }
                }
            }
            if (!notAvailable) {
                return;
            }
            // Clear the employee from current to less senior. We do not need to
            // recompute the current employee.
            maxSeniorEmployee.release(i + 1);
            minSwappable.release();
            minEmployee.release();
            maxTaskAssigned.release();
            maxTaskAssigned.run();
            minEmployee.run();
            minSwappable.run();
            maxSeniorEmployee.run(i + 1);
        }

        @Override
        protected boolean runSolver() throws GeneratePlanifException {
            // Loop on employee - the list is sorted by seniority
            for (int i = 0; i < xi.length; i++) {
                // Update monitor
                worked(UNIT);
                // Maximize the preferred position
                MaxPreferredPosition solver = new MaxPreferredPosition(i);
                solver.run();
                if (solver.isFeasible()) {
                    // Apply the non-availability.
                    handleNonAvailabilities(i);
                    // A solution as been found with the employee working on
                    // it's preferred position. So there is not need to optimize
                    // the preferred team.
                    continue;
                }
                // Maximize the preferred team
                MaxPreferredTeams maxPreferredTeam = new MaxPreferredTeams(i);
                maxPreferredTeam.run();
                // Apply the non-availability.
                handleNonAvailabilities(i);
            }
            // Loop on employee
            for (int i = 0; i < xi.length; i++) {
                worked(UNIT);
                // Maximize the employee preferred section.
                MaxPreferredSection maxPreferredSection = new MaxPreferredSection(i);
                maxPreferredSection.run();
            }
            return true;
        }

        @Override
        protected boolean setUp() throws GeneratePlanifException {
            LOGGER.debug("Maximize employee preferences."); //$NON-NLS-1$
            return true;
        }
    }

    /**
     * This runnable is used to maximize the assignment of important tasks when
     * all tasks can't be assigned.
     * 
     * <pre>
     * max(SUM from{i=0} to {n} SUM from{j=0} to {m} SUM from{k in ??} xijk)
     * </pre>
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class MaxImportantTaskAssigned extends AbstractConstraintRunnable {

        /**
         * The constraint's name.
         */
        private static final String CONSTRAINT_NAME =
                "MaxImportantTaskAssigned: SUM from{i=0} to {n} SUM from{j=0} to {m} SUM from{k in ??} xijk >=%d"; //$NON-NLS-1$

        /**
         * The objective's name
         */
        private static final String OBJECTIVE_NAME =
                "max(SUM from{i=0} to {n} SUM from{j=0} to {m} SUM from{k in ??} xijk)"; //$NON-NLS-1$

        /**
         * The linear being maximized.
         */
        private Linear linear;

        /**
         * Public constructor.
         */
        public MaxImportantTaskAssigned() {
            setFeasibilityPumpHeuristic(true);
        }

        /**
         * Create a constraint to maintain the number of assignment.
         */
        @Override
        protected void feasible() {
            // Get objective value
            Number value = Integer.valueOf(lp.getObjectiveValue().intValue());
            LOGGER.info("Maximum number of important task assigned: {}", value); //$NON-NLS-1$
            // Sets the number of tasks assigned as a constraint.
            String name = String.format(CONSTRAINT_NAME, value);
            this.constraint = lp.addConstraint(name, this.linear, value, null);
        }

        /**
         * This implementation create the linear to be maximized.
         */
        @Override
        protected boolean setUp() {
            LOGGER.debug("Maximize number of important task assigned."); //$NON-NLS-1$
            // Create a linear with important tasks
            this.linear = lp.createLinear();
            for (int i = 0; i < xijk.length; i++) {
                for (int j = 0; j < xijk[i].length; j++) {
                    for (int k = 0; k < xijk[i][j].length; k++) {
                        if (xijk[i][j][k] != null
                                && tasks.get(k).getPosition().getClassified()) {
                            this.linear.add(lp.createTerm(1, xijk[i][j][k]));
                        }
                    }
                }
            }
            // Set the linear as an objective
            lp.setObjectiveName(OBJECTIVE_NAME);
            lp.setObjectiveLinear(this.linear);
            lp.setObjectiveDirection(LinearProblem.MAXIMIZE);
            return true;
        }

        /**
         * Reset the objective function.
         */
        @Override
        protected void tearDown() {
            // Remove objective
            lp.setObjectiveName(null);
            lp.setObjectiveLinear(null);
        }

        /**
         * Should never be called.
         */
        @Override
        protected void unfeasible() throws GeneratePlanifException {
            // It's a maximization problem, it' must be feasible.
            throwProblemUnFeasible();
        }
    }

    /**
     * This runnable is used to assigned employee to it's preferred position.
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class MaxPreferredPosition extends SolveRunnable {

        /** The constraints name. */
        private static final String CONSTRAINT_NAME =
                "MaxPreferredPosition: SUM from{k in PPi} xik] >= %d, for i=%d"; //$NON-NLS-1$

        /** Constraint to check feasibility */
        private Constraint constraint;

        /** The employee index */
        private int i;

        /** Employee preferred position team */
        private int j;

        /** Employee preferred position */
        private Position pos;

        private boolean released;

        /**
         * When this value is set, we have determine a lower bound using the
         * snapshot value. In case we can't improve this value, the problem will
         * be unfeasible and as such, we must sets this value to the constraint.
         */
        private Number snapshotLowerBound;

        /**
         * Create a new runnable to maximize employee <code>i</code> preferred
         * position.
         * 
         * @param i the employee
         * @param loosly True to assign the employee to the preferred position
         * event if it's brake the max-senior-employee rule.
         */
        public MaxPreferredPosition(int i) {
            this.i = i;
            setFeasibilityPumpHeuristic(true);
        }

        private void assignEmployeeToPreferredPosition()
                throws GeneratePlanifException {
            // For each k, fix the xijk variables to ZERO or ONE if the employee
            // i is assigned to his preferred position.
            for (int k = 0; k < xijk[i][j].length; k++) {
                if (xijk[i][j][k] != null && table.kInRTj(k, j)
                        && ONE.equals(snapshot.get(xijk[i][j][k]))
                        && pos.equals(tasks.get(k).getPosition())) {
                    // The employee i is assigned to preferred position k, fix
                    // the variables xijk, for each i, and each j.
                    for (int j2 = 0; j2 < xijk[i].length; j2++) {
                        if (xijk[i][j2][k] != null) {
                            xijk[i][j2][k].setLowerBound(ONE);
                            // i == i2
                            // && j == j2 ? ONE : ZERO);
                            xijk[i][j2][k].setUpperBound(ONE);
                            // i == i2
                            // && j == j2 ? ONE : ZERO);
                        }
                    }
                }
            }
            // Fix to zero (0) xijk and xij variables not corresponding to
            // the team j for employee i. Be careful not to change fixed
            // variables.
            for (int j2 = 0; j2 < xij[this.i].length; j2++) {
                if (j2 != this.j) {
                    for (int k = 0; k < xijk[this.i][j2].length; k++) {
                        if (xijk[this.i][j2][k] != null
                                && !Variables.isFixed(xijk[this.i][j2][k])) {
                            xijk[this.i][j2][k].setLowerBound(ZERO);
                            xijk[this.i][j2][k].setUpperBound(ZERO);
                        }
                    }
                }
                if (xij[this.i][j2] != null && !Variables.isFixed(xij[i][j2])) {
                    xij[this.i][j2].setLowerBound(this.j == j2
                            ? ONE
                            : ZERO);
                    xij[this.i][j2].setUpperBound(this.j == j2
                            ? ONE
                            : ZERO);
                }
            }
            LOGGER.info("Employee {} assigned to preferred position.", //$NON-NLS-1$
                    employeeToString(i));
            // Override the feasible variable.
            this.feasible = true;
        }

        @Override
        protected void feasible() throws GeneratePlanifException {
            assignEmployeeToPreferredPosition();
        }

        @Override
        protected boolean runSolver() throws GeneratePlanifException {
            // The first time, we run the solver without releasing the
            // max-senior-employee rule
            boolean feasible = super.runSolver();
            // Check the results.
            if (feasible || snapshotLowerBound != null
                    && snapshotLowerBound.intValue() > 0) {
                return true;
            }
            // Release the max-senior-employee rule
            this.released = true;
            maxSeniorEmployee.release(i + 1);
            // Try to assign the employee to his preferred position again.
            return super.runSolver();
        }

        @Override
        protected boolean setUp() {
            // Check if the employee has a preferred position
            EmployeePreference pref = preferences.get(i);
            Team team;
            if (pref == null || (this.pos = pref.getPreferredPosition()) == null
                    || (team = pref.getPreferredPositionTeam()) == null) {
                this.feasible = false;
                return false;
            }
            this.j = table.indexOf(team);
            if (j < 0) {
                this.feasible = false;
                return false;
            }
            // TODO To accelerate this, we should sets the xijk not matching the
            // j to ZERO like we do for Preferred Team.
            // Build the linear expression to be maximize.
            Linear linear = lp.createLinear();
            for (int k = 0; k < tasks.size(); k++) {
                Task task = tasks.get(k);
                if (xijk[i][j][k] != null && this.pos.equals(task.getPosition())
                        && table.kInRTj(k, j) && !isFixedToZero(xijk[i][j][k])) {
                    linear.add(lp.createTerm(1, xijk[i][j][k]));
                }
            }
            if (linear.size() == 0) {
                // Noting to maximize, skip the solver
                this.feasible = false;
                return false;
            }
            String name;
            // Sets the linear as an objective
            name =
                    String.format(
                            "max[SUM from{k in PPi} xik], for i=%d", Integer.valueOf(i)); //$NON-NLS-1$
            lp.setObjectiveName(name);
            lp.setObjectiveLinear(linear);
            lp.setObjectiveDirection(LinearProblem.MAXIMIZE);
            // Also create a constraint to force optimization of the best known
            // solution. (Used to improve performance).
            int lowerBound = Math.max(ZERO.intValue(), (snapshot != null
                    ? (snapshotLowerBound = Linears.compute(linear, snapshot))
                    : ZERO).intValue());
            name =
                    String.format(CONSTRAINT_NAME, Integer.valueOf(lowerBound + 1),
                            Integer.valueOf(i));
            constraint =
                    lp.addConstraint(name, linear, Integer.valueOf(lowerBound + 1),
                            null);
            LOGGER.debug("Maximize employee {} preferred position. Optimize: {}", //$NON-NLS-1$
                    employeeToString(i), lowerBound);
            return true;
        }

        @Override
        protected void tearDown() throws GeneratePlanifException {
            // Release the constraint used to force optimization of the problem.
            if (this.constraint != null) {
                this.constraint.dispose();
                this.constraint = null;
            }
            this.snapshotLowerBound = null;
            // Remove objective
            lp.setObjectiveName(null);
            lp.setObjectiveLinear(null);
            if (this.released) {
                if (feasible) {
                    maxSeniorEmployee.run(i + 1);
                } else {
                    maxSeniorEmployee.engage(i + 1);
                }
            }
        }

        @Override
        protected void unfeasible() throws GeneratePlanifException {
            if (pos != null && j >= 0) {
                LOGGER.info("Employee {} not assigned to preferred position.", //$NON-NLS-1$
                        employeeToString(i));
            }
        }
    }

    /**
     * Used to maximize the number of assigned to preferred section.
     * <p>
     * This objective doesn't maximize the preferred section of swappable task.
     * See bug #68.
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class MaxPreferredSection extends SolveRunnable {

        /**
         * The employee's preferred section or null.
         */
        private int a;

        /**
         * Constraint used to check feasibility.
         */
        private Constraint constraint;

        /**
         * The employee.
         */
        private int i;

        /**
         * The linear being maximized.
         */
        private Linear linear;

        /**
         * When this value is set, we have determine a lower bound using the
         * snapshot value. In case we can't improve this value, the problem will
         * be unfeasible and as such, we must sets this value to the constraint.
         */
        private Number snapshotLowerBound;

        /**
         * Default constructor to maximize the preferred section of the employee
         * <code>i</code>.
         * 
         * @param i the employee
         */
        public MaxPreferredSection(int i) {
            this.i = i;
            this.a = -1;
            setFeasibilityPumpHeuristic(true);
        }

        private void assignEmployeeToSection(Integer lowerBound) {
            // Fix all the xik variables where the employee is working on the
            // section.
            for (int j2 = 0; j2 < xijk[i].length; j2++) {
                for (int k = 0; k < xijk[i][j2].length; k++) {
                    // Check if the xijk is assigned to the employee and is
                    // matching the preferred section.
                    if (xijk[i][j2][k] != null && siTable.kInSa(k, this.a)
                            && ONE.equals(snapshot.get(xijk[i][j2][k]))) {
                        // Find the matching Gl
                        for (List<Integer> coefs : table.glIntersectTask(k)) {
                            // For this Gl, remove any tasks not matching the
                            // section.
                            for (int k2 = 0; k2 < coefs.size(); k2++) {
                                if (xijk[i][j2][k2] != null && !siTable.kInSa(k2, a)
                                        && ONE.equals(coefs.get(k2))) {
                                    xijk[i][j2][k2].setLowerBound(ZERO);
                                    xijk[i][j2][k2].setLowerBound(ZERO);
                                }
                            }
                        }
                    }
                }
            }
            // Add a constraint to make sure the employee is assigned to the
            // preferred section.
            String name = String.format("SUM from{k in Si} xijk >=%d", lowerBound); //$NON-NLS-1$
            if (this.constraint != null) {
                this.constraint.setName(name);
                this.constraint.setLowerbound(lowerBound);
            } else {
                this.constraint =
                        lp.addConstraint(name, this.linear, lowerBound, null);
            }
        }

        @Override
        protected void feasible() {
            // Assign employee to preferred section.
            Integer value = Integer.valueOf(lp.getObjectiveValue().intValue());
            assignEmployeeToSection(value);
        }

        @Override
        protected boolean setUp() {
            // Check if the employee is schedule to work according to snapshot
            if (snapshot != null && ZERO.equals(snapshot.get(xi[i]))) {
                this.feasible = false;
                return false;
            }
            // Check if the employee as preferences
            EmployeePreference pref = preferences.get(i);
            Section section;
            if (pref == null || (section = pref.getPreferredSection()) == null) {
                this.feasible = false;
                return false;
            }
            // Check if preferred section is part of the planif.
            this.a = siTable.indexOf(section);
            if (this.a < 0) {
                this.feasible = false;
                return false;
            }
            // Build the linear expression to be maximize
            this.linear = lp.createLinear();
            for (int j = 0; j < xijk[i].length; j++) {
                for (int k = 0; k < xijk[i][j].length; k++) {
                    // Add the variable to the linear only if it's part of the
                    // Team as real (see bug #68) and part of the preferred
                    // section.
                    if (xijk[i][j][k] != null && table.kInRTj(k, j)
                            && siTable.kInSa(k, a)) {
                        this.linear.add(lp.createTerm(ONE, xijk[i][j][k]));
                    }
                }
            }
            if (this.linear.size() == 0) {
                this.feasible = false;
                return false;
            }
            // Also sets the linear with a minimum value using a constraints. To
            // improve performance, compute the minimum value using the
            // snapshot.
            int lowerBound = Math.max(ZERO.intValue(), (snapshot != null
                    ? (snapshotLowerBound = Linears.compute(this.linear, snapshot))
                    : ZERO).intValue());
            String name = String.format("SUM from{k in Si} xik >=%d, for i=%d", //$NON-NLS-1$
                    Integer.valueOf(lowerBound + 1), Integer.valueOf(i));
            this.constraint =
                    lp.addConstraint(name, this.linear,
                            Integer.valueOf(lowerBound + 1), null);
            // Set the linear as an objective
            name = String.format("max[SUM from{k in Si} xijk], for i=%d", //$NON-NLS-1$
                    Integer.valueOf(i));
            lp.setObjectiveName(name);
            lp.setObjectiveLinear(this.linear);
            lp.setObjectiveDirection(LinearProblem.MAXIMIZE);
            LOGGER.info("Maximize employee {} preferred section.", //$NON-NLS-1$
                    employeeToString(i));
            return true;
        }

        @Override
        protected void tearDown() {
            // Remove objective
            lp.setObjectiveName(null);
            lp.setObjectiveLinear(null);
        }

        @Override
        protected void unfeasible() {
            if (this.snapshotLowerBound != null) {
                assignEmployeeToSection(Integer.valueOf(snapshotLowerBound.intValue()));
            } else if (a >= 0) {
                LOGGER.warn("Employee {} can't be assigned to " //$NON-NLS-1$
                        + "preferred section.", employeeToString(i)); //$NON-NLS-1$
            }
        }
    }

    /**
     * Used to maximize the employee preferred team.
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class MaxPreferredTeams extends SolveRunnable {

        /**
         * The employee.
         */
        private int i;

        /**
         * The employee's preferred teams.
         */
        private List<Team> preferredTeams;

        /**
         * Create a new instance to maximize preferred team of the employee
         * <code>i</code>.
         * 
         * @param i the employee
         */
        public MaxPreferredTeams(int i) {
            this.i = i;
        }

        @Override
        protected boolean runSolver() throws GeneratePlanifException {
            // Loop on preferred team
            for (int a = 0; a < preferredTeams.size(); a++) {
                // Check if the employee can be assign to team j
                int j = table.indexOf(preferredTeams.get(a));
                if (j != -1) {
                    // Check if there is a feasible solution where employee i is
                    // assigned to team j.
                    CheckPreferredTeam solve1 = new CheckPreferredTeam(i, j);
                    solve1.run();
                    if (solve1.isFeasible()) {
                        return true;
                    }
                    // FIXME if we determine it's not possible to assign the
                    // employee to the given team, we should fix all the
                    // variable to zero.
                }
            }
            // FIXME if the employee can't be assigned to any preferred team, it
            // might be required to create a constraint for this employee to
            // work at least once. Otherwise, if this employee is not available,
            // most likely he will not be required to work and he will be
            // replace by another employee.
            return false;
        }

        @Override
        protected boolean setUp() {
            // Check if the employee is schedule to work according to snapshot
            if (snapshot != null
                    && (xi[i] == null || ZERO.equals(snapshot.get(xi[i])))) {
                feasible = false;
                return false;
            }
            // Check if employee as preference
            EmployeePreference pref = preferences.get(i);
            if (pref == null
                    || (this.preferredTeams = pref.getPreferredTeam()) == null
                    || this.preferredTeams.size() == 0) {
                // No preference for this employee, leave the function
                feasible = false;
                return false;
            }
            LOGGER.debug("Maximize employee {} preferred team.", //$NON-NLS-1$
                    employeeToString(i));
            return true;
        }
    }

    /**
     * Used to maximize the use of senior employee by checking if their is
     * solutions without least senior employee.
     * <p>
     * This implementation is using the {@link CheckEmployeeNotWorking}
     * runnables to avoid assignment of less senior employees.
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class MaxSeniorEmployee extends SolveRunnable {

        /**
         * The upper employee index to maximize.
         */
        private int from;

        /**
         * Array of runnable used internally.
         */
        private CheckEmployeeNotWorking[] runnables;

        /**
         * Public constructor
         */
        public MaxSeniorEmployee() {
            setFeasibilityPumpHeuristic(true);
            this.runnables = new CheckEmployeeNotWorking[xi.length];
        }

        /**
         * Check if every higher employee then <code>i</code> and <code>i</code>
         * it self is part of the solution.
         * 
         * @param i the employee index
         * @return True if every higher employees are part of the solution.
         */
        private boolean checkHigherEmployeesAssigned(int from, int to) {
            if (snapshot == null) {
                return false;
            }
            // Check if every other employee are schedule to work.
            int i2 = to;
            while (i2 >= from && ONE.equals(snapshot.get(xi[i2]))) {
                i2--;
            }
            return i2 < from;
        }

        /**
         * Restore the previous snapshot.
         */
        public void engage(int from) {
            // Release all the constraints.
            for (int i = from; i < xi.length; i++) {
                if (this.runnables[i] != null) {
                    this.runnables[i].engage();
                }
            }
        }

        /**
         * This implementation will call run. TODO improve performance by
         * checking satisfiability using previous snapshot.
         * 
         * @param from
         * @throws GeneratePlanifException
         */
        public void engageOrRun(int from) throws GeneratePlanifException {
            run(from);
        }

        /**
		 * 
		 */
        public void release(int from) {
            // Release all the constraints.
            for (int i = from; i < xi.length; i++) {
                if (this.runnables[i] != null) {
                    this.runnables[i].release();
                }
            }
        }

        @Override
        public void run() throws GeneratePlanifException {
            this.from = 0;
            super.run();
        }

        /**
         * Recompute this objective from employee define by <code>from</code>.
         * The employee index is inclusive.
         * 
         * @param from the employee index to recompute from (inclusive).
         * @throws GeneratePlanifException
         */
        public void run(int from) throws GeneratePlanifException {
            this.from = from;
            super.run();
        }

        @Override
        protected boolean runSolver() throws GeneratePlanifException {
            for (int i = xi.length - 1; i >= this.from; i--) {
                // Check if every higher employee are working, is so there
                // is no
                // need to compute any more
                if (checkHigherEmployeesAssigned(this.from, i)) {
                    // All higher employee are working, leave the function
                    LOGGER.info("All higher employees are part " //$NON-NLS-1$
                            + "of the solution."); //$NON-NLS-1$
                    return true;
                }
                if (this.runnables[i] == null) {
                    this.runnables[i] = new CheckEmployeeNotWorking(i);
                }
                this.runnables[i].run();
                // Update the progress or leave
                worked(0);
            }
            return true;
        }

        @Override
        protected boolean setUp() throws GeneratePlanifException {
            LOGGER.debug("Maximize use of senior employees."); //$NON-NLS-1$
            return true;
        }

        @Override
        protected void tearDown() throws GeneratePlanifException {
            // Remove the objective function
            lp.setObjectiveName(null);
            lp.setObjectiveLinear(null);
        }
    }

    /**
     * This runnable is used to find the maximum number of tasks that may be
     * assigned to employee.
     * <p>
     * This implementation will run a sequence of solver to achieve this
     * operation. In case it's not feasible to assign all task, the solver will
     * prompt the user to continue.
     * <ul>
     * <li>CheckTaskAssigned : check if all task can be assigned -- It's faster
     * to check feasibility than optimizing.</li>
     * <li>MaxTaskAssigned : optimize the total number of tasks assigned.</li>
     * </ul>
     * <p>
     * This solver is always feasible.
     * <p>
     * If all task can't be assigned and the user refused to continue, this
     * solver throw a {@link GeneratePlanifException#CANCEL_BY_USER}.
     * 
     * @author Patrik Dufresne
     */
    class MaxTaskAssigned extends SolveRunnable {

        /**
         * Solver used internally.
         */
        private CheckTaskAssigned checkTaskAssigned;

        /**
         * Solver used internally.
         */
        private MaxAllTaskAssigned maxAllTaskAssigned;

        /**
         * Solver used internally.
         */
        private MaxImportantTaskAssigned maxImportantTaskAssigned;

        /**
         * The progress monitor.
         */
        private IGeneratePlanifProgressMonitor monitor;

        /**
         * Create a new solver.
         * 
         * @param monitor the monitor used to prompt the user.
         */
        public MaxTaskAssigned(IGeneratePlanifProgressMonitor monitor) {
            if (monitor == null) {
                throw new IllegalArgumentException();
            }
            this.monitor = monitor;
        }

        /**
         * 
         */
        public void engage() {
            if (this.checkTaskAssigned != null) {
                this.checkTaskAssigned.engage();
            }
            if (this.maxImportantTaskAssigned != null) {
                this.maxImportantTaskAssigned.engage();
            }
            if (this.maxAllTaskAssigned != null) {
                this.maxAllTaskAssigned.engage();
            }
        }

        /**
         * Release all the constraint created to assign the maximum number of
         * tasks.
         */
        public void release() {
            // Release the constraints.
            if (this.checkTaskAssigned != null) {
                this.checkTaskAssigned.release();
            }
            if (this.maxImportantTaskAssigned != null) {
                this.maxImportantTaskAssigned.release();
            }
            if (this.maxAllTaskAssigned != null) {
                this.maxAllTaskAssigned.release();
            }
        }

        /**
         * This implementation override the default implementation by running
         * other solver.
         */
        @Override
        protected boolean runSolver() throws GeneratePlanifException {
            // Try to assign all tasks.
            this.checkTaskAssigned = new CheckTaskAssigned();
            this.checkTaskAssigned.run();
            if (this.checkTaskAssigned.isFeasible()) {
                return true;
            }
            // Release the solver.
            this.checkTaskAssigned = null;
            // Ask user if the search should continue
            if (!monitor.callbackContinue()) {
                throw new GeneratePlanifException("Cancel by user", //$NON-NLS-1$
                        GeneratePlanifException.CANCEL_BY_USER);
            }
            // Maximize important tasks assignment
            this.maxImportantTaskAssigned = new MaxImportantTaskAssigned();
            this.maxImportantTaskAssigned.run();
            // Maximize all the assignment
            this.maxAllTaskAssigned = new MaxAllTaskAssigned();
            this.maxAllTaskAssigned.run();
            // Should always return true.
            return this.maxAllTaskAssigned.isFeasible();
        }
    }

    /**
     * This runnable is used to minimize the number of swappable tasks assigned
     * to employees.
     * 
     * <pre>
     * min(SUM from{i=0} to{n} SUM from{j=0} to{m} SUM from{k notin RTj} xijk)
     * </pre>
     * 
     * @author Patrik Dufresne
     */
    class MinAllSwappableTask extends AbstractConstraintRunnable {

        /**
         * The constraint's name
         */
        private static final String CONSTRAINT_NAME =
                "SUM from{i=0} to{n} SUM from{j=0} to{m} SUM from{k notin RTj} xijk <= %d"; //$NON-NLS-1$

        /**
         * The objective name.
         */
        private static final String OBJECTIVE_NAME =
                "min(SUM from{i=0} to{n} SUM from{j=0} to{m} SUM from{k notin RTj} xijk)"; //$NON-NLS-1$

        /**
         * The linear objective.
         */
        private Linear linear;

        /**
         * If set, define the known upper bound according to the snapshot.
         */
        private Integer snapshotUpperBound;

        public MinAllSwappableTask() {
            setFeasibilityPumpHeuristic(true);
        }

        @Override
        protected void feasible() {
            // The solution improved, fix the number of swappable tasks.
            Integer value = Integer.valueOf(lp.getObjectiveValue().intValue());
            fixSwappableTask(value);
        }

        /**
         * Used to fix the number of swappable task assign in this problem to
         * the specified value.
         * <p>
         * This function create or redefine the constraint with the given upper
         * bound.
         * 
         * @param value the maximum number of swappable tasks assigned.
         */
        private void fixSwappableTask(Integer value) {
            // To avoid assignment to more swappable, create or redefine the
            // constraint.
            String name = String.format(CONSTRAINT_NAME, value);
            if (this.constraint != null) {
                this.constraint.setName(name);
                this.constraint.setUpperBound(value);
            } else {
                this.constraint = lp.addConstraint(name, this.linear, null, value);
            }
            LOGGER.info("Minimum number of swappable tasks assigned: {}", value); //$NON-NLS-1$
            this.feasible = true;
        }

        @Override
        protected boolean setUp() {
            // Create the linear function to be optimized
            this.linear = lp.createLinear();
            int m = table.tj().size();
            int p = tasks.size();
            for (int k = 0; k < p; k++) {
                for (int j = 0; j < m; j++) {
                    if (!table.kInRTj(k, j)) {
                        for (int i = 0; i < xijk.length; i++) {
                            if (xijk[i][j][k] != null) {
                                this.linear.add(lp.createTerm(ONE, xijk[i][j][k]));
                            }
                        }
                    }
                }
            }
            // Use the snapshot as a starting solution to be improved.
            if (snapshot != null) {
                this.snapshotUpperBound =
                        Integer.valueOf(Linears.compute(this.linear, snapshot)
                                .intValue());
                // Restrict the feasibility to a lower value.
                Integer upperBound =
                        Integer.valueOf(this.snapshotUpperBound.intValue() - 1);
                String name = String.format(CONSTRAINT_NAME, upperBound);
                this.constraint =
                        lp.addConstraint(name, this.linear, null, upperBound);
            }
            lp.setObjectiveName(OBJECTIVE_NAME);
            lp.setObjectiveLinear(this.linear);
            lp.setObjectiveDirection(LinearProblem.MINIMIZE);
            LOGGER.info("Minimize the number of swappable tasks assigned."); //$NON-NLS-1$
            return true;
        }

        @Override
        protected void tearDown() {
            // In all cases, remove the objective function.
            lp.setObjectiveName(null);
            lp.setObjectiveLinear(null);
        }

        @Override
        protected void unfeasible() throws GeneratePlanifException {
            if (this.snapshotUpperBound != null) {
                // It was impossible to improve the best known solution.
                fixSwappableTask(this.snapshotUpperBound);
            } else {
                // Should be feasible, throw exception.
                throwProblemUnFeasible();
            }
        }
    }

    /**
     * Minimize the number of employee assigned.
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    class MinEmployeeAssigned extends AbstractConstraintRunnable {

        /**
         * The constraint's name.
         */
        private static final String CONSTRAINT_NAME = "SUM from{i} to{n} xi <= %d"; //$NON-NLS-1$

        /**
         * The objective's name.
         */
        private static final String OBJECTIVE_NAME = "min(SUM from{i} to{n} xi)"; //$NON-NLS-1$

        /**
         * The linear being minimized.
         */
        private Linear linear;

        /**
         * If sets, define the best known solution according to the snapshot.
         */
        private Integer snapshotUpperBound;

        /**
         * Public constructor.
         */
        public MinEmployeeAssigned() {
            setFeasibilityPumpHeuristic(true);
        }

        @Override
        protected void feasible() {
            // Get the objective value
            Integer value = Integer.valueOf(lp.getObjectiveValue().intValue());
            fixMaximumNumberOfEmployee(value);
        }

        /**
         * Create a redefine the constraint to sets the maximum number of
         * employees.
         * 
         * @param value the maximum number of employee.
         */
        private void fixMaximumNumberOfEmployee(Integer value) {
            // Create or redefine the constraint upper bound.
            String name = String.format(CONSTRAINT_NAME, value);
            if (this.constraint != null) {
                this.constraint.setName(name);
                this.constraint.setUpperBound(value);
            } else {
                this.constraint = lp.addConstraint(name, linear, null, value);
            }
            LOGGER.info("Minimum number of employees: {}", value); //$NON-NLS-1$
            this.feasible = true;
        }

        @Override
        protected boolean setUp() {
            LOGGER.debug("Minimize number of employee."); //$NON-NLS-1$
            // Create the following linear function : SUM xi
            Integer[] coefs = new Integer[xi.length];
            Arrays.fill(coefs, ONE);
            this.linear = lp.createLinear(coefs, xi);
            // TODO Use the snapshot value to restrict the feasibility and force
            // the solver to improve the solution.
            // To reduce the computing time, sets a maximum using a constraint.
            if (snapshot != null) {
                this.snapshotUpperBound =
                        Integer.valueOf(Linears.compute(linear, snapshot).intValue());
                if (ZERO.equals(this.snapshotUpperBound)) {
                    this.snapshotUpperBound = null;
                    this.feasible = false;
                    return false;
                }
                // Create a constraint to force the solver to improve the
                // current solution.
                Integer upperBound =
                        Integer.valueOf(this.snapshotUpperBound.intValue() - 1);
                String name = String.format(CONSTRAINT_NAME, upperBound);
                this.constraint = lp.addConstraint(name, linear, null, upperBound);
            }
            // Set the linear as an objective.
            lp.setObjectiveName(OBJECTIVE_NAME);
            lp.setObjectiveLinear(this.linear);
            lp.setObjectiveDirection(LinearProblem.MINIMIZE);
            return true;
        }

        @Override
        protected void tearDown() {
            // Remove objective
            lp.setObjectiveName(null);
            lp.setObjectiveLinear(null);
            this.snapshotUpperBound = null;
            this.linear = null;
        }

        @Override
        protected void unfeasible() throws GeneratePlanifException {
            if (this.snapshotUpperBound != null) {
                fixMaximumNumberOfEmployee(this.snapshotUpperBound);
            } else {
                // It's a maximization problem, it' must be feasible.
                throwProblemUnFeasible();
            }
        }
    }

    /**
     * This runnable is used to minimized the number of swapable task. It's
     * believed to be faster to check for feasibility first and then optimize if
     * required.
     * 
     * @author Patrik Dufresne
     */
    class MinSwappableTask extends SolveRunnable {

        private CheckSwappableTask checkSwappableTask;

        private MinAllSwappableTask minAllSwappableTask;

        /**
         * This implementation is calling the {@link #engage()} function of the
         * underlying runnable.
         */
        @Override
        public void engage() {
            // Only one of the solve-runnable should be instantiated. Engage it.
            if (this.checkSwappableTask != null) {
                this.checkSwappableTask.engage();
            } else if (this.minAllSwappableTask != null) {
                this.minAllSwappableTask.engage();
            } else {
                throw new RuntimeException("release() should be called first."); //$NON-NLS-1$
            }
        }

        /**
         * This implementation is calling the {@link #release()} of the
         * underlying runnable.
         */
        @Override
        public void release() {
            // Only one of the solve-runnable should be instantiated. Release
            // it.
            if (this.checkSwappableTask != null) {
                this.checkSwappableTask.release();
            } else if (this.minAllSwappableTask != null) {
                this.minAllSwappableTask.release();
            } else {
                throw new RuntimeException("run() should be called first."); //$NON-NLS-1$
            }
        }

        /**
         * This implementation will
         */
        @Override
        protected boolean runSolver() throws GeneratePlanifException {
            this.checkSwappableTask = new CheckSwappableTask();
            this.checkSwappableTask.run();
            if (this.checkSwappableTask.isFeasible()) {
                return true;
            }
            this.checkSwappableTask = null;
            this.minAllSwappableTask = new MinAllSwappableTask();
            this.minAllSwappableTask.run();
            // Should always be feasible.
            return this.minAllSwappableTask.isFeasible();
        }
    }

    static class SiTable {

        /**
         * Create a new SiTable.
         * 
         * @param tasks the list of taskst
         * @return instance of SiTable.
         */
        public static SiTable create(List<Task> tasks) {
            Set<Section> set = new HashSet<Section>();
            for (Task t : tasks) {
                set.add(t.getPosition().getSection());
            }
            List<Section> sections = new ArrayList<Section>(set);
            boolean[][] table = new boolean[set.size()][tasks.size()];
            for (int k = 0; k < tasks.size(); k++) {
                Section section = tasks.get(k).getPosition().getSection();
                table[sections.indexOf(section)][k] = true;
            }
            return new SiTable(sections, table);
        }

        /**
         * Known sections.
         */
        List<Section> sections;

        /**
         * Table to represent the association of task to sections.
         */
        boolean[][] table;

        /**
         * Create a new SiTable.
         * 
         * @param sections
         * @param table
         */
        protected SiTable(List<Section> sections, boolean[][] table) {
            this.sections = sections;
            this.table = table;
        }

        /**
         * Return the coefficient list for the section <code>a</code>.
         * 
         * @param a the section
         * @return the coefficient list
         */
        public List<Integer> get(int a) {
            return new CoefficientList(this.table[a]);
        }

        /**
         * Return the coefficient list for the section specified.
         * 
         * @param section the section
         * @return the coefficent list or null is the section doesn't exists in
         * this table.
         */
        public List<Integer> get(Section section) {
            int a = this.sections.indexOf(section);
            if (a < 0) {
                return null;
            }
            return new CoefficientList(this.table[a]);
        }

        /**
         * Return the section index.
         * 
         * @param section the section
         * @return the index or -1
         */
        public int indexOf(Section section) {
            return this.sections.indexOf(section);
        }

        /**
         * Check if the task <code>k</code> is part of the section
         * <code>a</code>
         * 
         * @param k the task
         * @param a the section
         * @return True if the task is part of the section.
         */
        public boolean kInSa(int k, int a) {
            return this.table[a][k];
        }
    }

    /**
     * Abstract class providing a structure to solve a linear problem.
     * 
     * @author Patrik Dufresne
     */
    @SuppressWarnings("unqualified-field-access")
    abstract class SolveRunnable {

        protected boolean feasible;

        protected SolverOption option;

        /**
         * Create the solver with default option.
         */
        public SolveRunnable() {
            this.option = solver.createSolverOption();
        }

        /**
         * Used to restore constraints previously destroyed by a call to
         * {@link #release()}.
         * <p>
         * Sub-classes must implement this function to create the constraint
         * again.
         * <p>
         * This implementation always throw an exception.
         */
        public void engage() {
            throw new UnsupportedOperationException();
        }

        /**
         * Used to restore a constraint previously release.
         */
        public void engageOrRun() {
            throw new UnsupportedOperationException();
        }

        /**
         * Sub-classes may implement this function to execute code after the
         * solver determine there is a feasible solution.
         */
        protected void feasible() throws GeneratePlanifException {
            // Nothing to do
        }

        /**
         * Return True if the problem is feasible.
         * 
         * @return
         */
        public boolean isFeasible() {
            return this.feasible;
        }

        /**
         * Used to remove any constraints created by a previous run.
         * <p>
         * Sub-classes should implement this function to dispose any created
         * constraints and store a copy of it to be re-engage. It is recommended
         * to use constraint snapshot.
         * <p>
         * This implementation always throw an exception.
         */
        public void release() {
            throw new UnsupportedOperationException();
        }

        /**
         * Run the solver.
         */
        public void run() throws GeneratePlanifException {
            // Setup the solver
            if (setUp()) {
                // Run the solver if required.
                this.feasible = runSolver();
            }
            // Fork depending of the feasibility.
            if (this.feasible) {
                feasible();
            } else {
                unfeasible();
            }
            tearDown();
        }

        /**
         * Execute the solve operation on the linear problem.
         * 
         * @return True if the problem is feasible.
         * @throws GeneratePlanifException
         */
        protected boolean runSolver() throws GeneratePlanifException {
            boolean feasible = solver.solve(lp, this.option);
            if (feasible) {
                takeSnapshot();
            }
            return feasible;
        }

        /**
         * Sets the branching last always down technique.
         * 
         * @param enabled
         * @return
         */
        protected void setBranchingLastAlwaysDown(boolean enabled) {
            if (!(option instanceof IBranchingTechniqueLastAlwaysDown)) {
                return;
            }
            ((IBranchingTechniqueLastAlwaysDown) option)
                    .setBranchingLastAlwaysDown(enabled);
        }

        /**
         * Sets the feasibility pump heuristic if the solver support it.
         * 
         * @param enabled True to enable, False to disable, null to do nothing
         * @return the previous value
         */
        protected void setFeasibilityPumpHeuristic(boolean enabled) {
            if (!(option instanceof IFeasibilityPumpHeuristic)) {
                return;
            }
            ((IFeasibilityPumpHeuristic) option).setFeasibilityPumpHeuristic(enabled);
        }

        /**
         * Sub-classes may implement this function to execute code before the
         * solver run. i.e.: set constraint or objective
         * 
         * @return True to continue, False to avoid running the solver.
         * Sub-classes must sets the feasible field.
         */
        protected boolean setUp() throws GeneratePlanifException {
            return true;
        }

        /**
         * Take a new snapshot of the feasible solution.
         */
        protected void takeSnapshot() {
            snapshot = ValueSnapshot.create(lp.getVariables());
        }

        /**
         * Sub-classes may implement this function to execute code after the
         * solver run.
         */
        protected void tearDown() throws GeneratePlanifException {
            // Nothing to do
        }

        /**
         * Used to throw new exception when the problem is un-feasible but it
         * should be feasible.
         * 
         * @throws GeneratePlanifException
         */
        protected void throwProblemUnFeasible() throws GeneratePlanifException {
            GeneratePlanifException e =
                    new GeneratePlanifException("Problem is unfeasible.", //$NON-NLS-1$
                            GeneratePlanifException.UNFEASIBLE);
            LOGGER.warn("Problem is unfeasible.", e); //$NON-NLS-1$
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(lp.toString());
            }
            throw e;
        }

        /**
         * Sub-classes may implement this function to run code after the solver
         * determine there is no feasible solution.
         */
        protected void unfeasible() throws GeneratePlanifException {
        }
    }

    static class Table {

        /**
         * Immutable class representing a single Gl.
         * 
         * @author Patrik Dufresne
         */
        private abstract static class Gl {

            public static Gl fromShift(final Shift s, final Date start, final Date end) {
                return new Gl() {

                    public Date getEndDate() {
                        if (s.getEndDate().before(end)) {
                            return s.getEndDate();
                        }
                        return end;
                    }

                    public Shift getShift() {
                        return s;
                    }

                    public Date getStartDate() {
                        if (s.getStartDate().after(start)) {
                            return s.getStartDate();
                        }
                        return start;
                    }
                };
            }

            public static Gl fromTask(final Task t) {
                return new Gl() {

                    public Date getEndDate() {
                        return t.getEndDate();
                    }

                    public Shift getShift() {
                        return t.getProductionEvent().getShift();
                    }

                    public Date getStartDate() {
                        return t.getStartDate();
                    }
                };
            }

            @Override
            public boolean equals(Object obj) {
                if (this == obj)
                    return true;
                if (obj == null)
                    return false;
                if (!(obj instanceof Gl))
                    return false;
                Gl other = (Gl) obj;
                return getEndDate().compareTo(other.getEndDate()) == 0
                        && getStartDate().compareTo(other.getStartDate()) == 0
                        && getShift().equals(other.getShift());
            }

            public abstract Date getEndDate();

            public abstract Shift getShift();

            public abstract Date getStartDate();

            @Override
            public int hashCode() {
                final int prime = 31;
                int result = 1;
                result = prime * result + getEndDate().hashCode();
                result = prime * result + getShift().hashCode();
                result = prime * result + getStartDate().hashCode();
                return result;
            }

            @Override
            public String toString() {
                return "Gl [getShift()=" + getShift() + ", getStartDate()="
                        + getStartDate() + ", getEndDate()=" + getEndDate() + "]";
            }
        }

        /**
         * Create a new instance of {@link Table} from the shifts and tasks
         * specified.
         * 
         * @param allShifts the collection of all shifts.
         * @param tasks the ordered list of tasks.
         * @return a new instance of Table
         */
        public static Table create(Collection<Shift> allShifts, List<Task> tasks) {
            // Count the maximum number of team
            Set<Team> set = new HashSet<Team>();
            for (Shift s : allShifts) {
                set.add(s.getTeam());
            }
            /*
             * Compute the tables
             */
            List<Gl> glList = new ArrayList<Gl>();
            List<Team> teamList = new ArrayList<Team>();
            boolean[][] tj = new boolean[set.size()][tasks.size()];
            boolean[][] rtj = new boolean[set.size()][tasks.size()];
            List<boolean[]> glTable = new ArrayList<boolean[]>();
            // For each task find the matching team and gl.
            int j, k, l;
            Task task = null;
            for (k = 0; k < tasks.size(); k++) {
                task = tasks.get(k);
                // If the related position is swappable, search for every
                // matching shift, otherwise do it for a single shift.
                for (Shift shift : task.getPosition().getSwappable()
                        ? allShifts
                        : Arrays.asList(task.getProductionEvent().getShift())) {
                    // Check if the shift contains the task and vice-versa.
                    if (TimeRanges.containsEquals(shift, task.getProductionEvent()
                            .getShift())
                            || TimeRanges.containsEquals(task.getProductionEvent()
                                    .getShift(), shift)) {
                        // Find corresponding team
                        j = teamList.indexOf(shift.getTeam());
                        if (j == -1) {
                            j = teamList.size();
                            teamList.add(shift.getTeam());
                        }
                        tj[j][k] = true;
                        if (shift.equals(task.getProductionEvent().getShift())) {
                            rtj[j][k] = true;
                        }
                        // Sets corresponding gl
                        Gl item =
                                Gl.fromShift(shift, task.getStartDate(),
                                        task.getEndDate());
                        l = glList.indexOf(item);
                        if (l == -1) {
                            l = glTable.size();
                            glList.add(item);
                            glTable.add(new boolean[tasks.size()]);
                        }
                        glTable.get(l)[k] = true;
                    }
                }
            }
            /*
             * Create a copy of the table by removing the unused row.
             */
            if (tj.length != teamList.size()) {
                tj = Arrays.copyOf(tj, teamList.size());
                rtj = Arrays.copyOf(rtj, teamList.size());
            }
            boolean[][] gl = new boolean[glTable.size()][tasks.size()];
            glTable.toArray(gl);
            return new Table((List<Gl>) glList, teamList, tj, rtj, gl);
        }

        /**
         * The Gl table.
         */
        boolean[][] gl;

        private List<Gl> gls;

        /**
         * Association table the real team, according to
         * task.getShift().getTeam().
         */
        boolean[][] rtj;

        /**
		 * 
		 */
        private List<Team> teams;

        /**
         * Association table including swappable tasks.
         */
        boolean[][] tj;

        /**
         * Private constructor to create the TjTable.
         * 
         * @param gl
         */
        private Table(List<Gl> gls, List<Team> teams, boolean[][] table,
                boolean[][] rtj, boolean[][] gl) {
            this.gls = gls;
            this.teams = teams;
            this.tj = table;
            this.rtj = rtj;
            this.gl = gl;
        }

        /**
         * Return the team as the index <code>j</code>.
         * 
         * @param j the team index
         * @return the team
         */
        public Team getTeam(int j) {
            return this.teams.get(j);
        }

        /**
         * This function return a view on the Gl table. Each entry in this table
         * represent one Gl. An entry is a coefficient list (zero or one)
         * defining if a task is part of the Gl.
         * 
         * @return the Gl table.
         */
        public List<List<Integer>> gl() {
            // Create a view on the gl
            return new AbstractList<List<Integer>>() {

                @Override
                public List<Integer> get(int l) {
                    return new CoefficientList(Table.this.gl[l]);
                }

                @Override
                public int size() {
                    return Table.this.gl.length;
                }
            };
        }

        /**
         * Return every Gl intersecting with the task <code>k</code> specified.
         * The integer list correspond to a coefficient list for the tasks.
         * 
         * @param k the task
         * @return a collection of Gl
         */
        public Collection<List<Integer>> glIntersectTask(final int k) {
            // Create an abstract array filtering the Gl not intersecting with
            // k.
            return new AbstractCollection<List<Integer>>() {

                /**
                 * This implementation is wrapping the iterator with a
                 * FilterIterator.
                 */
                @Override
                @SuppressWarnings("unchecked")
                public Iterator<List<Integer>> iterator() {
                    return new FilterIterator(gl().iterator(), new Predicate() {

                        @Override
                        public boolean evaluate(Object object) {
                            return ONE.equals(((List<Integer>) object).get(k));
                        }
                    });
                }

                /**
                 * This implementation use the iterator to count the number of
                 * entries.
                 */
                @Override
                public int size() {
                    int count = 0;
                    Iterator<List<Integer>> iter = iterator();
                    while (iter.hasNext()) {
                        iter.next();
                        count++;
                    }
                    return count;
                }
            };
        }

        /**
         * Return the index of the team specified.
         * 
         * @param team the team for which to find the index
         * @return the team index of -1 if not found.
         */
        public int indexOf(Team team) {
            return this.teams.indexOf(team);
        }

        /**
         * Check if the task <code>k</code> is part of team <code>j</code>.
         * 
         * @param k the task
         * @param j the team
         * @return True if the task is part of the team as real
         */
        public boolean kInRTj(int k, int j) {
            return this.rtj[j][k];
        }

        /**
         * Check if the task <code>k</code> is part of team <code>j</code>.
         * <p>
         * Same as <code>tj().get(j).get(k).equals(ONE)</code>.
         * 
         * @param k the task
         * @param j the team
         * @return True if the task is part of the team.
         */
        public boolean kInTj(int k, int j) {
            return this.tj[j][k];
        }

        /**
         * This function return a view on the RTj table. Each entry in this
         * table represent one team. An entry is a coefficient list (zero or
         * one) defining if the task is part of the team for real (not as a
         * swappable).
         * 
         * @return the RTj table
         */
        public List<List<Integer>> rtj() {
            // Create a view on the gl
            return new AbstractList<List<Integer>>() {

                @Override
                public List<Integer> get(int l) {
                    return new CoefficientList(Table.this.rtj[l]);
                }

                @Override
                public int size() {
                    return Table.this.rtj.length;
                }
            };
        }

        /**
         * This function return a reversed view on the RTj table.
         * 
         * @return
         */
        public List<List<Integer>> rtjReverse() {
            // Create a view on the RTj
            return new AbstractList<List<Integer>>() {

                @Override
                public List<Integer> get(int l) {
                    return new CoefficientList(Table.this.rtj[l], true);
                }

                @Override
                public int size() {
                    return Table.this.rtj.length;
                }
            };
        }

        public int teamForGl(int l) {
            Team team = this.gls.get(l).getShift().getTeam();
            return this.teams.indexOf(team);
        }

        /**
         * Return the complete list of teams.
         * 
         * @return
         */
        public Collection<Team> teams() {
            return Collections.unmodifiableList(this.teams);
        }

        /**
         * This function return a view on the Tj table. Each entry in this table
         * represent one team. An entry is a coefficient list (zero or one)
         * defining if the task is part of the team.
         * <p>
         * If a task <code>k</code> is part of the team <code>j</code>, then
         * tj().get(j)[k] equals ONE, otherwise it's null.
         * 
         * @return the Tj table
         */
        public List<List<Integer>> tj() {
            // Create a view on the gl
            return new AbstractList<List<Integer>>() {

                @Override
                public List<Integer> get(int l) {
                    return new CoefficientList(Table.this.tj[l]);
                }

                @Override
                public int size() {
                    return Table.this.tj.length;
                }
            };
        }
    }

    /**
     * Logger
     */
    static final transient Logger LOGGER = LoggerFactory
            .getLogger(ProblemBuilder.class);

    static final Integer ONE = Integer.valueOf(1);

    /**
     * Unit of work.
     */
    private static final int UNIT = 1;

    static final Integer ZERO = Integer.valueOf(0);

    /**
     * Create binary variable with the name xik[i,k].
     * 
     * @param lp the linear problem
     * @param i the i value
     * @param k the k value
     * @return the variable
     */
    protected static Variable createXijkVariable(LinearProblem lp, int i, int j, int k) {
        String name = String.format("xijk[%d,%d,%d]", i, j, k); //$NON-NLS-1$
        return lp.addBinaryVariable(name);
    }

    /**
     * Create a binary variable with the name xij[i, j].
     * 
     * @param lp the linear problem
     * @param i the i value
     * @param j the j value
     * @return the variable
     */
    protected static Variable createXijVariable(LinearProblem lp, int i, int j) {
        String name = String.format("xij[%d,%d]", i, j); //$NON-NLS-1$
        return lp.addBinaryVariable(name);
    }

    /**
     * Create a binary variable with the name xi[i].
     * 
     * @param lp the linear problem
     * @param i the i value
     * @return the variable
     */
    protected static Variable createXiVariable(LinearProblem lp, int i) {
        String name = String.format("xi[%d]", i); //$NON-NLS-1$
        return lp.addBinaryVariable(name);
    }

    /**
     * True if the given variable is fixed
     * 
     * @param var
     * @return
     */
    static boolean isFixedToZero(Variable var) {
        return var.getLowerBound().equals(var.getUpperBound())
                && var.getLowerBound().equals(ZERO);
    }

    /**
     * Create an array of preferences for fast access.
     * 
     * @param preferences the preferences received by the problem builder
     * @param employees the employees
     * @throws GeneratePlanifException
     */
    private static List<EmployeePreference> sortPreferences(
            Collection<EmployeePreference> preferences, List<Employee> employees) {
        // Build an array of preference
        EmployeePreference[] prefs = new EmployeePreference[employees.size()];
        // Fill the array with preference object
        for (EmployeePreference pref : preferences) {
            // Check if the employee associated to the preference exists in our
            // list.
            int index = employees.indexOf(pref.getEmployee());
            if (!(index < 0)) {
                prefs[index] = pref;
            }
        }
        // Return array
        return Arrays.asList(prefs);
    }

    /**
     * This function always throw an exception.
     * 
     * @param employee
     * @param task
     * @throws GeneratePlanifException
     */
    private static void throwUnavailableEmployeeLocked(Employee employee, Task task)
            throws GeneratePlanifException {
        // Can't locked an employee on unqualified position,
        // throw exception
        throw new GeneratePlanifException(
                employee + " not avaiable for" + task, //$NON-NLS-1$
                GeneratePlanifException.EMPLOYEE_LOCKED_UNAVAIL_OR_UNQUALIFY,
                employee, task);
    }

    int count = 0;

    /**
     * Employees array ordered by seniority starting by the most senior
     * employee.
     */
    List<Employee> employees;

    /**
     * The linear problem.
     */
    transient LinearProblem lp;

    private MaxEmployeePreferences maxEmployeePreferences;

    private MaxSeniorEmployee maxSeniorEmployee;

    private MaxTaskAssigned maxTaskAssigned;

    MinEmployeeAssigned minEmployee;

    private MinSwappableTask minSwappable;

    private IGeneratePlanifProgressMonitor monitor;

    BidiMultiMap<Employee, Task> nonAvailabilityTable;

    /**
     * Employee's preferences ordered by seniority. The index matches the
     * {@link #employees} index.
     */
    List<EmployeePreference> preferences;

    BidiMultiMap<Employee, Task> qualificationTable;

    /**
     * Section table.
     */
    transient SiTable siTable;

    /**
     * Used to keep track of the last known feasible solution.
     */
    transient ValueSnapshot snapshot;

    /**
     * Linear problem solver.
     */
    transient Solver solver;

    /**
     * Coefficients table for the team.
     * <p>
     * This object represent T_j, TS_j, TNS_j.
     */
    Table table;

    /**
     * Tasks array.
     */
    List<Task> tasks;

    transient Variable[] xi;

    transient Variable[][] xij;

    transient Variable[][][] xijk;

    /**
     * Create a new problem builder with the task and the employee specified.
     * 
     * @param events the production events
     * @param tasks the tasks associated to the specified production events
     * @param qualificationTable the qualification table for each tasks
     * @param nonAvailabilityTable the non availability table
     * @param preferences the employee's preferences for each employee in the
     * qualification table.
     */
    public ProblemBuilder(Collection<Shift> shifts,
            BidiMultiMap<Employee, Task> qualificationTable,
            BidiMultiMap<Employee, Task> nonAvailabilityTable,
            Collection<EmployeePreference> preferences) {
        this.qualificationTable = qualificationTable;
        this.nonAvailabilityTable = nonAvailabilityTable;
        // Copy the tasks into an unmodifable list
        this.tasks = new ArrayList<Task>();
        for (Task t : qualificationTable.valueSet()) {
            if (!(t.getLocked() && t.getEmployee() == null)) {
                this.tasks.add(t);
            }
        }
        this.tasks = Collections.unmodifiableList(this.tasks);
        // Copy the employees list and sort it by seniority
        // The qualificationTable contains employees that are not available
        // during the week. We need to remove them. See #59.
        this.employees = new ArrayList<Employee>();
        for (Employee e : qualificationTable.keySet()) {
            if (isAvailable(e)) {
                this.employees.add(e);
            }
        }
        Collections.sort(this.employees, EmployeeComparators.bySeniority());
        this.employees = Collections.unmodifiableList(this.employees);
        // Sort the preference in the same order as the employees.
        this.preferences =
                Collections.unmodifiableList(sortPreferences(preferences,
                        this.employees));
        // Compute the tables
        this.table =
                Table.create(Collections.unmodifiableCollection(shifts), this.tasks);
        this.siTable = SiTable.create(this.tasks);
        /**
         * Create a ILogger to handle logs from the ILP engine.
         */
        ILPPolicy.setLog(new ILPLogger() {

            final transient Logger ilpLogger = LoggerFactory
                    .getLogger(ILPLogger.class);

            /**
             * This implementation return the log level of the ilp logger.
             */
            @Override
            public int getLevel() {
                if (ilpLogger.isDebugEnabled()) {
                    return ILPLogger.DEBUG;
                } else if (ilpLogger.isWarnEnabled()) {
                    return ILPLogger.WARNING;
                } else if (ilpLogger.isInfoEnabled()) {
                    return ILPLogger.INFO;
                }
                return ILPLogger.ERROR;
            }

            @Override
            public void log(int severity, String message) {
                // Phase out the info and debug log to avoid generating to much
                // log in debug mode.
                switch (severity) {
                    case ILPLogger.DEBUG:
                        this.ilpLogger.debug(message);
                        break;
                    case ILPLogger.INFO:
                        this.ilpLogger.info(message);
                        break;
                    case ILPLogger.WARNING:
                        this.ilpLogger.warn(message);
                        break;
                    case ILPLogger.ERROR:
                        this.ilpLogger.error(message);
                        break;
                }
            }
        });
    }

    /**
     * Call the {@link IGeneratePlanifProgressMonitor#beginSearch(int)} with the
     * total number of work.
     * 
     * @param monitor the progress monitor
     */
    private void beginSearch() {
        int totalWork = 0;
        // One unit for createEmployeeTaskVariables
        totalWork += UNIT;
        // One unit for createConstraintEmployeeAssignToAtMostOneTaskWithinGl
        totalWork += UNIT;
        // One unit for createConstraintEmployeeAssignToAtMostOneTeam
        totalWork += UNIT;
        // One unit for createConstraintAssignEmployeeWithPreferredSeniority
        totalWork += UNIT;
        // One unit for maximizeNbTaskAssigned
        totalWork += UNIT;
        // One unit for MinEmployeeAssigned
        totalWork += UNIT;
        // One unit for MinSwappableTask
        totalWork += UNIT;
        // One unit for MaxSeniorEmployee
        totalWork += UNIT;
        // One two units for each employee :
        // maximizeEmployeesPreferredPositionAndPreferredTeam
        totalWork += (UNIT + UNIT) * this.employees.size();
        this.monitor.beginSearch(totalWork);
    }

    /**
     * Create the problen and solve it
     * 
     * @param monitor the progress monitor (can't be null)
     * @return True if the problem is feasible
     * @throws GeneratePlanifException
     * @throws InterruptedException
     */
    public void createAndSolveProblem(IGeneratePlanifProgressMonitor monitor)
            throws GeneratePlanifException {
        this.monitor = monitor;
        /*
         * Create linear problem
         */
        LOGGER.info("Create linear problem"); //$NON-NLS-1$
        SolverFactory solverFactory = GLPKSolverFactory.instance();
        this.solver = solverFactory.createSolver();
        this.lp = this.solver.createLinearProblem();
        // Update the monitor
        beginSearch();
        /*
         * Create variables
         */
        try {
            setStep(IGeneratePlanifProgressMonitor.STEP_CREATE_VARIABLES);
            LOGGER.info("Create variables"); //$NON-NLS-1$
            xi = new Variable[this.employees.size()];
            xij = new Variable[this.employees.size()][this.table.tj().size()];
            xijk =
                    new Variable[this.employees.size()][this.table.tj().size()][this.tasks
                            .size()];
            createEmployeeTaskVariables();
            worked(UNIT);
            createConstraintEmployeeAssignToAtMostOneTaskWithinGl();
            worked(UNIT);
            createConstraintEmployeeAssignToAtMostOneTeam();
            worked(UNIT);
            // Check if the problem is feasible.
            LOGGER.info("Check if problem is feasible."); //$NON-NLS-1$
            if (!this.solver.solve(this.lp, this.solver.createSolverOption())) {
                LOGGER.warn("Problem can't be solved."); //$NON-NLS-1$
                throw new GeneratePlanifException("Starting condition is unfeasible.", //$NON-NLS-1$
                        GeneratePlanifException.STARTING_UNFEASIBLE);
            }
            createConstraintAssignEmployeeWithPreferredSeniority();
            worked(UNIT);
            // Find the maximum number of tasks
            setStep(IGeneratePlanifProgressMonitor.STEP_MAXIMIZE_ASSIGNED_TASK);
            this.maxTaskAssigned = new MaxTaskAssigned(this.monitor);
            this.maxTaskAssigned.run();
            worked(UNIT);
            // Find the minimum number of employee required
            setStep(IGeneratePlanifProgressMonitor.STEP_MINIMIZE_EMPLOYEE);
            this.minEmployee = new MinEmployeeAssigned();
            this.minEmployee.run();
            worked(UNIT);
            // Find the minimum number of swappable task to be assigned.
            this.minSwappable = new MinSwappableTask();
            this.minSwappable.run();
            worked(UNIT);
            // Find the most senior employees
            setStep(IGeneratePlanifProgressMonitor.STEP_MAXIMIZE_SENIOR);
            this.maxSeniorEmployee = new MaxSeniorEmployee();
            this.maxSeniorEmployee.run();
            worked(UNIT);
            // Maximize the employee's preferences
            setStep(IGeneratePlanifProgressMonitor.STEP_MAXIMIZE_PREFERENCES);
            this.maxEmployeePreferences = new MaxEmployeePreferences();
            this.maxEmployeePreferences.run();
            // This solve is not always required, need to determine if the
            // problem is dirty and require a solve.
            if (lp.isDirty()) {
                if (!solver.solve(lp, this.solver.createSolverOption())) {
                    LOGGER.warn("Problem can't be solved."); //$NON-NLS-1$
                    throw new GeneratePlanifException("Problem can't be solved.", //$NON-NLS-1$
                            GeneratePlanifException.UNFEASIBLE);
                }
            }
            // Sets the employee value for each task.
            persistResult();
        } finally {
            this.tasks = null;
            this.preferences = null;
            this.xi = null;
            this.xij = null;
            this.xijk = null;
            // Free resources
            lp.dispose();
            solver.dispose();
        }
    }

    /**
     * For the given linear problem, this function will try to assign employee
     * with preferred seniority to their first preferred team.
     * 
     * @throws GeneratePlanifException
     */
    private void createConstraintAssignEmployeeWithPreferredSeniority()
            throws GeneratePlanifException {
        boolean first = true;
        for (int i = 0; i < this.employees.size(); i++) {
            // Avoid creating constraint for employees without preferred
            // seniority and required preferred team.
            EmployeePreference pref;
            List<Team> teams;
            int j;
            if (this.employees.get(i) == null
                    || !this.employees.get(i).getPreferencialSeniority()
                    || (pref = this.preferences.get(i)) == null
                    || (teams = pref.getPreferredTeam()) == null || teams.size() <= 0
                    || (j = table.indexOf(teams.get(0))) <= -1) {
                continue;
            }
            if (first) {
                LOGGER.info("Create constraints: assign employee with " + //$NON-NLS-1$
                        "preferred seniority."); //$NON-NLS-1$
                first = false;
            }
            // Apply the non-availabilities
            Employee employee = employees.get(i);
            for (int j2 = 0; j2 < xijk[i].length; j2++) {
                for (int k = 0; k < xijk[i][j2].length; k++) {
                    if (xijk[i][j2][k] != null && !isAvailable(employee, tasks.get(k))) {
                        // Need to sets the variables to zero.
                        xijk[i][j2][k].setLowerBound(ZERO);
                        xijk[i][j2][k].setUpperBound(ZERO);
                    }
                }
            }
            // Check if the employee can be assigned to preferred team.
            CheckPreferredTeam runnable = new CheckPreferredTeam(i, j);
            runnable.run();
            if (!runnable.isFeasible()) {
                // Throw new exception.
                String msg =
                        String.format("Employee %s can't work on preferred shift %s.", //$NON-NLS-1$
                                employeeToString(i), teamToString(j));
                throw new GeneratePlanifException(msg,
                        GeneratePlanifException.EMPLOYEE_PREFERRED_SENIORITY,
                        this.employees.get(i), teams.get(0));
            }
        }
    }

    /**
     * Create the following constraint to enforce an employee to be assign only
     * to task within the same team.
     * 
     * <pre>
     * SUM from{k ∈ Gl} xijk <= x_ij, for each i and each l
     * </pre>
     */
    private void createConstraintEmployeeAssignToAtMostOneTaskWithinGl() {
        LOGGER.info("Create constraints: employee assign " + //$NON-NLS-1$
                "to at most one task whitin a Gl."); //$NON-NLS-1$
        // Loop on each Gl
        List<List<Integer>> gl = table.gl();
        for (int l = 0; l < gl.size(); l++) {
            // Considering their is only one team for a Gl, get the team
            // associated with the gl.
            int j = this.table.teamForGl(l);
            List<Integer> coefs = gl.get(l);
            // Loop on employees
            for (int i = 0; i < this.employees.size(); i++) {
                // Create the following constraints
                // SUM from{k in Gl} xijk <= xij
                Linear linear = lp.createLinear(coefs, Arrays.asList(xijk[i][j]));
                // Check if there is any term in the linear expression
                if (linear.size() > 0) {
                    if (xij[i][j] == null) {
                        xij[i][j] = createXijVariable(lp, i, j);
                    }
                    linear.add(lp.createTerm(-1, xij[i][j]));
                    String name =
                            String.format(
                                    "SUM from{k ∈ Gl} xijk <= x_ij, for i=%d, j=%d, l=%d", //$NON-NLS-1$
                                    i, j, l);
                    lp.addConstraint(name, linear, null, ZERO);
                }
            }
        }
    }

    /**
     * Create the following constraints to enforce the employee to work on at
     * most one team.
     * 
     * <pre>
     * SUM from{j=0} to{m} xij <= xi, for each employee i
     * </pre>
     */
    private void createConstraintEmployeeAssignToAtMostOneTeam() {
        LOGGER.info("Create constraints: employee assign to at " //$NON-NLS-1$
                + "most one team."); //$NON-NLS-1$
        for (int i = 0; i < this.employees.size(); i++) {
            // Create the linear
            Linear linear = lp.createLinear();
            for (int j = 0; j < this.xij[i].length; j++) {
                if (xij[i] != null && xij[i][j] != null) {
                    linear.add(lp.createTerm(ONE, xij[i][j]));
                }
            }
            // Create the constraint if the linear is not empty.
            if (linear.size() > 0) {
                // Create the variable if needed
                if (xi[i] == null) {
                    xi[i] = createXiVariable(lp, i);
                }
                linear.add(lp.createTerm(-1, xi[i]));
                String name =
                        String.format("SUM from{j=0} to{m} xij <= xi, for i=%d", i); //$NON-NLS-1$
                lp.addConstraint(name, linear, null, ZERO);
            }
        }
    }

    /**
     * This function create every xijk variables if required. It also create the
     * following constraint:
     * 
     * <pre>
     * SUM from{i=0} to{n} SUM from{j=0} to {m} x_ijk <= 1, for each k
     * </pre>
     * 
     * A task is performed by at most one employee.
     * 
     * @throws GeneratePlanifException
     */
    private void createEmployeeTaskVariables() throws GeneratePlanifException {
        // Loop on tasks
        for (int k = 0; k < tasks.size(); k++) {
            Task task = tasks.get(k);
            Linear linear = lp.createLinear();
            // Check if the tasks id locked.
            if (task.getLocked()) {
                Employee employee = task.getEmployee();
                // Check if the locked employee is available, if not throw an
                // exception.
                int i = employees.indexOf(employee);
                if (!isAvailable(employee, task) || i < 0) {
                    throwUnavailableEmployeeLocked(employee, task);
                }
                // Create a fixed variable for locked task whatever the employee
                // is qualify or not. See #122.
                Linear lockLinear = lp.createLinear();
                for (int j = 0; j < table.tj().size(); j++) {
                    if (table.kInTj(k, j)) {
                        xijk[i][j][k] = createXijkVariable(lp, i, j, k);
                        lockLinear.add(lp.createTerm(ONE, xijk[i][j][k]));
                        linear.add(lp.createTerm(ONE, xijk[i][j][k]));
                    }
                }
                if (lockLinear.size() > 0) {
                    String name =
                            String.format(
                                    "SUM from{j=0} to{p} xijk >= 1, for i=%d, k=%d", //$NON-NLS-1$
                                    i, k);
                    lp.addConstraint(name, lockLinear, ONE, null);
                }
            } else {
                // Loop on each employee
                for (int i = 0; i < employees.size(); i++) {
                    // Avoid creation of variables for which the employee is not
                    // qualify or available.
                    boolean isQualify = isQualify(employees.get(i), task);
                    if (isQualify) {
                        // Need to create the variables
                        for (int j = 0; j < table.tj().size(); j++) {
                            if (table.kInTj(k, j)) {
                                xijk[i][j][k] = createXijkVariable(lp, i, j, k);
                                linear.add(lp.createTerm(ONE, xijk[i][j][k]));
                            }
                        }
                    }
                }
            }
            // Add the constraint if the linear is not empty.
            if (linear.size() > 0) {
                String name =
                        String.format(
                                "SUM from{i=0} to{n} SUM from{j=0} to{p} xijk <= 1, for k=%d", //$NON-NLS-1$
                                k);
                lp.addConstraint(name, linear, null, ONE);
            }
        }
    }

    /**
     * Return a string representation of the employee i
     * 
     * @param i the employee
     * @return the string
     */
    String employeeToString(int i) {
        StringBuilder buf = new StringBuilder();
        buf.append(i);
        Employee employee = this.employees.get(i);
        String firstname = employee.getFirstname();
        String lastname = employee.getLastname();
        if (firstname != null || lastname != null) {
            buf.append(" ("); //$NON-NLS-1$
            if (firstname != null) {
                buf.append(firstname);
                buf.append(" "); //$NON-NLS-1$
            }
            if (lastname != null) {
                buf.append(lastname);
            }
            buf.append(")"); //$NON-NLS-1$
        }
        return buf.toString();
    }

    /**
     * Check if the employee is available.
     * 
     * @param employee the employee
     * @return True if the employee is not available for this planif.
     */
    private boolean isAvailable(Employee employee) {
        int count = this.nonAvailabilityTable.valueSet(employee).size();
        return count < this.tasks.size();
    }

    /**
     * Check if the employee is available to work on the given tasks using the
     * non-availability table.
     * 
     * @param employee the employee
     * @param task the task
     * @return True if the employee is available.
     */
    boolean isAvailable(Employee employee, Task task) {
        return !nonAvailabilityTable.containsEntry(employee, task);
    }

    /**
     * This function is used to check if an employee is quality to be assigned
     * to the given task.
     * 
     * @return True if the employee is qualify.
     */
    private boolean isQualify(Employee employee, Task task) {
        return this.qualificationTable.containsEntry(employee, task);
    }

    /**
     * Save the result of the search into the planif event objects.
     * 
     * @throws GeneratePlanifException
     */
    private void persistResult() throws GeneratePlanifException {
        for (int k = 0; k < this.tasks.size(); k++) {
            // By default, initialize the employee to null
            this.tasks.get(k).setEmployee(null);
            for (int i = 0; i < xijk.length; i++) {
                for (int j = 0; j < xijk[i].length; j++) {
                    if (xijk[i][j][k] != null && ONE.equals(xijk[i][j][k].getValue())) {
                        this.tasks.get(k).setEmployee(this.employees.get(i));
                    }
                }
            }
        }
    }

    /**
     * Sets the new step to the progress monitor. Should be a one of the
     * constant define in {@link IGeneratePlanifProgressMonitor}.
     * 
     * @param step the new step id.
     */
    private void setStep(int step) {
        if (this.monitor != null) {
            this.monitor.setStep(step);
        }
    }

    /**
     * Return a string representation of the team j
     * 
     * @param j the team
     * @return the string
     */
    String teamToString(int j) {
        StringBuilder buf = new StringBuilder();
        buf.append(j);
        Team team = this.table.getTeam(j);
        String name = team.getName();
        if (name != null) {
            buf.append(" ("); //$NON-NLS-1$
            buf.append(team.getName());
            buf.append(")"); //$NON-NLS-1$
        }
        return buf.toString();
    }

    /**
     * Return a collection of variables related to employee <code>i</code>. The
     * collection return each non-null xi, xij, xijk variables.
     * 
     * @param i the employee.
     * @return the collection of variables.
     */
    public Collection<Variable> variablesForEmployee(int i) {
        ArrayList<Variable> list = new ArrayList<Variable>();
        list.add(xi[i]);
        for (int j = 0; j < xij[i].length; j++) {
            if (xij[i][j] != null) {
                list.add(xij[i][j]);
            }
            for (int k = 0; k < xijk[i][j].length; k++) {
                if (xijk[i][j][k] != null) {
                    list.add(xijk[i][j][k]);
                }
            }
        }
        return list;
    }

    /**
     * Used to update the progress of the monitor and it's also check the cancel
     * state of the monitor.
     * 
     * @param monitor the progress monitor
     * @param unit unit of work
     * @throws InterruptedException if
     * {@link IGeneratePlanifProgressMonitor#isCanceled()} return true
     */
    private void worked(int unit) throws GeneratePlanifException {
        if (this.monitor != null) {
            if (this.monitor.isCanceled()) {
                throw new GeneratePlanifException("Cancel by user", //$NON-NLS-1$
                        GeneratePlanifException.CANCEL_BY_USER);
            }
            this.monitor.worked(UNIT);
        }
    }
}
