// Copyright 2010-2021 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//

#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "absl/base/attributes.h"
#include "absl/memory/memory.h"
#include "absl/strings/match.h"
#include "absl/strings/str_format.h"
#include "ortools/base/commandlineflags.h"
#include "ortools/base/hash.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/logging.h"
#include "ortools/base/timer.h"
#include "ortools/linear_solver/linear_solver.h"

#if defined(USE_CLP) || defined(USE_CBC)

#undef PACKAGE
#undef VERSION
#include "ClpConfig.h"
#include "ClpMessage.hpp"
#include "ClpSimplex.hpp"
#include "CoinBuild.hpp"

namespace operations_research {

class CLPInterface : public MPSolverInterface {
 public:
  // Constructor that takes a name for the underlying CLP solver.
  explicit CLPInterface(MPSolver* const solver);
  ~CLPInterface() override;

  // Sets the optimization direction (min/max).
  void SetOptimizationDirection(bool maximize) override;

  // ----- Solve -----
  // Solve the problem using the parameter values specified.
  MPSolver::ResultStatus Solve(const MPSolverParameters& param) override;

  // ----- Model modifications and extraction -----
  // Resets extracted model
  void Reset() override;

  // Modify bounds.
  void SetVariableBounds(int var_index, double lb, double ub) override;
  void SetVariableInteger(int var_index, bool integer) override;
  void SetConstraintBounds(int row_index, double lb, double ub) override;

  // Add constraint incrementally.
  void AddRowConstraint(MPConstraint* const ct) override;
  // Add variable incrementally.
  void AddVariable(MPVariable* const var) override;
  // Change a coefficient in a constraint.
  void SetCoefficient(MPConstraint* const constraint,
                      const MPVariable* const variable, double new_value,
                      double old_value) override;
  // Clear a constraint from all its terms.
  void ClearConstraint(MPConstraint* const constraint) override;

  // Change a coefficient in the linear objective.
  void SetObjectiveCoefficient(const MPVariable* const variable,
                               double coefficient) override;
  // Change the constant term in the linear objective.
  void SetObjectiveOffset(double offset) override;
  // Clear the objective from all its terms.
  void ClearObjective() override;

  // ------ Query statistics on the solution and the solve ------
  // Number of simplex iterations
  int64_t iterations() const override;
  // Number of branch-and-bound nodes. Only available for discrete problems.
  int64_t nodes() const override;

  // Returns the basis status of a row.
  MPSolver::BasisStatus row_status(int constraint_index) const override;
  // Returns the basis status of a column.
  MPSolver::BasisStatus column_status(int variable_index) const override;

  // ----- Misc -----
  // Query problem type.
  bool IsContinuous() const override { return true; }
  bool IsLP() const override { return true; }
  bool IsMIP() const override { return false; }

  void ExtractNewVariables() override;
  void ExtractNewConstraints() override;
  void ExtractObjective() override;

  std::string SolverVersion() const override { return "Clp " CLP_VERSION; }

  void* underlying_solver() override {
    return reinterpret_cast<void*>(clp_.get());
  }

 private:
  // Create dummy variable to be able to create empty constraints.
  void CreateDummyVariableForEmptyConstraints();

  // Set all parameters in the underlying solver.
  void SetParameters(const MPSolverParameters& param) override;
  // Reset to their default value the parameters for which CLP has a
  // stateful API. To be called after the solve so that the next solve
  // starts from a clean parameter state.
  void ResetParameters();
  // Set each parameter in the underlying solver.
  void SetRelativeMipGap(double value) override;
  void SetPrimalTolerance(double value) override;
  void SetDualTolerance(double value) override;
  void SetPresolveMode(int value) override;
  void SetScalingMode(int value) override;
  void SetLpAlgorithm(int value) override;

  // Transforms basis status from CLP enum to MPSolver::BasisStatus.
  MPSolver::BasisStatus TransformCLPBasisStatus(
      ClpSimplex::Status clp_basis_status) const;

