/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*               This file is part of the program and library                */
/*    PaPILO --- Parallel Presolve for Integer and Linear Optimization       */
/*                                                                           */
/* Copyright (C) 2020-2026 Zuse Institute Berlin (ZIB)                       */
/*                                                                           */
/* 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.                                            */
/*                                                                           */
/* You should have received a copy of the Apache-2.0 license                 */
/* along with PaPILO; see the file LICENSE. If not visit scipopt.org.        */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef _PAPILO_INTERFACES_SOPLEX_INTERFACE_HPP_
#define _PAPILO_INTERFACES_SOPLEX_INTERFACE_HPP_

#include "papilo/core/Problem.hpp"
#include "papilo/interfaces/SolverInterface.hpp"
#include "papilo/misc/String.hpp"
#include "papilo/misc/Vec.hpp"
#include "soplex.h"
#include <cassert>
#include <stdexcept>
#include <type_traits>

namespace papilo
{

template <typename REAL>
class SoplexInterface : public SolverInterface<REAL>
{
 private:
   soplex::SoPlex spx;

 public:
   void
   readSettings( const String& file ) override
   {
      if( !spx.loadSettingsFile( file.c_str() ) )
         this->status = SolverStatus::kError;
   }

   soplex::SoPlex&
   getSoPlex()
   {
      return spx;
   }

   void
   setTimeLimit( double tlim ) override
   {
      using namespace soplex;

      spx.setIntParam( SoPlex::TIMER, SoPlex::TIMER_WALLCLOCK );
      spx.setRealParam( SoPlex::TIMELIMIT, Real( tlim ) );
   }

   void
   setUp( const Problem<REAL>& problem, const Vec<int>& row_maps,
          const Vec<int>& col_maps ) override
   {
      using namespace soplex;

      assert( problem.getSymmetries().symmetries.empty() );
      int ncols = problem.getNCols();
      int nrows = problem.getNRows();
      const VariableDomains<REAL>& domains = problem.getVariableDomains();
      const Objective<REAL>& obj = problem.getObjective();
      const auto& consMatrix = problem.getConstraintMatrix();
      const auto& lhs_values = consMatrix.getLeftHandSides();
      const auto& rhs_values = consMatrix.getRightHandSides();
      const auto& rflags = problem.getRowFlags();

      /* set the objective sense and offset */
      spx.setIntParam( SoPlex::OBJSENSE, SoPlex::OBJSENSE_MINIMIZE );

      if( obj.offset != 0 )
         spx.setRealParam( SoPlex::OBJ_OFFSET, Real( obj.offset ) );

      LPRowSet rows( nrows );
      LPColSet cols( ncols );
      DSVector vec( ncols );
      for( int i = 0; i < nrows; ++i )
      {
         Real lhs = rflags[i].test( RowFlag::kLhsInf ) ? -infinity
                                                       : Real( lhs_values[i] );
         Real rhs = rflags[i].test( RowFlag::kRhsInf ) ? infinity
                                                       : Real( rhs_values[i] );

         rows.add( lhs, vec, rhs );
      }

      spx.addRowsReal( rows );

      for( int i = 0; i < ncols; ++i )
      {
         assert(!domains.flags[i].test( ColFlag::kInactive ));
         Real lb = domains.flags[i].test( ColFlag::kLbInf )
                       ? -infinity
                       : Real( domains.lower_bounds[i] );
         Real ub = domains.flags[i].test( ColFlag::kUbInf )
                       ? infinity
                       : Real( domains.upper_bounds[i] );
         assert(lb < ub);

         auto colvec = consMatrix.getColumnCoefficients( i );

         int collen = colvec.getLength();
         const int* colrows = colvec.getIndices();
         const REAL* colvals = colvec.getValues();

         vec.clear();

         if( std::is_same<REAL, Real>::value )
         {
            vec.add( collen, colrows, (const Real*)colvals );
         }
         else
         {
            for( int j = 0; j != collen; ++j )
               vec.add( colrows[j], Real( colvals[j] ) );
         }

         cols.add( Real( obj.coefficients[i] ), lb, vec, ub );
      }

      spx.addColsReal( cols );
   }

