// Copyright (C) 2005, International Business Machines
// Corporation and others.  All Rights Reserved.
// This code is licensed under the terms of the Eclipse Public License (EPL).

#if defined(_MSC_VER)
// Turn off compiler warning about long names
#  pragma warning(disable:4786)
#endif

#include "CoinHelperFunctions.hpp"
#include "CoinWarmStartBasis.hpp"

#include "OsiConfig.h"
#include "CoinFinite.hpp"

#include "OsiSolverInterface.hpp"
#include "OsiSolverBranch.hpp"
#include <cassert>
#include <cmath>
#include <cfloat>
//#############################################################################
// Constructors / Destructor / Assignment
//#############################################################################

//-------------------------------------------------------------------
// Default Constructor
//-------------------------------------------------------------------
OsiSolverBranch::OsiSolverBranch () :
  indices_(NULL),
  bound_(NULL)
{
  memset(start_,0,sizeof(start_));
}

//-------------------------------------------------------------------
// Copy constructor
//-------------------------------------------------------------------
OsiSolverBranch::OsiSolverBranch (const OsiSolverBranch & rhs)
{
  memcpy(start_,rhs.start_,sizeof(start_));
  int size = start_[4];
  if (size) {
    indices_ = CoinCopyOfArray(rhs.indices_,size);
    bound_ = CoinCopyOfArray(rhs.bound_,size);
  } else {
    indices_=NULL;
    bound_=NULL;
  }
}

//-------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------
OsiSolverBranch::~OsiSolverBranch ()
{
  delete [] indices_;
  delete [] bound_;
}

//----------------------------------------------------------------
// Assignment operator
//-------------------------------------------------------------------
OsiSolverBranch &
OsiSolverBranch::operator=(const OsiSolverBranch& rhs)
{
  if (this != &rhs) {
    delete [] indices_;
    delete [] bound_;
    memcpy(start_,rhs.start_,sizeof(start_));
    int size = start_[4];
    if (size) {
      indices_ = CoinCopyOfArray(rhs.indices_,size);
      bound_ = CoinCopyOfArray(rhs.bound_,size);
    } else {
      indices_=NULL;
      bound_=NULL;
    }
  }
  return *this;
}

//-----------------------------------------------------------------------------
// add simple branch
//-----------------------------------------------------------------------------

void
OsiSolverBranch::addBranch(int iColumn, double value)
{
  delete [] indices_;
  delete [] bound_;
  indices_ = new int[2];
  bound_ = new double[2];
  indices_[0]=iColumn;
  indices_[1]=iColumn;
  start_[0]=0;
  start_[1]=0;
  start_[2]=1;
  bound_[0]=floor(value);
  start_[3]=2;
  bound_[1]=ceil(value);
  start_[4]=2;
  assert (bound_[0]!=bound_[1]);
}
//-----------------------------------------------------------------------------
// Add bounds - way =-1 is first , +1 is second
//-----------------------------------------------------------------------------

