/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*               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_MISC_NUM_HPP_
#define _PAPILO_MISC_NUM_HPP_

#include "papilo/misc/MultiPrecision.hpp"
#include "papilo/misc/String.hpp"

namespace papilo
{

template <typename T>
struct num_traits
{
   constexpr static bool is_rational =
       std::numeric_limits<T>::is_specialized == true &&
       std::numeric_limits<T>::is_integer == false &&
       std::numeric_limits<T>::is_exact == true &&
       std::numeric_limits<T>::min_exponent == 0 &&
       std::numeric_limits<T>::max_exponent == 0 &&
       std::numeric_limits<T>::min_exponent10 == 0 &&
       std::numeric_limits<T>::max_exponent10 == 0;

   constexpr static bool is_floating_point =
       std::numeric_limits<T>::is_specialized == true &&
       std::numeric_limits<T>::is_integer == false &&
       std::numeric_limits<T>::is_exact == false &&
       std::numeric_limits<T>::min_exponent != 0 &&
       std::numeric_limits<T>::max_exponent != 0 &&
       std::numeric_limits<T>::min_exponent10 != 0 &&
       std::numeric_limits<T>::max_exponent10 != 0;

   constexpr static bool is_integer =
       std::numeric_limits<T>::is_specialized == true &&
       std::numeric_limits<T>::is_integer == true &&
       std::numeric_limits<T>::is_exact == true &&
       std::numeric_limits<T>::min_exponent == 0 &&
       std::numeric_limits<T>::max_exponent == 0 &&
       std::numeric_limits<T>::min_exponent10 == 0 &&
       std::numeric_limits<T>::max_exponent10 == 0;
};

template <typename T>
class provides_numerator_and_denominator_overloads
{
   struct no
   {
   };

   template <typename T2>
   static decltype( numerator( std::declval<T2>() ) )
   test_numerator( int );

   template <typename T2>
   static decltype( numerator( std::declval<T2>() ) )
   test_denominator( int );

   template <typename T2>
   static no
   test_numerator( ... );

   template <typename T2>
   static no
   test_denominator( ... );

 public:
   constexpr static bool value =
       num_traits<decltype( test_numerator<T>( 0 ) )>::is_integer &&
       num_traits<decltype( test_denominator<T>( 0 ) )>::is_integer;
};

template <typename Rational,
          typename std::enable_if<
              num_traits<Rational>::is_rational &&
                  provides_numerator_and_denominator_overloads<Rational>::value,
              int>::type = 0>
Rational
floor( const Rational& x, ... )
{
   if( x >= 0 )
      return numerator( x ) / denominator( x );

   if( numerator( x ) < 0 )
      return -1 + ( numerator( x ) + 1 ) / denominator( x );

   return -1 + ( numerator( x ) - 1 ) / denominator( x );
}

template <typename Rational,
          typename std::enable_if<
              num_traits<Rational>::is_rational &&
                  provides_numerator_and_denominator_overloads<Rational>::value,
              int>::type = 0>
Rational
ceil( const Rational& x, ... )
{
   if( x <= 0 )
      return numerator( x ) / denominator( x );

   if( numerator( x ) < 0 )
      return 1 + ( numerator( x ) + 1 ) / denominator( x );

   return 1 + ( numerator( x ) - 1 ) / denominator( x );
}

using std::abs;
using std::ceil;
using std::copysign;
using std::exp;
using std::floor;
using std::frexp;
using std::ldexp;
using std::log;
using std::log2;
using std::pow;
using std::sqrt;

template <typename REAL, bool exact = std::numeric_limits<REAL>::is_exact>
struct DefaultTolerances;

template <typename REAL>
struct DefaultTolerances<REAL, false>
{
   static constexpr REAL
   epsilon()
   {
      return feasTol() *
             pow( REAL{ 10 }, -( std::numeric_limits<REAL>::digits10 / 4 ) );
   }

