//===========================================================================//
// This file is part of the DIP Solver Framework.                            //
//                                                                           //
// DIP is distributed under the Eclipse Public License as part of the        //
// COIN-OR repository (http://www.coin-or.org).                              //
//                                                                           //
// Author: Matthew Galati, SAS Institute Inc. (matthew.galati@sas.com)       //
//                                                                           //
// Conceptual Design: Matthew Galati, SAS Institute Inc.                     //
//                    Ted Ralphs, Lehigh University                          //
//                                                                           //
// Copyright (C) 2002-2015, Lehigh University, Matthew Galati, and Ted Ralphs//
// All Rights Reserved.                                                      //
//===========================================================================//

#ifndef DECOMP_MODEL_INCLUDED
#define DECOMP_MODEL_INCLUDED

//===========================================================================//
#include "UtilMacrosDecomp.h"
#include "DecompParam.h"
#include "DecompConstraintSet.h"
#include "DecompSolverResult.h"
#ifdef __DECOMP_IP_SYMPHONY__
#include "OsiSolverInterface.hpp"
#include "OsiSymSolverInterface.hpp"
#endif
//===========================================================================//
//naming convention - usually would do DecompModelXx, DecompModelYy
//===========================================================================//
class DecompAppModel {
protected:
   DecompConstraintSet* m_model;
   std::string           m_modelName;
   int                   m_blockId;

public:
   DecompConstraintSet* getModel()     const {
      return m_model;
   }
   const std::string&      getModelName() const {
      return m_modelName;
   }
   const int             getBlockId()   const {
      return m_blockId;
   }

public:
   void setModel    (DecompConstraintSet* model) {
      m_model = model;
   }
   void setModelName(const std::string modelName) {
      m_modelName = modelName;
   }
   void setBlockId  (const int blockId) {
      m_blockId = blockId;
   }

public:
   DecompAppModel(const DecompAppModel& appModel) {
      m_model     = appModel.m_model;
      m_modelName = appModel.m_modelName;
      m_blockId   = appModel.m_blockId;
   }
   DecompAppModel& operator=(const DecompAppModel& rhs) {
      m_model     = rhs.m_model;
      m_modelName = rhs.m_modelName;
      m_blockId   = rhs.m_blockId;
      return *this;
   }
   DecompAppModel() :
      m_model    (NULL),
      m_modelName(""),
      m_blockId  (0) {};
   DecompAppModel(DecompConstraintSet* model,
                  std::string                modelName,
                  int                   blockId) :
      m_model    (model),
      m_modelName(modelName),
      m_blockId  (blockId) {};
   virtual ~DecompAppModel() {}
};

//===========================================================================//
class DecompAlgoModel : public DecompAppModel {
private:
   OsiSolverInterface*   m_osi;
#ifdef __DECOMP_IP_SYMPHONY__
   OsiSymSolverInterface* osi_Sym;
#endif
   int                   m_numCols;
   int*                  m_colIndices;
   int                   m_counter;
   int                   m_ws_tag;
   CoinWarmStart*        m_ws;
public:

   inline void setCounter(const int num) {
      m_counter = num;
   }

   inline int getCounter() {
      return m_counter;
   }

   void setOsi(OsiSolverInterface* osi) {
      m_osi = osi;

      if (!m_colIndices) {
         //---
         //--- For use with (re-)setting the objective coefficients
         //---  setup an array of indices 0,...,n-1. This object assumes
         //---  that the number of columns stayed constant throughout.
         //---
         const int numCols = m_osi->getNumCols();
         m_numCols         = numCols;
         m_colIndices      = new int[numCols];

         if (!m_colIndices) {
            UtilExceptionMemory("setOsi", "DecompAlgoModel");
         }

         UtilIotaN(m_colIndices, numCols, 0);
      }
   }