   void
   setUp( const Problem<REAL>& problem, const Vec<int>& row_maps,
          const Vec<int>& col_maps, const Components& components,
          const ComponentInfo& component ) override
   {
      using namespace soplex;
      assert( problem.getSymmetries().symmetries.empty() );
      const VariableDomains<REAL>& domains = problem.getVariableDomains();
      const Objective<REAL>& obj = problem.getObjective();
      const auto& consMatrix = problem.getConstraintMatrix();
      const auto& lhs_values = consMatrix.getLeftHandSides();
      const auto& rhs_values = consMatrix.getRightHandSides();
      const auto& rflags = problem.getRowFlags();
      const int* rowset = components.getComponentsRows( component.componentid );
      const int* colset = components.getComponentsCols( component.componentid );
      int numrows = components.getComponentsNumRows( component.componentid );
      int numcols = components.getComponentsNumCols( component.componentid );

      /* set the objective sense and offset */
      spx.setIntParam( SoPlex::OBJSENSE, SoPlex::OBJSENSE_MINIMIZE );

      LPRowSet rows( numrows );
      LPColSet cols( numcols );
      DSVector vec( numcols );
      for( int i = 0; i != numrows; ++i )
      {
         int row = rowset[i];

         assert( components.getRowComponentIdx( row ) == i );

         Real lhs = rflags[row].test( RowFlag::kLhsInf )
                        ? -infinity
                        : Real( lhs_values[row] );
         Real rhs = rflags[row].test( RowFlag::kRhsInf )
                        ? infinity
                        : Real( rhs_values[row] );

         rows.add( lhs, vec, rhs );
      }

      spx.addRowsReal( rows );

      for( int i = 0; i != numcols; ++i )
      {
         int col = colset[i];
         assert(components.getColComponentIdx( col ) == i);
         assert(!domains.flags[col].test( ColFlag::kInactive ));
         Real lb = domains.flags[col].test( ColFlag::kLbInf )
                       ? -infinity
                       : Real( domains.lower_bounds[col] );
         Real ub = domains.flags[col].test( ColFlag::kUbInf )
                       ? infinity
                       : Real( domains.upper_bounds[col] );
         assert(lb < ub);

         auto colvec = consMatrix.getColumnCoefficients( col );

         int collen = colvec.getLength();
         const int* colrows = colvec.getIndices();
         const REAL* colvals = colvec.getValues();

         vec.clear();

         for( int j = 0; j != collen; ++j )
            vec.add( components.getRowComponentIdx( colrows[j] ),
                     Real( colvals[j] ) );

         cols.add( Real( obj.coefficients[col] ), lb, vec, ub );
      }

      spx.addColsReal( cols );
   }

   void
   solve() override
   {
      using namespace soplex;

      assert( this->status != SolverStatus::kError );

      spx.setSettings( spx.settings() );

      SPxSolver::Status stat = spx.optimize();

      switch( stat )
      {
      default:
         this->status = SolverStatus::kError;
         return;
      case SPxSolver::Status::INForUNBD:
         this->status = SolverStatus::kUnbndOrInfeas;
         return;
      case SPxSolver::Status::INFEASIBLE:
         this->status = SolverStatus::kInfeasible;
         return;
      case SPxSolver::Status::UNBOUNDED:
         this->status = SolverStatus::kUnbounded;
         return;
      case SPxSolver::Status::ABORT_CYCLING:
         this->status = SolverStatus::kInterrupted;
         return;
      case SPxSolver::Status::OPTIMAL_UNSCALED_VIOLATIONS:
      case SPxSolver::Status::OPTIMAL:
         this->status = SolverStatus::kOptimal;
      }
   }

   void
   setVerbosity( VerbosityLevel verbosity ) override
   {
      using namespace soplex;

      switch( verbosity )
      {
      case VerbosityLevel::kQuiet:
      case VerbosityLevel::kError:
         spx.setIntParam( SoPlex::VERBOSITY, SoPlex::VERBOSITY_ERROR );
         break;
      case VerbosityLevel::kWarning:
         spx.setIntParam( SoPlex::VERBOSITY, SoPlex::VERBOSITY_WARNING );
         break;
      case VerbosityLevel::kInfo:
         spx.setIntParam( SoPlex::VERBOSITY, SoPlex::VERBOSITY_NORMAL );
         break;
      case VerbosityLevel::kDetailed:
         spx.setIntParam( SoPlex::VERBOSITY, SoPlex::VERBOSITY_HIGH );
      }
   }

   REAL
   getDualBound() override
   {
      if( spx.hasPrimal() )
         return spx.objValueReal();
      else
         return -soplex::infinity;
   }