  std::unique_ptr<ClpSimplex> clp_;    // TODO(user) : remove pointer.
  std::unique_ptr<ClpSolve> options_;  // For parameter setting.
};

// ----- Solver -----

// Creates a LP/MIP instance with the specified name and minimization objective.
CLPInterface::CLPInterface(MPSolver* const solver)
    : MPSolverInterface(solver), clp_(new ClpSimplex), options_(new ClpSolve) {
  clp_->setStrParam(ClpProbName, solver_->name_);
  clp_->setOptimizationDirection(1);
}

CLPInterface::~CLPInterface() {}

void CLPInterface::Reset() {
  clp_ = std::make_unique<ClpSimplex>();
  clp_->setOptimizationDirection(maximize_ ? -1 : 1);
  ResetExtractionInformation();
}

// ------ Model modifications and extraction -----

namespace {
// Variable indices are shifted by 1 internally because of the dummy "objective
// offset" variable (with internal index 0).
int MPSolverVarIndexToClpVarIndex(int var_index) { return var_index + 1; }
}  // namespace

// Not cached
void CLPInterface::SetOptimizationDirection(bool maximize) {
  InvalidateSolutionSynchronization();
  clp_->setOptimizationDirection(maximize ? -1 : 1);
}

void CLPInterface::SetVariableBounds(int var_index, double lb, double ub) {
  InvalidateSolutionSynchronization();
  if (variable_is_extracted(var_index)) {
    // Not cached if the variable has been extracted
    DCHECK_LT(var_index, last_variable_index_);
    clp_->setColumnBounds(MPSolverVarIndexToClpVarIndex(var_index), lb, ub);
  } else {
    sync_status_ = MUST_RELOAD;
  }
}

// Ignore as CLP does not solve models with integer variables
void CLPInterface::SetVariableInteger(int var_index, bool integer) {}

void CLPInterface::SetConstraintBounds(int index, double lb, double ub) {
  InvalidateSolutionSynchronization();
  if (constraint_is_extracted(index)) {
    // Not cached if the row has been extracted
    DCHECK_LT(index, last_constraint_index_);
    clp_->setRowBounds(index, lb, ub);
  } else {
    sync_status_ = MUST_RELOAD;
  }
}

void CLPInterface::SetCoefficient(MPConstraint* const constraint,
                                  const MPVariable* const variable,
                                  double new_value, double old_value) {
  InvalidateSolutionSynchronization();
  if (constraint_is_extracted(constraint->index()) &&
      variable_is_extracted(variable->index())) {
    // The modification of the coefficient for an extracted row and
    // variable is not cached.
    DCHECK_LE(constraint->index(), last_constraint_index_);
    DCHECK_LE(variable->index(), last_variable_index_);
    clp_->modifyCoefficient(constraint->index(),
                            MPSolverVarIndexToClpVarIndex(variable->index()),
                            new_value);
  } else {
    // The modification of an unextracted row or variable is cached
    // and handled in ExtractModel.
    sync_status_ = MUST_RELOAD;
  }
}

// Not cached
void CLPInterface::ClearConstraint(MPConstraint* const constraint) {
  InvalidateSolutionSynchronization();
  // Constraint may not have been extracted yet.
  if (!constraint_is_extracted(constraint->index())) return;
  for (const auto& entry : constraint->coefficients_) {
    DCHECK(variable_is_extracted(entry.first->index()));
    clp_->modifyCoefficient(constraint->index(),
                            MPSolverVarIndexToClpVarIndex(entry.first->index()),
                            0.0);
  }
}

// Cached
void CLPInterface::SetObjectiveCoefficient(const MPVariable* const variable,
                                           double coefficient) {
  InvalidateSolutionSynchronization();
  if (variable_is_extracted(variable->index())) {
    clp_->setObjectiveCoefficient(
        MPSolverVarIndexToClpVarIndex(variable->index()), coefficient);
  } else {
    sync_status_ = MUST_RELOAD;
  }
}

// Cached
void CLPInterface::SetObjectiveOffset(double offset) {
  // Constant term. Use -offset instead of +offset because CLP does
  // not follow conventions.
  InvalidateSolutionSynchronization();
  clp_->setObjectiveOffset(-offset);
}

// Clear objective of all its terms.
void CLPInterface::ClearObjective() {
  InvalidateSolutionSynchronization();
  // Clear linear terms
  for (const auto& entry : solver_->objective_->coefficients_) {
    const int mpsolver_var_index = entry.first->index();
    // Variable may have not been extracted yet.
    if (!variable_is_extracted(mpsolver_var_index)) {
      DCHECK_NE(MODEL_SYNCHRONIZED, sync_status_);
    } else {
      clp_->setObjectiveCoefficient(
          MPSolverVarIndexToClpVarIndex(mpsolver_var_index), 0.0);
    }
  }
  // Clear constant term.
  clp_->setObjectiveOffset(0.0);
}

void CLPInterface::AddRowConstraint(MPConstraint* const ct) {
  sync_status_ = MUST_RELOAD;
}

void CLPInterface::AddVariable(MPVariable* const var) {
  sync_status_ = MUST_RELOAD;
}

void CLPInterface::CreateDummyVariableForEmptyConstraints() {
  clp_->setColumnBounds(kDummyVariableIndex, 0.0, 0.0);
  clp_->setObjectiveCoefficient(kDummyVariableIndex, 0.0);
  // Workaround for peculiar signature of setColumnName. Note that we do need
  // std::string here, and not 'string', which aren't the same as of 2013-12
  // (this will change later).
  std::string dummy = "dummy";  // We do need to create this temporary variable.
  clp_->setColumnName(kDummyVariableIndex, dummy);
}

// Define new variables and add them to existing constraints.
void CLPInterface::ExtractNewVariables() {
  // Define new variables
  int total_num_vars = solver_->variables_.size();
  if (total_num_vars > last_variable_index_) {
    if (last_variable_index_ == 0 && last_constraint_index_ == 0) {
      // Faster extraction when nothing has been extracted yet.
      clp_->resize(0, total_num_vars + 1);
      CreateDummyVariableForEmptyConstraints();
      for (int i = 0; i < total_num_vars; ++i) {
        MPVariable* const var = solver_->variables_[i];
        set_variable_as_extracted(i, true);
        if (!var->name().empty()) {
          std::string name = var->name();
          clp_->setColumnName(MPSolverVarIndexToClpVarIndex(i), name);
        }
        clp_->setColumnBounds(MPSolverVarIndexToClpVarIndex(i), var->lb(),
                              var->ub());
      }
    } else {
      // TODO(user): This could perhaps be made slightly faster by
      // iterating through old constraints, constructing by hand the
      // column-major representation of the addition to them and call
      // clp_->addColumns. But this is good enough for now.
      // Create new variables.
      for (int j = last_variable_index_; j < total_num_vars; ++j) {
        MPVariable* const var = solver_->variables_[j];
        DCHECK(!variable_is_extracted(j));
        set_variable_as_extracted(j, true);
        // The true objective coefficient will be set later in ExtractObjective.
        double tmp_obj_coef = 0.0;
        clp_->addColumn(0, nullptr, nullptr, var->lb(), var->ub(),
                        tmp_obj_coef);
        if (!var->name().empty()) {
          std::string name = var->name();
          clp_->setColumnName(MPSolverVarIndexToClpVarIndex(j), name);
        }
      }
      // Add new variables to existing constraints.
      for (int i = 0; i < last_constraint_index_; i++) {
        MPConstraint* const ct = solver_->constraints_[i];
        const int ct_index = ct->index();
        for (const auto& entry : ct->coefficients_) {
          const int mpsolver_var_index = entry.first->index();
          DCHECK(variable_is_extracted(mpsolver_var_index));
          if (mpsolver_var_index >= last_variable_index_) {
            clp_->modifyCoefficient(
                ct_index, MPSolverVarIndexToClpVarIndex(mpsolver_var_index),
                entry.second);
          }
        }
      }
    }
  }
}

// Define new constraints on old and new variables.
void CLPInterface::ExtractNewConstraints() {
  int total_num_rows = solver_->constraints_.size();
  if (last_constraint_index_ < total_num_rows) {
    // Find the length of the longest row.
    int max_row_length = 0;
    for (int i = last_constraint_index_; i < total_num_rows; ++i) {
      MPConstraint* const ct = solver_->constraints_[i];
      DCHECK(!constraint_is_extracted(ct->index()));
      set_constraint_as_extracted(ct->index(), true);
      if (ct->coefficients_.size() > max_row_length) {
        max_row_length = ct->coefficients_.size();
      }
    }
    // Make space for dummy variable.
    max_row_length = std::max(1, max_row_length);
    std::unique_ptr<int[]> indices(new int[max_row_length]);
    std::unique_ptr<double[]> coefs(new double[max_row_length]);
    CoinBuild build_object;
    // Add each new constraint.
    for (int i = last_constraint_index_; i < total_num_rows; ++i) {
      MPConstraint* const ct = solver_->constraints_[i];
      DCHECK(constraint_is_extracted(ct->index()));
      int size = ct->coefficients_.size();
      if (size == 0) {
        // Add dummy variable to be able to build the constraint.
        indices[0] = kDummyVariableIndex;
        coefs[0] = 1.0;
        size = 1;
      }
      int j = 0;
      for (const auto& entry : ct->coefficients_) {
        const int mpsolver_var_index = entry.first->index();
        DCHECK(variable_is_extracted(mpsolver_var_index));
        indices[j] = MPSolverVarIndexToClpVarIndex(mpsolver_var_index);
        coefs[j] = entry.second;
        j++;
      }
      build_object.addRow(size, indices.get(), coefs.get(), ct->lb(), ct->ub());
    }
    // Add and name the rows.
    clp_->addRows(build_object);
    for (int i = last_constraint_index_; i < total_num_rows; ++i) {
      MPConstraint* const ct = solver_->constraints_[i];
      if (!ct->name().empty()) {
        std::string name = ct->name();
        clp_->setRowName(ct->index(), name);
      }
    }
  }
}

void CLPInterface::ExtractObjective() {
  // Linear objective: set objective coefficients for all variables
  // (some might have been modified)
  for (const auto& entry : solver_->objective_->coefficients_) {
    clp_->setObjectiveCoefficient(
        MPSolverVarIndexToClpVarIndex(entry.first->index()), entry.second);
  }

  // Constant term. Use -offset instead of +offset because CLP does
  // not follow conventions.
  clp_->setObjectiveOffset(-solver_->Objective().offset());
}

// Extracts model and solve the LP/MIP. Returns the status of the search.
MPSolver::ResultStatus CLPInterface::Solve(const MPSolverParameters& param) {
  try {
    WallTimer timer;
    timer.Start();

    if (param.GetIntegerParam(MPSolverParameters::INCREMENTALITY) ==
        MPSolverParameters::INCREMENTALITY_OFF) {
      Reset();
    }

    // Set log level.
    CoinMessageHandler message_handler;
    clp_->passInMessageHandler(&message_handler);
    if (quiet_) {
      message_handler.setLogLevel(1, 0);
      clp_->setLogLevel(0);
    } else {
      message_handler.setLogLevel(1, 1);
      clp_->setLogLevel(1);
    }

    // Special case if the model is empty since CLP is not able to
    // handle this special case by itself.
    if (solver_->variables_.empty() && solver_->constraints_.empty()) {
      sync_status_ = SOLUTION_SYNCHRONIZED;
      result_status_ = MPSolver::OPTIMAL;
      objective_value_ = solver_->Objective().offset();
      return result_status_;
    }

    ExtractModel();
    VLOG(1) << absl::StrFormat("Model built in %.3f seconds.", timer.Get());

    // Time limit.
    if (solver_->time_limit() != 0) {
      VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
      clp_->setMaximumSeconds(solver_->time_limit_in_secs());
    } else {
      clp_->setMaximumSeconds(-1.0);
    }

    // Start from a fresh set of default parameters and set them to
    // specified values.
    options_ = std::make_unique<ClpSolve>();
    SetParameters(param);

    // Solve
    timer.Restart();
    clp_->initialSolve(*options_);
    VLOG(1) << absl::StrFormat("Solved in %.3f seconds.", timer.Get());

    // Check the status: optimal, infeasible, etc.
    int tmp_status = clp_->status();
    VLOG(1) << "clp result status: " << tmp_status;
    switch (tmp_status) {
      case CLP_SIMPLEX_FINISHED:
        result_status_ = MPSolver::OPTIMAL;
        break;
      case CLP_SIMPLEX_INFEASIBLE:
        result_status_ = MPSolver::INFEASIBLE;
        break;
      case CLP_SIMPLEX_UNBOUNDED:
        result_status_ = MPSolver::UNBOUNDED;
        break;
      case CLP_SIMPLEX_STOPPED:
        result_status_ = MPSolver::FEASIBLE;
        break;
      default:
        result_status_ = MPSolver::ABNORMAL;
        break;
    }

    if (result_status_ == MPSolver::OPTIMAL ||
        result_status_ == MPSolver::FEASIBLE) {
      // Get the results
      objective_value_ = clp_->objectiveValue();
      VLOG(1) << "objective=" << objective_value_;
      const double* const values = clp_->getColSolution();
      const double* const reduced_costs = clp_->getReducedCost();
      for (int i = 0; i < solver_->variables_.size(); ++i) {
        MPVariable* const var = solver_->variables_[i];
        const int clp_var_index = MPSolverVarIndexToClpVarIndex(var->index());
        const double val = values[clp_var_index];
        var->set_solution_value(val);
        VLOG(3) << var->name() << ": value = " << val;
        double reduced_cost = reduced_costs[clp_var_index];
        var->set_reduced_cost(reduced_cost);
        VLOG(4) << var->name() << ": reduced cost = " << reduced_cost;
      }
      const double* const dual_values = clp_->getRowPrice();
      for (int i = 0; i < solver_->constraints_.size(); ++i) {
        MPConstraint* const ct = solver_->constraints_[i];
        const int constraint_index = ct->index();
        const double dual_value = dual_values[constraint_index];
        ct->set_dual_value(dual_value);
        VLOG(4) << "row " << ct->index() << " dual value = " << dual_value;
      }
    }

    ResetParameters();
    sync_status_ = SOLUTION_SYNCHRONIZED;
    return result_status_;
  } catch (CoinError& e) {
    LOG(WARNING) << "Caught exception in Coin LP: " << e.message();
    result_status_ = MPSolver::ABNORMAL;
    return result_status_;
  }
}

MPSolver::BasisStatus CLPInterface::TransformCLPBasisStatus(
    ClpSimplex::Status clp_basis_status) const {
  switch (clp_basis_status) {
    case ClpSimplex::isFree:
      return MPSolver::FREE;
    case ClpSimplex::basic:
      return MPSolver::BASIC;
    case ClpSimplex::atUpperBound:
      return MPSolver::AT_UPPER_BOUND;
    case ClpSimplex::atLowerBound:
      return MPSolver::AT_LOWER_BOUND;
    case ClpSimplex::superBasic:
      return MPSolver::FREE;
    case ClpSimplex::isFixed:
      return MPSolver::FIXED_VALUE;
    default:
      LOG(FATAL) << "Unknown CLP basis status";
      return MPSolver::FREE;
  }
}

// ------ Query statistics on the solution and the solve ------

int64_t CLPInterface::iterations() const {
  if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations;
  return clp_->getIterationCount();
}

int64_t CLPInterface::nodes() const {
  LOG(DFATAL) << "Number of nodes only available for discrete problems";
  return kUnknownNumberOfNodes;
}

MPSolver::BasisStatus CLPInterface::row_status(int constraint_index) const {
  DCHECK_LE(0, constraint_index);
  DCHECK_GT(last_constraint_index_, constraint_index);
  const ClpSimplex::Status clp_basis_status =
      clp_->getRowStatus(constraint_index);
  return TransformCLPBasisStatus(clp_basis_status);
}

MPSolver::BasisStatus CLPInterface::column_status(int variable_index) const {
  DCHECK_LE(0, variable_index);
  DCHECK_GT(last_variable_index_, variable_index);
  const ClpSimplex::Status clp_basis_status =
      clp_->getColumnStatus(MPSolverVarIndexToClpVarIndex(variable_index));
  return TransformCLPBasisStatus(clp_basis_status);
}

// ------ Parameters ------

void CLPInterface::SetParameters(const MPSolverParameters& param) {
  SetCommonParameters(param);
}

void CLPInterface::ResetParameters() {
  clp_->setPrimalTolerance(MPSolverParameters::kDefaultPrimalTolerance);
  clp_->setDualTolerance(MPSolverParameters::kDefaultDualTolerance);
}

void CLPInterface::SetRelativeMipGap(double value) {
  LOG(WARNING) << "The relative MIP gap is only available "
               << "for discrete problems.";
}

void CLPInterface::SetPrimalTolerance(double value) {
  clp_->setPrimalTolerance(value);
}

void CLPInterface::SetDualTolerance(double value) {
  clp_->setDualTolerance(value);
}

void CLPInterface::SetPresolveMode(int value) {
  switch (value) {
    case MPSolverParameters::PRESOLVE_OFF: {
      options_->setPresolveType(ClpSolve::presolveOff);
      break;
    }
    case MPSolverParameters::PRESOLVE_ON: {
      options_->setPresolveType(ClpSolve::presolveOn);
      break;
    }
    default: {
      SetIntegerParamToUnsupportedValue(MPSolverParameters::PRESOLVE, value);
    }
  }
}

void CLPInterface::SetScalingMode(int value) {
  SetUnsupportedIntegerParam(MPSolverParameters::SCALING);
}

void CLPInterface::SetLpAlgorithm(int value) {
  switch (value) {
    case MPSolverParameters::DUAL: {
      options_->setSolveType(ClpSolve::useDual);
      break;
    }
    case MPSolverParameters::PRIMAL: {
      options_->setSolveType(ClpSolve::usePrimal);
      break;
    }
    case MPSolverParameters::BARRIER: {
      options_->setSolveType(ClpSolve::useBarrier);
      break;
    }
    default: {
      SetIntegerParamToUnsupportedValue(MPSolverParameters::LP_ALGORITHM,
                                        value);
    }
  }
}

MPSolverInterface* BuildCLPInterface(MPSolver* const solver) {
  return new CLPInterface(solver);
}

}  // namespace operations_research
#endif  // #if defined(USE_CBC) || defined(USE_CLP)