   void setOsiObjCoeff(const double* objCoeff) {
      assert(m_osi);
      assert(m_colIndices);
      assert(m_numCols == m_osi->getNumCols());

      if (getModel()->isSparse()) {
         const DecompConstraintSet* model = getModel();
         const std::map<int, int>& origToSparse = model->getMapOrigToSparse();
         std::map<int, int>::const_iterator mcit;

         for (mcit = origToSparse.begin();
               mcit != origToSparse.end(); mcit++) {
            m_osi->setObjCoeff(mcit->second,           //sparse-index
                               objCoeff[mcit->first]); //original-index
         }
      } else
         m_osi->setObjCoeffSet(m_colIndices,
                               m_colIndices + m_numCols, objCoeff);
   }


   void setActiveColBounds(const double* colLB,
                           const double* colUB) {
      DecompConstraintSet* model         = getModel();
      std::vector<int>&          activeColumns = model->activeColumns;

      //---
      //--- if no active columns are set,  assume they are all active
      //---   for e.g., in the case of one block (or sparse)
      //---
      if (model->isSparse()) {
         const std::map<int, int>& origToSparse = model->getMapOrigToSparse();
         std::map<int, int>::const_iterator mcit;

         for (mcit  = origToSparse.begin();
               mcit != origToSparse.end(); mcit++) {
            m_osi->setColLower(mcit->second,        //sparse-index
                               colLB[mcit->first]); //original-index
            m_osi->setColUpper(mcit->second,        //sparse-index
                               colUB[mcit->first]); //original-index
            //printf("setColBounds orig:%d sparse:%d lb:%g ub:%g\n",
            //   mcit->first, mcit->second,
            //   colLB[mcit->first],
            //   colUB[mcit->first]);
         }
      } else {
         if (activeColumns.size()) {
            std::vector<int>::iterator vi;

            for (vi = activeColumns.begin(); vi != activeColumns.end(); vi++) {
               //printf("setColBounds i:%d to LB:%g UB:%g\n",
               //     *vi, colLB[*vi], colUB[*vi]);
               m_osi->setColBounds(*vi, colLB[*vi], colUB[*vi]);
            }
         } else {
            m_osi->setColLower(colLB);
            m_osi->setColUpper(colUB);
         }
      }
   }

public:
   OsiSolverInterface*   getOsi() const {
      return m_osi;
   }

public:
   void solveOsiAsIp(DecompSolverResult* result,
                     DecompParam&         param,
                     bool                 doExact,
                     bool                 doCutoff,
                     bool                 isRoot,
                     double               cutoff,
                     double               timeLimit);

   bool isPointFeasible(const double* x,
                        const bool     isXSparse  = false,
                        const int      logLevel   = 0,
                        const double   feasVarTol = 1.0e-5,
                        const double   feasConTol = 1.0e-4);

public:
   DecompAlgoModel(const DecompAppModel& appModel) :
      DecompAppModel(appModel),
      m_osi         (NULL),
#ifdef __DECOMP_IP_SYMPHONY__
      osi_Sym     (NULL),
#endif
      m_numCols     (0   ),
      m_colIndices  (NULL),
      m_counter    ( 0 ),
      m_ws_tag     ( 0 ),
      m_ws          (NULL)
   {};

   DecompAlgoModel& operator=(const DecompAppModel& rhs) {
      DecompAppModel::operator=(rhs);
      return *this;
   }

   DecompAlgoModel() :
      DecompAppModel(),
      m_osi         (NULL),
#ifdef __DECOMP_IP_SYMPHONY__
      osi_Sym       (NULL),
#endif
      m_numCols     (0   ),
      m_colIndices  (NULL),
      m_counter     (0),
      m_ws_tag      (0),
      m_ws          (NULL)
   {};
   DecompAlgoModel(DecompConstraintSet* model,
                   std::string                modelName,
                   int                   blockId) :
      DecompAppModel(model, modelName, blockId),
      m_osi         (NULL),
#ifdef __DECOMP_IP_SYMPHONY__
      osi_Sym       (NULL),
#endif
      m_numCols     (0   ),
      m_colIndices  (NULL),
      m_counter     (0),
      m_ws_tag      (0),
      m_ws          (NULL)
   {};
   ~DecompAlgoModel() {
      if (m_osi) {
         delete    m_osi;
      }

      if (m_colIndices) {
         delete [] m_colIndices;
      }
   }
};

#endif