   bool
   getSolution( Solution<REAL>& sol, PostsolveStorage<REAL>& postsolve ) override
   {
      Vec<soplex::Real> buffer;

      int numcols = spx.numColsReal();
      buffer.resize( numcols );

      if( !spx.getPrimalReal( buffer.data(), numcols ) )
         return false;

      sol.primal.resize( numcols );
      for( int i = 0; i != numcols; ++i )
         sol.primal[i] = REAL( buffer[i] );

      if( sol.type == SolutionType::kPrimal )
         return true;

      if( !spx.getRedCostReal( buffer.data(), numcols ) )
         return false;

      sol.reducedCosts.resize( numcols );
      for( int i = 0; i != numcols; ++i )
         sol.reducedCosts[i] = REAL( buffer[i] );

      int numrows = spx.numRowsReal();

      buffer.resize( numrows );
      if( !spx.getDualReal( buffer.data(), numrows ) )
         return false;

      sol.dual.resize( numrows );
      for( int i = 0; i != numrows; ++i )
         sol.dual[i] = REAL( buffer[i] );
      sol.basisAvailabe = true;

      sol.varBasisStatus.resize( numcols, VarBasisStatus::UNDEFINED );
      for( int i = 0; i < numcols; ++i )
         switch( spx.basisColStatus( i ) )
         {
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::BASIC:
            sol.varBasisStatus[i] = VarBasisStatus::BASIC;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ON_LOWER:
            sol.varBasisStatus[i] = VarBasisStatus::ON_LOWER;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ON_UPPER:
            sol.varBasisStatus[i] = VarBasisStatus::ON_UPPER;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::FIXED:
            sol.varBasisStatus[i] = VarBasisStatus::FIXED;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ZERO:
            sol.varBasisStatus[i] = VarBasisStatus::ZERO;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::UNDEFINED:
            sol.varBasisStatus[i] = VarBasisStatus::UNDEFINED;
            break;
         }

      sol.rowBasisStatus.resize( numrows, VarBasisStatus::UNDEFINED );for( int i = 0; i < numrows; ++i )
         switch( spx.basisRowStatus( i ) )
         {
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::BASIC:
            sol.rowBasisStatus[i] = VarBasisStatus::BASIC;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ON_LOWER:
            sol.rowBasisStatus[i] = VarBasisStatus::ON_LOWER;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ON_UPPER:
            sol.rowBasisStatus[i] = VarBasisStatus::ON_UPPER;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::FIXED:
            sol.rowBasisStatus[i] = VarBasisStatus::FIXED;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ZERO:
            sol.rowBasisStatus[i] = VarBasisStatus::ZERO;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::UNDEFINED:
            sol.rowBasisStatus[i] = VarBasisStatus::UNDEFINED;
            break;
         }

      return true;
   }

   bool
   getSolution( const Components& components, int component,
                Solution<REAL>& sol ) override
   {
      Vec<soplex::Real> buffer;

      int numcols = spx.numColsReal();
      assert( components.getComponentsNumCols( component ) ==
              spx.numColsReal() );

      buffer.resize( numcols );
      if( !spx.getPrimalReal( buffer.data(), numcols ) )
         return false;

      const int* compcols = components.getComponentsCols( component );
      for( int i = 0; i != numcols; ++i )
         sol.primal[compcols[i]] = REAL( buffer[i] );

      if( sol.type == SolutionType::kPrimal )
         return true;

      if( !spx.getRedCostReal( buffer.data(), numcols ) )
         return false;

      for( int i = 0; i != numcols; ++i )
         sol.reducedCosts[compcols[i]] = REAL( buffer[i] );

      int numrows = spx.numRowsReal();
      buffer.resize( numrows );
      const int* comprows = components.getComponentsRows( component );

      if( !spx.getDualReal( buffer.data(), numrows ) )
         return false;

      for( int i = 0; i != numrows; ++i )
         sol.dual[comprows[i]] = REAL( buffer[i] );

      for( int i = 0; i < numcols; ++i )
         switch( spx.basisColStatus( i ) )
         {
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::BASIC:
            sol.varBasisStatus[comprows[i]] = VarBasisStatus::BASIC;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ON_LOWER:
            sol.varBasisStatus[comprows[i]] = VarBasisStatus::ON_LOWER;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ON_UPPER:
            sol.varBasisStatus[comprows[i]] = VarBasisStatus::ON_UPPER;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::FIXED:
            sol.varBasisStatus[comprows[i]] = VarBasisStatus::FIXED;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ZERO:
            sol.varBasisStatus[comprows[i]] = VarBasisStatus::ZERO;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::UNDEFINED:
            sol.varBasisStatus[comprows[i]] = VarBasisStatus::UNDEFINED;
            break;
         }

      for( int i = 0; i < numrows; ++i )
         switch( spx.basisRowStatus( i ) )
         {
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::BASIC:
            sol.rowBasisStatus[comprows[i]] = VarBasisStatus::BASIC;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ON_LOWER:
            sol.rowBasisStatus[comprows[i]] = VarBasisStatus::ON_LOWER;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ON_UPPER:
            sol.rowBasisStatus[comprows[i]] = VarBasisStatus::ON_UPPER;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::FIXED:
            sol.rowBasisStatus[comprows[i]] = VarBasisStatus::FIXED;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::ZERO:
            sol.rowBasisStatus[comprows[i]] = VarBasisStatus::ZERO;
            break;
         case soplex::SPxSolverBase<soplex::Real>::VarStatus::UNDEFINED:
            sol.rowBasisStatus[comprows[i]] = VarBasisStatus::UNDEFINED;
            break;
         }
      return true;
   }