void
OsiSolverBranch::addBranch(int way,int numberTighterLower, const int * whichLower,
                           const double * newLower,
                           int numberTighterUpper, const int * whichUpper, const double * newUpper)
{
  assert (way==-1||way==1);
  int numberNew = numberTighterLower+numberTighterUpper;
  int base = way+1; // will be 0 or 2
  int numberNow = start_[4-base]-start_[2-base];
  int * tempI = new int[numberNow+numberNew];
  double * tempD = new double[numberNow+numberNew];
  int putNew = (way==-1) ? 0 : start_[2];
  int putNow = (way==-1) ? numberNew : 0;
  memcpy(tempI+putNow,indices_+start_[2-base],numberNow*sizeof(int));
  memcpy(tempD+putNow,bound_+start_[2-base],numberNow*sizeof(double));
  memcpy(tempI+putNew,whichLower,numberTighterLower*sizeof(int));
  memcpy(tempD+putNew,newLower,numberTighterLower*sizeof(double));
  putNew += numberTighterLower;
  memcpy(tempI+putNew,whichUpper,numberTighterUpper*sizeof(int));
  memcpy(tempD+putNew,newUpper,numberTighterUpper*sizeof(double));
  delete [] indices_;
  indices_ = tempI;
  delete [] bound_;
  bound_ = tempD;
  int numberOldLower = start_[3-base]-start_[2-base];
  int numberOldUpper = start_[4-base]-start_[3-base];
  start_[0]=0;
  if (way==-1) {
    start_[1] = numberTighterLower;
    start_[2] = start_[1] + numberTighterUpper;
    start_[3] = start_[2] + numberOldLower;
    start_[4] = start_[3] + numberOldUpper;
  } else {
    start_[1] = numberOldLower;
    start_[2] = start_[1] + numberOldUpper;
    start_[3] = start_[2] + numberTighterLower;
    start_[4] = start_[3] + numberTighterUpper;
  }
}
//-----------------------------------------------------------------------------
// Add bounds - way =-1 is first , +1 is second
//-----------------------------------------------------------------------------

