
/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
 *  Main authors:
 *     Gleb Belov <gleb.belov@monash.edu>
 */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#pragma once

#include <minizinc/solver_config.hh>
#include <minizinc/solver_instance_base.hh>
#include <minizinc/solvers/MIP/MIP_wrap.hh>
// CMakeLists.txt needs OSICBC_HOME defined
// #include <coin/CoinPackedVector.hpp>
// #include <coin/CoinPackedMatrix.hpp>
// #include <coin/CoinShallowPackedVector.hpp>
// #include <coin/CoinTime.hpp>
// #include <coin/OsiSolverInterface.hpp>
//  #include <coin/OsiCbcSolverInterface.hpp>
#include <coin/CbcModel.hpp>
#include <coin/OsiClpSolverInterface.hpp>
// #include <coin/CbcSolver.hpp>

class MIPosicbcWrapper : public MIPWrapper {
  OsiClpSolverInterface _osi;
  int _error;
  std::string _osicbcBuffer;  // [CBC_MESSAGEBUFSIZE];
                              //     string          osicbc_status_buffer; // [CBC_MESSAGEBUFSIZE];

  std::vector<double> _x;

  // To add constraints:
  std::vector<CoinPackedVector> _rows;
  std::vector<double>  // element,
      _rowlb, _rowub;

  std::unordered_map<VarId, double> _warmstart;  // this accumulates warmstart infos

public:
  class FactoryOptions {
  public:
    // NOLINTNEXTLINE(readability-convert-member-functions-to-static)
    bool processOption(int& i, std::vector<std::string>& argv, const std::string& workingDir) {
      return false;
    }
  };

  class Options : public MiniZinc::SolverInstanceBase::Options {
  public:
    int nThreads = 1;
    std::string sExportModel;
    int nTimeout = 0;
    long int nSolLimit = -1;
    double nWorkMemLimit = -1;
    std::string sReadParams;
    std::string sWriteParams;
    bool flagIntermediate = false;

    double absGap = -1;
    double relGap = 1e-8;
    double intTol = 1e-8;
    double objDiff = 1.0;

    std::vector<std::string> cbcCmdOptions;

    std::unordered_map<std::string, std::string> extraParams;

    bool processOption(int& i, std::vector<std::string>& argv,
                       const std::string& workingDir = std::string());
    static void printHelp(std::ostream& os);
  };

private:
  Options* _options = nullptr;

public:
  MIPosicbcWrapper(FactoryOptions& factoryOpt, Options* opt) : _options(opt) { openOSICBC(); }
  ~MIPosicbcWrapper() override { closeOSICBC(); }

  static std::string getDescription(FactoryOptions& factoryOpt,
                                    MiniZinc::SolverInstanceBase::Options* opt = nullptr);
  static std::string getVersion(FactoryOptions& factoryOpt,
                                MiniZinc::SolverInstanceBase::Options* opt = nullptr);
  static std::string getId();
  static std::string getName();
  static std::vector<std::string> getTags();
  static std::vector<std::string> getStdFlags();
  static std::vector<std::string> getRequiredFlags(FactoryOptions& factoryOpt) { return {}; };
  static std::vector<std::string> getFactoryFlags() { return {}; };

  static std::vector<MiniZinc::SolverConfig::ExtraFlag> getExtraFlags(FactoryOptions& factoryOpt);

  void printVersion(std::ostream&);
  void printHelp(std::ostream&);

  /// derived should overload and call the ancestor
  void openOSICBC() {}
  void closeOSICBC() {}

  /// actual adding new variables to the solver
  void doAddVars(size_t n, double* obj, double* lb, double* ub, VarType* vt,
                 std::string* names) override;

  void addPhase1Vars() override {
    if (fVerbose) {
      std::cerr << "  MIPosicbcWrapper: delaying physical addition of variables..." << std::endl;
    }
  }

  /// adding a linear constraint
  void addRow(int nnz, int* rmatind, double* rmatval, LinConType sense, double rhs,
              int mask = MaskConsType_Normal, const std::string& rowName = "") override;

  bool addWarmStart(const std::vector<VarId>& vars, const std::vector<double>& vals) override;

  void setObjSense(int s) override;  // +/-1 for max/min

  double getInfBound() override { return _osi.getInfinity(); }

  int getNCols() override {
    int nc = _osi.getNumCols();
    return nc != 0 ? nc : static_cast<int>(colLB.size());
  }
  int getNColsModel() override { return _osi.getNumCols(); }
  int getNRows() override {
    if (!_rowlb.empty()) {
      return static_cast<int>(_rowlb.size());
    }
    return _osi.getNumRows();
  }

  void solve() override;

protected:
  void wrapAssert(bool cond, const std::string& msg);

  /// Need to consider the 100 status codes in OSICBC and change with every version? TODO
  Status convertStatus(CbcModel* pModel);
  Status convertStatus();
};