   void
   addParameters( ParameterSet& paramSet ) override
   {
      using namespace soplex;

      SoPlex::Settings& settings =
          const_cast<SoPlex::Settings&>( spx.settings() );

      for( int i = 0; i != SoPlex::BOOLPARAM_COUNT; ++i )
      {
         paramSet.addParameter( settings.boolParam.name[i].c_str(),
                                settings.boolParam.description[i].c_str(),
                                settings._boolParamValues[i] );
      }

      for( int i = 0; i != SoPlex::INTPARAM_COUNT; ++i )
      {
         paramSet.addParameter( settings.intParam.name[i].c_str(),
                                settings.intParam.description[i].c_str(),
                                settings._intParamValues[i],
                                settings.intParam.lower[i],
                                settings.intParam.upper[i] );
      }

      for( int i = 0; i != SoPlex::REALPARAM_COUNT; ++i )
      {
         paramSet.addParameter( settings.realParam.name[i].c_str(),
                                settings.realParam.description[i].c_str(),
                                settings._realParamValues[i],
                                settings.realParam.lower[i],
                                settings.realParam.upper[i] );
      }
   }

   SolverType
   getType() override
   {
      return SolverType::LP;
   }

   String
   getName() override
   {
      return "SoPlex";
   }

   bool
   is_dual_solution_available() override
   {
      return true;
   }

   void
   printDetails() override
   {
      spx.printStatistics( std::cout );
   }
};

template <typename REAL>
class SoplexFactory : public SolverFactory<REAL>
{
   void ( *soplexsetup )( soplex::SoPlex& soplex, void* usrdata );
   void* soplexsetup_usrdata;

   SoplexFactory( void ( *soplexsetup_ )( soplex::SoPlex& soplex,
                                         void* usrdata ),
                  void* soplexsetup_usrdata_ )
       : soplexsetup( soplexsetup_ ), soplexsetup_usrdata( soplexsetup_usrdata_ )
   {
   }

 public:
   std::unique_ptr<SolverInterface<REAL>>
   newSolver( VerbosityLevel verbosity ) const override
   {
      auto soplex =
          std::unique_ptr<SolverInterface<REAL>>( new SoplexInterface<REAL>() );

      // set verbosity already before the setup function call
      soplex->setVerbosity( verbosity );

      if( soplexsetup != nullptr )
         soplexsetup(
             static_cast<SoplexInterface<REAL>*>( soplex.get() )->getSoPlex(),
             soplexsetup_usrdata );

      // set verbosity again in case the setup function altered it
      soplex->setVerbosity( verbosity );

      return soplex;
   }

   virtual void
   add_parameters( ParameterSet& parameter ) const override
   {
   }

   static std::unique_ptr<SolverFactory<REAL>>
   create( void ( *soplexsetup )( soplex::SoPlex& soplex,
                                  void* usrdata ) = nullptr,
           void* soplexsetup_usrdata = nullptr )
   {
      return std::unique_ptr<SolverFactory<REAL>>(
          new SoplexFactory<REAL>( soplexsetup, soplexsetup_usrdata ) );
   }
};

} // namespace papilo

#endif
