class Random
  # Implementation corresponding to the actual Random class of Ruby
  # The actual random generator (mersenne twister) is in MT19937.
  # Ruby specific conversions are handled in bits_and_bytes.
  # The high level stuff (argument checking) is done here.
  #
  module Implementation
    attr_reader :seed

    def initialize(seed = 0)
      super()
      srand(seed)
    end

    def srand(new_seed = 0)
      new_seed = Backports.coerce_to_int(new_seed)
      old, @seed = @seed, new_seed.nonzero? || Random.new_seed
      @mt = MT19937[ @seed ]
      old
    end

    def rand(limit = Backports::Undefined)
      case limit
        when Backports::Undefined
          @mt.random_float
        when Float
          limit * @mt.random_float unless limit <= 0
        when Range
          _rand_range(limit)
        else
          limit = Backports.coerce_to_int(limit)
          @mt.random_integer(limit) unless limit <= 0
      end || raise(ArgumentError, "invalid argument #{limit}")
    end

    def bytes(nb)
      nb = Backports.coerce_to_int(nb)
      raise ArgumentError, "negative size" if nb < 0
      @mt.random_bytes(nb)
    end

    def ==(other)
      other.is_a?(Random) &&
        seed == other.seed &&
        left == other.send(:left) &&
        state == other.send(:state)
    end

    def inspect
      "#<#{self.class.name}:#{object_id}>"
    end

  private
    def state
      @mt.to_bignum
    end

    def left
      MT19937::STATE_SIZE - @mt.last_read
    end

    def _rand_range(limit)
      if limit.end.is_a?(Float)
        from = Backports.coerce_to(limit.begin, Float, :to_f)
        to = limit.end
        range = to - from
        if range < 0
          nil
        elsif limit.exclude_end?
          from + @mt.random_float * range unless range <= 0
        else
          # cheat a bit... this will reduce the nb of random bits
          loop do
            r = @mt.random_float * range * 1.0001
            break from + r unless r > range
          end
        end
      else
        from, to = [limit.begin, limit.end].map(&Backports.method(:coerce_to_int))
        to += 1 unless limit.exclude_end?
        range = to - from
        from + @mt.random_integer(range) unless range <= 0
      end
    end
  end

  def self.new_seed
    Kernel::srand # use the built-in seed generator
    Kernel::srand # return the generated seed
  end
end