void
OsiSolverBranch::addBranch(int way,int numberColumns, const double * oldLower,
                           const double * newLower2,
                           const double * oldUpper, const double * newUpper2)
{
  assert (way==-1||way==1);
  // find
  int i;
  int * whichLower = new int[numberColumns];
  double * newLower = new double[numberColumns];
  int numberTighterLower=0;
  for (i=0;i<numberColumns;i++) {
    if (newLower2[i]>oldLower[i]) {
      whichLower[numberTighterLower]=i;
      newLower[numberTighterLower++]=newLower2[i];
    }
  }
  int * whichUpper = new int[numberColumns];
  double * newUpper = new double[numberColumns];
  int numberTighterUpper=0;
  for (i=0;i<numberColumns;i++) {
    if (newUpper2[i]<oldUpper[i]) {
      whichUpper[numberTighterUpper]=i;
      newUpper[numberTighterUpper++]=newUpper2[i];
    }
  }
  int numberNew = numberTighterLower+numberTighterUpper;
  int base = way+1; // will be 0 or 2
  int numberNow = start_[4-base]-start_[2-base];
  int * tempI = new int[numberNow+numberNew];
  double * tempD = new double[numberNow+numberNew];
  int putNew = (way==-1) ? 0 : start_[2];
  int putNow = (way==-1) ? numberNew : 0;
  memcpy(tempI+putNow,indices_+start_[2-base],numberNow*sizeof(int));
  memcpy(tempD+putNow,bound_+start_[2-base],numberNow*sizeof(double));
  memcpy(tempI+putNew,whichLower,numberTighterLower*sizeof(int));
  memcpy(tempD+putNew,newLower,numberTighterLower*sizeof(double));
  putNew += numberTighterLower;
  memcpy(tempI+putNew,whichUpper,numberTighterUpper*sizeof(int));
  memcpy(tempD+putNew,newUpper,numberTighterUpper*sizeof(double));
  delete [] indices_;
  indices_ = tempI;
  delete [] bound_;
  bound_ = tempD;
  int numberOldLower = start_[3-base]-start_[2-base];
  int numberOldUpper = start_[4-base]-start_[3-base];
  start_[0]=0;
  if (way==-1) {
    start_[1] = numberTighterLower;
    start_[2] = start_[1] + numberTighterUpper;
    start_[3] = start_[2] + numberOldLower;
    start_[4] = start_[3] + numberOldUpper;
  } else {
    start_[1] = numberOldLower;
    start_[2] = start_[1] + numberOldUpper;
    start_[3] = start_[2] + numberTighterLower;
    start_[4] = start_[3] + numberTighterUpper;
  }
  delete [] whichLower;
  delete [] newLower;
  delete [] whichUpper;
  delete [] newUpper;
}
// Apply bounds
void
OsiSolverBranch::applyBounds(OsiSolverInterface & solver,int way) const
{
  int base = way+1;
  assert (way==-1||way==1);
  int numberColumns = solver.getNumCols();
  const double * columnLower = solver.getColLower();
  int i;
  for (i=start_[base];i<start_[base+1];i++) {
    int iColumn = indices_[i];
    if (iColumn<numberColumns) {
      double value = CoinMax(bound_[i],columnLower[iColumn]);
      solver.setColLower(iColumn,value);
    } else {
      int iRow = iColumn-numberColumns;
      const double * rowLower = solver.getRowLower();
      double value = CoinMax(bound_[i],rowLower[iRow]);
      solver.setRowLower(iRow,value);
    }
  }
  const double * columnUpper = solver.getColUpper();
  for (i=start_[base+1];i<start_[base+2];i++) {
    int iColumn = indices_[i];
    if (iColumn<numberColumns) {
      double value = CoinMin(bound_[i],columnUpper[iColumn]);
      solver.setColUpper(iColumn,value);
    } else {
      int iRow = iColumn-numberColumns;
      const double * rowUpper = solver.getRowUpper();
      double value = CoinMin(bound_[i],rowUpper[iRow]);
      solver.setRowUpper(iRow,value);
    }
  }
}
// Returns true if current solution satsifies one side of branch
bool
OsiSolverBranch::feasibleOneWay(const OsiSolverInterface & solver) const
{
  bool feasible = false;
  int numberColumns = solver.getNumCols();
  const double * columnLower = solver.getColLower();
  const double * columnUpper = solver.getColUpper();
  const double * columnSolution = solver.getColSolution();
  double primalTolerance;
  solver.getDblParam(OsiPrimalTolerance,primalTolerance);
  for (int base = 0; base<4; base +=2) {
    feasible=true;
    int i;
    for (i=start_[base];i<start_[base+1];i++) {
      int iColumn = indices_[i];
      if (iColumn<numberColumns) {
        double value = CoinMax(bound_[i],columnLower[iColumn]);
        if (columnSolution[iColumn]<value-primalTolerance) {
          feasible=false;
          break;
        }
      } else {
        abort(); // do later (other stuff messed up anyway - e.g. CBC)
      }
    }
    if (!feasible)
      break;
    for (i=start_[base+1];i<start_[base+2];i++) {
      int iColumn = indices_[i];
      if (iColumn<numberColumns) {
        double value = CoinMin(bound_[i],columnUpper[iColumn]);
        if (columnSolution[iColumn]>value+primalTolerance) {
          feasible=false;
          break;
        }
      } else {
        abort(); // do later (other stuff messed up anyway - e.g. CBC)
      }
    }
    if (feasible)
      break; // OK this way
  }
  return feasible;
}
//#############################################################################
// Constructors / Destructor / Assignment
//#############################################################################

//-------------------------------------------------------------------
// Default Constructor
//-------------------------------------------------------------------
OsiSolverResult::OsiSolverResult () :
  objectiveValue_(COIN_DBL_MAX),
  primalSolution_(NULL),
  dualSolution_(NULL)
 {
}

//-------------------------------------------------------------------
// Constructor from solver
//-------------------------------------------------------------------
OsiSolverResult::OsiSolverResult (const OsiSolverInterface & solver,const double * lowerBefore,
                                  const double * upperBefore) :
  objectiveValue_(COIN_DBL_MAX),
  primalSolution_(NULL),
  dualSolution_(NULL)
{
  if (solver.isProvenOptimal()&&!solver.isDualObjectiveLimitReached()) {
    objectiveValue_ = solver.getObjValue()*solver.getObjSense();
    CoinWarmStartBasis * basis = dynamic_cast<CoinWarmStartBasis *> (solver.getWarmStart());
    assert (basis);
    basis_ = * basis;
    delete basis;
    int numberRows = basis_.getNumArtificial();
    int numberColumns = basis_.getNumStructural();
    assert (numberColumns==solver.getNumCols());
    assert (numberRows==solver.getNumRows());
    primalSolution_ = CoinCopyOfArray(solver.getColSolution(),numberColumns);
    dualSolution_ = CoinCopyOfArray(solver.getRowPrice(),numberRows);
    fixed_.addBranch(-1,numberColumns,lowerBefore,solver.getColLower(),
                     upperBefore,solver.getColUpper());
  }
}

