/**
 * Copyright (C) 2019 Patrik Dufresne Service Logiciel <info@patrikdufresne.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package com.planimod.core.planif;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.patrikdufresne.ilp.Linear;
import com.patrikdufresne.ilp.LinearProblem;
import com.patrikdufresne.ilp.util.Linears;

/**
 * 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
 */
public class MinAllSwappableTask extends AbstractConstraintRunnable {

    private static final transient Logger LOGGER = LoggerFactory.getLogger(MinAllSwappableTask.class);

    /**
     * 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";

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

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

    /**
     * Public constructor.
     * 
     * @param problem
     *            reference to ProblemBuilder
     */
    public MinAllSwappableTask(ProblemBuilder problem) {
        super(problem);
        setFeasibilityPumpHeuristic(true);
    }

    @Override
    protected void feasible() {
        // The solution improved, fix the number of swappable tasks.
        Integer value = Integer.valueOf(pb.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.setUpperBound(value);
        } else {
            this.constraint = pb.lp.addConstraint(name, this.linear, null, value);
        }
        LOGGER.info("Minimum number of swappable tasks assigned: {}", value);
        this.feasible = true;
    }

    @Override
    protected boolean setUp() {
        // Create the linear function to be optimized
        this.linear = pb.lp.createLinear();
        int m = pb.table.tj().size();
        int p = pb.tasks.size();
        for (int k = 0; k < p; k++) {
            for (int j = 0; j < m; j++) {
                if (!pb.table.kInRTj(k, j)) {
                    for (int i = 0; i < pb.xijk.length; i++) {
                        if (pb.xijk[i][j][k] != null) {
                            this.linear.add(pb.lp.createTerm(ONE, pb.xijk[i][j][k]));
                        }
                    }
                }
            }
        }
        // Use the snapshot as a starting solution to be improved.
        if (pb.snapshot != null) {
            this.snapshotUpperBound = Integer.valueOf(Linears.compute(this.linear, pb.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 = pb.lp.addConstraint(name, this.linear, null, upperBound);
        }
        pb.lp.setObjectiveLinear(this.linear);
        pb.lp.setObjectiveDirection(LinearProblem.MINIMIZE);
        LOGGER.info("Minimize the number of swappable tasks assigned.");
        return true;
    }

    @Override
    protected void tearDown() {
        // In all cases, remove the objective function.
        pb.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();
        }
    }
}