   static constexpr REAL
   feasTol()
   {
      return pow( REAL{ 10 },
                  -( std::numeric_limits<REAL>::digits10 / 2 - 1 ) );
   }
};

template <typename REAL>
struct DefaultTolerances<REAL, true>
{
   static constexpr REAL
   epsilon()
   {
      return REAL{ 0 };
   }

   static constexpr REAL
   feasTol()
   {
      return REAL{ 0 };
   }
};

template <typename REAL>
class Num
{
 public:
   Num()
       : epsilon( REAL{ 1e-9 } ), feastol( REAL{ 1e-6 } ),
         hugeval( REAL{ 1e8 } ), useabsfeas( true )
   {
   }

   template <typename R>
   static constexpr REAL
   round( const R& x )
   {
      return floor( REAL( x + REAL( 0.5 ) ) );
   }

   template <typename R1, typename R2>
   bool
   isEq( const R1& a, const R2& b ) const
   {
      return abs( a - b ) <= epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasEq( const R1& a, const R2& b ) const
   {
      return useabsfeas ? isFeasAbsEq(a, b) : isFeasRelEq(a, b);
   }

   template <typename R1, typename R2>
   bool
   isGE( const R1& a, const R2& b ) const
   {
      return a - b >= -epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasGE( const R1& a, const R2& b ) const
   {
      return useabsfeas ? isFeasAbsGE(a, b) : isFeasRelGE(a, b);
   }

   template <typename R1, typename R2>
   bool
   isLE( const R1& a, const R2& b ) const
   {
      return a - b <= epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasLE( const R1& a, const R2& b ) const
   {
      return useabsfeas ? isFeasAbsLE(a, b) : isFeasRelLE( a, b);
   }

   template <typename R1, typename R2>
   bool
   isGT( const R1& a, const R2& b ) const
   {
      return a - b > epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasGT( const R1& a, const R2& b ) const
   {
      return useabsfeas ? isFeasAbsGT(a, b) : isFeasRelGT(a, b);
   }

   template <typename R1, typename R2>
   bool
   isLT( const R1& a, const R2& b ) const
   {
      return a - b < -epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasLT( const R1& a, const R2& b ) const
   {
      return useabsfeas ? isFeasAbsLT(a, b) : isFeasRelLT(a, b);
   }

   template <typename R1, typename R2>
   static REAL
   max( const R1& a, const R2& b )
   {
      return a > b ? REAL( a ) : REAL( b );
   }

   template <typename R1, typename R2>
   static REAL
   min( const R1& a, const R2& b )
   {
      return a < b ? REAL( a ) : REAL( b );
   }

   template <typename R1, typename R2>
   static REAL
   relDiff( const R1& a, const R2& b )
   {
      return ( a - b ) / max( max( abs( a ), abs( b ) ), 1 );
   }

   template <typename R1, typename R2>
   bool
   isRelEq( const R1& a, const R2& b ) const
   {
      return abs( relDiff( a, b ) ) <= epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasAbsEq( const R1& a, const R2& b ) const
   {
      return abs( a - b ) <= feastol;
   }

   template <typename R1, typename R2>
   bool
   isFeasRelEq( const R1& a, const R2& b ) const
   {
      return abs( relDiff( a, b ) ) <= feastol;
   }

   template <typename R1, typename R2>
   bool
   isRelGE( const R1& a, const R2& b ) const
   {
      return relDiff( a, b ) >= -epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasAbsGE( const R1& a, const R2& b ) const
   {
      return a - b >= -feastol;
   }

   template <typename R1, typename R2>
   bool
   isFeasRelGE( const R1& a, const R2& b ) const
   {
      return relDiff( a, b ) >= -feastol;
   }

   template <typename R1, typename R2>
   bool
   isRelLE( const R1& a, const R2& b ) const
   {
      return relDiff( a, b ) <= epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasAbsLE( const R1& a, const R2& b ) const
   {
      return a - b <= feastol;
   }

   template <typename R1, typename R2>
   bool
   isFeasRelLE( const R1& a, const R2& b ) const
   {
      return relDiff( a, b ) <= feastol;
   }

   template <typename R1, typename R2>
   bool
   isRelGT( const R1& a, const R2& b ) const
   {
      return relDiff( a, b ) > epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasAbsGT( const R1& a, const R2& b ) const
   {
      return a - b > feastol;
   }

   template <typename R1, typename R2>
   bool
   isFeasRelGT( const R1& a, const R2& b ) const
   {
      return relDiff( a, b ) > feastol;
   }

   template <typename R1, typename R2>
   bool
   isRelLT( const R1& a, const R2& b ) const
   {
      return relDiff( a, b ) < -epsilon;
   }

   template <typename R1, typename R2>
   bool
   isFeasAbsLT( const R1& a, const R2& b ) const
   {
      return a - b < -feastol;
   }

   template <typename R1, typename R2>
   bool
   isFeasRelLT( const R1& a, const R2& b ) const
   {
      return relDiff( a, b ) < -feastol;
   }

   template <typename R1, typename R2>
   static constexpr bool
   isSafeEq( const R1& a, const R2& b )
   {
      return !num_traits<REAL>::is_floating_point ||
             ( abs( relDiff( a, b ) ) <=
               ( 1024 * std::numeric_limits<REAL>::epsilon() ) );
   }

   template <typename R1, typename R2>
   static constexpr bool
   isSafeGE( const R1& a, const R2& b )
   {
      return !num_traits<REAL>::is_floating_point ||
             ( relDiff( a, b ) >=
               -( 1024 * std::numeric_limits<REAL>::epsilon() ) );
   }

   template <typename R1, typename R2>
   static constexpr bool
   isSafeLE( const R1& a, const R2& b )
   {
      return num_traits<REAL>::is_floating_point
                 ? ( relDiff( a, b ) <=
                     ( 1024 * std::numeric_limits<REAL>::epsilon() ) )
                 : true;
   }

   template <typename R1, typename R2>
   static constexpr bool
   isSafeGT( const R1& a, const R2& b )
   {
      return !num_traits<REAL>::is_floating_point ||
             ( relDiff( a, b ) >
               ( 1024 * std::numeric_limits<REAL>::epsilon() ) );
   }

   template <typename R1, typename R2>
   bool static constexpr isSafeLT( const R1& a, const R2& b )
   {
      return !num_traits<REAL>::is_floating_point ||
             ( relDiff( a, b ) <
               -( 1024 * std::numeric_limits<REAL>::epsilon() ) );
   }

   template <typename R>
   REAL
   feasCeil( const R& a ) const
   {
      return ceil( REAL( a - feastol ) );
   }

   template <typename R>
   REAL
   epsCeil( const R& a ) const
   {
      return ceil( REAL( a - epsilon ) );
   }

   template <typename R>
   REAL
   feasFloor( const R& a ) const
   {
      return floor( REAL( a + feastol ) );
   }

   template <typename R>
   REAL
   epsFloor( const R& a ) const
   {
      return floor( REAL( a + epsilon ) );
   }

   template <typename R>
   bool
   isIntegral( const R& a ) const
   {
      return isEq( a, round( a ) );
   }

   template <typename R>
   bool
   isFeasIntegral( const R& a ) const
   {
      return isFeasEq( a, round( a ) );
   }

   template <typename R>
   bool
   isZero( const R& a ) const
   {
      return abs( a ) <= epsilon;
   }

   template <typename R>
   bool
   isFeasZero( const R& a ) const
   {
      return abs( a ) <= feastol;
   }

   static std::size_t
   hashCode( const REAL& xval )
   {
      int theexp;
      double x = static_cast<double>( xval );

      // normalize the value
      double val = frexp( x, &theexp );

      // include the sign bit and the value in the upper 16 bits, i.e. use the
      // upper 15 bits of the mantissa. Cast to signed 16 bit int then to
      // unsigned 16 bit int to have the wrap-around for negative numbers at
      // 2^16.
      uint16_t upperhalf =
          static_cast<uint16_t>( static_cast<int16_t>( ldexp( val, 14 ) ) );

      // in the lower 16 bits include the exponent
      uint16_t lowerhalf = static_cast<uint16_t>( theexp );

      // compose the halfs into one 32 bit integral value and add to state
      return ( static_cast<std::size_t>( upperhalf ) << 16 ) |
             static_cast<std::size_t>( lowerhalf );
   }

   const REAL&
   getEpsilon() const
   {
      return epsilon;
   }

   const REAL&
   getFeasTol() const
   {
      return feastol;
   }

   const REAL&
   getHugeVal() const
   {
      return hugeval;
   }

   template <typename R>
   bool
   isHugeVal( const R& a ) const
   {
      return abs( a ) >= hugeval;
   }

   void
   setEpsilon( REAL value )
   {
      assert( value >= 0 );
      this->epsilon = value;
   }

   void
   setFeasTol( REAL value )
   {
      assert( value >= 0 );
      this->feastol = value;
   }

   void
   setHugeVal( REAL value )
   {
      assert( value >= 0 );
      this->hugeval = value;
   }

   void
   setUseAbsFeas( bool value )
   {
      this->useabsfeas = value;
   }

   template <typename Archive>
   void
   serialize( Archive& ar, const unsigned int version )
   {
      ar & epsilon;
      ar & feastol;
      ar & hugeval;
      ar & useabsfeas;
   }

 private:
   REAL epsilon;
   REAL feastol;
   REAL hugeval;
   bool useabsfeas;
};

/**
 *
 * @tparam REAL
 * @param s
 * @return boolean value is true if parsing failed, if not REAL contains the parsed number
 */
template <typename REAL>
std::pair<bool, REAL>
parse_number( const String& s )
{
   REAL number;
   try
   {
      std::stringstream string_stream;
      string_stream.str( s );
      string_stream >> number;
      if( !string_stream.fail() && string_stream.eof() )
         return { false, number };
   }
   catch( ... ) { }

   bool failure = false;
   Integral numerator = 0;
   Integral denominator = 1;
   unsigned exponent = 0;
   unsigned phase = 0;
   bool num_negated = false;
   bool exp_negated = false;
   for( char c : s )
   {
      int digit = '0' <= c && c <= '9' ? c - '0' : -1;
      switch( phase )
      {
      // number sign
      case 0:
         ++phase;
         if( c == '+' )
            break;
         else if( c == '-' )
         {
            num_negated = true;
            break;
         }
      // before delimiter
      case 1:
         if( digit >= 0 )
         {
            numerator *= 10;
            numerator += digit;
            break;
         }
         else
         {
            ++phase;
            if( num_traits<REAL>::is_rational )
            {
               if( c == '.' )
                  break;
            }
            else
            {
               if( c == '/' )
               {
                  denominator = 0;
                  break;
               }
            }
         }
      // after delimiter
      case 2:
         if( digit >= 0 )
         {
            if( num_traits<REAL>::is_rational )
            {
               numerator *= 10;
               numerator += digit;
               denominator *= 10;
            }
            else
            {
               denominator *= 10;
               denominator += digit;
            }
         }
         else if( ( c == 'e' || c == 'E' ) && num_traits<REAL>::is_rational )
            ++phase;
         else
            failure = true;
         break;
      // exponent sign
      case 3:
         ++phase;
         if( c == '+' )
            break;
         else if( c == '-' )
         {
            exp_negated = true;
            break;
         }
      // exponent value
      case 4:
         if( digit >= 0 )
         {
            exponent *= 10;
            exponent += digit;
            break;
         }
         else
            ++phase;
      default:
         failure = true;
         break;
      }
      if( failure )
         break;
   }
   if( denominator == 0 )
   {
      denominator = 1;
      failure = true;
   }
   if( num_negated )
      numerator *= -1;
   if( exp_negated )
      denominator *= boost::multiprecision::pow( Integral( 10 ), exponent );
   else
      numerator *= boost::multiprecision::pow( Integral( 10 ), exponent );
   number = REAL( Rational( numerator, denominator ) );

   return { failure, number };
}

} // namespace papilo

#endif
