module Flt
  module Support

    class InfiniteLoopError < StandardError
    end

    module_function
    # replace :ceiling and :floor rounding modes by :up/:down (depending on sign of the number to be rounded)
    def simplified_round_mode(round_mode, negative)
      if negative
        if round_mode == :ceiling
          round_mode = :floor
        elsif round_mode == :floor
          round_mode = :ceiling
        end
      end
      if round_mode == :ceiling
        round_mode = :up
      elsif round_mode == :floor
        round_mode = :down
      end
      round_mode
    end

    # Adjust truncated digits based on the rounding mode (:round_mode option)
    # and on the information about the following digits contained in the :round_up
    # parameter (nil for only zeros, :lo for nonzero values below tie, :tie for a :tie
    # and :hi for nonzero digits over the tie). Other parameters: :negative to consider
    # the number negative, :base the base of the number.
    def adjust_digits(dec_pos, digits, options={})
      round_mode = options[:round_mode]
      negative   = options[:negative]
      round_up   = options[:round_up]
      base       = options[:base]
      round_mode = simplified_round_mode(round_mode, negative)

      increment = (round_up && (round_mode != :down)) &&
                    ((round_mode == :up) ||
                     (round_up == :hi) ||
                     ((round_up == :tie) &&
                      ((round_mode==:half_up) ||
                       ((round_mode==:half_even) && ((digits.last % 2)==1)))))

      if increment
        digits = digits.dup
        # carry = increment ? 1 : 0
        # digits = digits.reverse.map{|d| d += carry; d>=base ? 0 : (carry=0;d)}.reverse
        # if carry != 0
        #   digits.unshift carry
        #   dec_pos += 1
        # end
        i = digits.size - 1
        while i>=0
          digits[i] += 1
          if digits[i] == base
            digits[i] = 0
          else
            break
          end
          i -= 1
        end
        if i<0
          dec_pos += 1
          digits.unshift 1
        end
      end
      [dec_pos, digits]
    end

    module AuxiliarFunctions

      module_function

      # Number of bits in binary representation of the positive integer n, or 0 if n == 0.
      def _nbits(x)
        raise  TypeError, "The argument to _nbits should be nonnegative." if x < 0
        # Ruby 2.1 introduced Integer#bit_length
        return x.bit_length if x.respond_to? :bit_length
        if x <= MAXIMUM_SMALLISH_INTEGER
          return 0 if x==0
          x.to_s(2).length
        elsif x <= NBITS_LIMIT
          Math.frexp(x).last
        else
          n = 0
          while x!=0
            y = x
            x >>= NBITS_BLOCK
            n += NBITS_BLOCK
          end
          n += y.to_s(2).length - NBITS_BLOCK if y!=0
          n
        end
      end
      NBITS_BLOCK = 32
      NBITS_LIMIT = Math.ldexp(1,Float::MANT_DIG).to_i
      MAXIMUM_SMALLISH_INTEGER = (2 << 63) - 1

      # Number of base b digits in an integer
      def _ndigits(x, b)
        raise  TypeError, "The argument to _ndigits should be nonnegative." if x < 0
        return 0 unless x.is_a?(Integer)
        return _nbits(x) if b==2
        if x <= MAXIMUM_SMALLISH_INTEGER
          return 0 if x==0
          x.to_s(b).length
        elsif x <= NDIGITS_LIMIT
          (Math.log(x)/Math.log(b)).floor + 1
        else
          n = 0
          block = b**NDIGITS_BLOCK
          while x!=0
            y = x
            x /= block
            n += NDIGITS_BLOCK
          end
          n += y.to_s(b).length - NDIGITS_BLOCK if y!=0
          n
        end
      end
      NDIGITS_BLOCK = 50
      NDIGITS_LIMIT = Float::MAX.to_i

      def detect_float_rounding
        x = x = Math::ldexp(1, Float::MANT_DIG+1) # 10000...00*Float::RADIX**2 == Float::RADIX**(Float::MANT_DIG+1)
        y = x + Math::ldexp(1, 2)                 # 00000...01*Float::RADIX**2 == Float::RADIX**2
        h = Float::RADIX/2
        b = h*Float::RADIX
        z = Float::RADIX**2 - 1
        if x + 1 == y
          if (y + 1 == y) && Float::RADIX==10
            :up05
          elsif -x - 1 == -y
            :up
          else
            :ceiling
          end
        else # x + 1 == x
          if x + z == x
            if -x - z == -x
              :down
            else
              :floor
            end
          else # x + z == y
            # round to nearest
            if x + b == x
              if y + b == y
                :half_down
              else
                :half_even
              end
            else # x + b == y
              :half_up
            end
          end
        end
      end

    end # AuxiliarFunctions

  end # Support


end # Flt

# The math method of context needs instance_exec to pass parameters to the blocks evaluated
# with a modified self (pointing to a context object.) intance_exec is available in Ruby 1.9.1 and
# is also defined by ActiveRecord. Here we use Mauricio Fernández implementation if it is not
# available.
class Object
  unless defined? instance_exec
    module InstanceExecHelper; end
    include InstanceExecHelper
    def instance_exec(*args, &block) # !> method redefined; discarding old instance_exec
      mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
      InstanceExecHelper.module_eval{ define_method(mname, &block) }
      begin
        ret = send(mname, *args)
      ensure
        InstanceExecHelper.module_eval{ undef_method(mname) } rescue nil
      end
      ret
    end
  end
end