//-------------------------------------------------------------------
// Copy constructor
//-------------------------------------------------------------------
OsiSolverResult::OsiSolverResult (const OsiSolverResult & rhs)
{
  objectiveValue_ = rhs.objectiveValue_;
  basis_ = rhs.basis_;
  fixed_ = rhs.fixed_;
  int numberRows = basis_.getNumArtificial();
  int numberColumns = basis_.getNumStructural();
  if (numberColumns) {
    primalSolution_ = CoinCopyOfArray(rhs.primalSolution_,numberColumns);
    dualSolution_ = CoinCopyOfArray(rhs.dualSolution_,numberRows);
  } else {
    primalSolution_=NULL;
    dualSolution_=NULL;
  }
}

//-------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------
OsiSolverResult::~OsiSolverResult ()
{
  delete [] primalSolution_;
  delete [] dualSolution_;
}

//----------------------------------------------------------------
// Assignment operator
//-------------------------------------------------------------------
OsiSolverResult &
OsiSolverResult::operator=(const OsiSolverResult& rhs)
{
  if (this != &rhs) {
    delete [] primalSolution_;
    delete [] dualSolution_;
    objectiveValue_ = rhs.objectiveValue_;
    basis_ = rhs.basis_;
    fixed_ = rhs.fixed_;
    int numberRows = basis_.getNumArtificial();
    int numberColumns = basis_.getNumStructural();
    if (numberColumns) {
      primalSolution_ = CoinCopyOfArray(rhs.primalSolution_,numberColumns);
      dualSolution_ = CoinCopyOfArray(rhs.dualSolution_,numberRows);
    } else {
      primalSolution_=NULL;
      dualSolution_=NULL;
    }
  }
  return *this;
}
// Create result
void
OsiSolverResult::createResult(const OsiSolverInterface & solver, const double * lowerBefore,
                              const double * upperBefore)
{
  delete [] primalSolution_;
  delete [] dualSolution_;
  if (solver.isProvenOptimal()&&!solver.isDualObjectiveLimitReached()) {
    objectiveValue_ = solver.getObjValue()*solver.getObjSense();
    CoinWarmStartBasis * basis = dynamic_cast<CoinWarmStartBasis *> (solver.getWarmStart());
    assert (basis);
    basis_ = * basis;
    int numberRows = basis_.getNumArtificial();
    int numberColumns = basis_.getNumStructural();
    assert (numberColumns==solver.getNumCols());
    assert (numberRows==solver.getNumRows());
    primalSolution_ = CoinCopyOfArray(solver.getColSolution(),numberColumns);
    dualSolution_ = CoinCopyOfArray(solver.getRowPrice(),numberRows);
    fixed_.addBranch(-1,numberColumns,lowerBefore,solver.getColLower(),
                     upperBefore,solver.getColUpper());
  } else {
    // infeasible
    objectiveValue_ = COIN_DBL_MAX;
    basis_ = CoinWarmStartBasis();;
    primalSolution_=NULL;
    dualSolution_=NULL;
  }
}
// Restore result
void
OsiSolverResult::restoreResult(OsiSolverInterface & solver) const
{
  //solver.setObjValue(objectiveValue_)*solver.getObjSense();
  solver.setWarmStart(&basis_);
  solver.setColSolution(primalSolution_);
  solver.setRowPrice(dualSolution_);
  fixed_.applyBounds(solver,-1);
}
