require_relative '../../spec_helper'
require_relative 'fixtures/classes'

describe "Kernel#rand" do
  it "is a private method" do
    Kernel.private_instance_methods(false).should.include?(:rand)
  end

  it "returns a float if no argument is passed" do
    rand.should.is_a?(Float)
  end

  it "returns an integer for an integer argument" do
    rand(77).should.is_a?(Integer)
  end

  it "returns an integer for a float argument greater than 1" do
    rand(1.3).should.is_a?(Integer)
  end

  it "returns a float for an argument between -1 and 1" do
    rand(-0.999).should.is_a?(Float)
    rand(-0.01).should.is_a?(Float)
    rand(0).should.is_a?(Float)
    rand(0.01).should.is_a?(Float)
    rand(0.999).should.is_a?(Float)
  end

  it "ignores the sign of the argument" do
    [0, 1, 2, 3].should.include?(rand(-4))
  end

  it "never returns a value greater or equal to 1.0 with no arguments" do
    1000.times do
      (0...1.0).should.include?(rand)
    end
  end

  it "never returns a value greater or equal to any passed in max argument" do
    1000.times do
      (0...100).to_a.should.include?(rand(100))
    end
  end

  it "calls to_int on its argument" do
    l = mock('limit')
    l.should_receive(:to_int).and_return 7

    rand l
  end

  context "given an exclusive range" do
    it "returns an Integer between the two Integers" do
      1000.times do
        x = rand(4...6)
        x.should.is_a?(Integer)
        (4...6).should.include?(x)
      end
    end

    it "returns a Float between the given Integer and Float" do
      1000.times do
        x = rand(4...6.5)
        x.should.is_a?(Float)
        (4...6.5).should.include?(x)
      end
    end

    it "returns a Float between the given Float and Integer" do
      1000.times do
        x = rand(3.5...6)
        x.should.is_a?(Float)
        (3.5...6).should.include?(x)
      end
    end

    it "returns a Float between the two given Floats" do
      1000.times do
        x = rand(3.5...6.5)
        x.should.is_a?(Float)
        (3.5...6.5).should.include?(x)
      end
    end
  end

  context "given an inclusive range" do
    it "returns an Integer between the two Integers" do
      1000.times do
        x = rand(4..6)
        x.should.is_a?(Integer)
        (4..6).should.include?(x)
      end
    end

    it "returns a Float between the given Integer and Float" do
      1000.times do
        x = rand(4..6.5)
        x.should.is_a?(Float)
        (4..6.5).should.include?(x)
      end
    end

    it "returns a Float between the given Float and Integer" do
      1000.times do
        x = rand(3.5..6)
        x.should.is_a?(Float)
        (3.5..6).should.include?(x)
      end
    end

    it "returns a Float between the two given Floats" do
      1000.times do
        x = rand(3.5..6.5)
        x.should.is_a?(Float)
        (3.5..6.5).should.include?(x)
      end
    end
  end

  context "given an inclusive range between 0 and 1" do
    it "returns an Integer between the two Integers" do
      x = rand(0..1)
      x.should.is_a?(Integer)
      (0..1).should.include?(x)
    end

    it "returns a Float if at least one side is Float" do
      seed = 42
      x1 = Random.new(seed).rand(0..1.0)
      x2 = Random.new(seed).rand(0.0..1.0)
      x3 = Random.new(seed).rand(0.0..1)

      x3.should.is_a?(Float)
      x1.should.eql?(x3)
      x2.should.eql?(x3)

      (0.0..1.0).should.include?(x3)
    end
  end

  context "given an exclusive range between 0 and 1" do
    it "returns zero as an Integer" do
      x = rand(0...1)
      x.should.is_a?(Integer)
      x.should.eql?(0)
    end

    it "returns a Float if at least one side is Float" do
      seed = 42
      x1 = Random.new(seed).rand(0...1.0)
      x2 = Random.new(seed).rand(0.0...1.0)
      x3 = Random.new(seed).rand(0.0...1)

      x3.should.is_a?(Float)
      x1.should.eql?(x3)
      x2.should.eql?(x3)

      (0.0...1.0).should.include?(x3)
    end
  end

  it "returns a numeric for an range argument where max is < 1" do
    rand(0.25..0.75).should.is_a?(Numeric)
  end

  it "returns nil when range is backwards" do
    rand(1..0).should == nil
  end

  it "returns the range start/end when Float range is 0" do
    rand(1.0..1.0).should.eql?(1.0)
  end

  it "returns the range start/end when Integer range is 0" do
    rand(42..42).should.eql?(42)
  end

  it "supports custom object types" do
    rand(KernelSpecs::CustomRangeInteger.new(1)..KernelSpecs::CustomRangeInteger.new(42)).should.instance_of?(KernelSpecs::CustomRangeInteger)
    rand(KernelSpecs::CustomRangeFloat.new(1.0)..KernelSpecs::CustomRangeFloat.new(42.0)).should.instance_of?(KernelSpecs::CustomRangeFloat)
    rand(Time.now..Time.now).should.instance_of?(Time)
  end

  it "is random on boot" do
    results = 2.times.map {
      out = ruby_exe('p rand', options: '--disable-gems')
      Float(out)
    }
    results.size.should == 2
    # this is technically flaky, but very unlikely in a good distribution
    results[0].should_not == results[1]
  end
end

describe "Kernel.rand" do
  it "is a public method" do
    Kernel.public_methods(false).should.include?(:rand)
  end
end
