require "spec"
require "spec/helpers/iterate"

module OtherInterface; end

private record Three do
  include OtherInterface
end

private record Four do
  include OtherInterface
end

private struct InterfaceIndexable
  include Indexable(OtherInterface)

  def size
    2
  end

  def unsafe_fetch(index : Int) : OtherInterface
    case index
    when 0 then Three.new
    when 1 then Four.new
    else
      raise ""
    end
  end
end

private class SafeIndexable
  include Indexable(Int32)

  property size

  def initialize(@size : Int32, @offset = 0_i32)
  end

  def unsafe_fetch(index) : Int32
    raise IndexError.new unless 0 <= index < size
    (index + @offset).to_i
  end
end

private class SafeNestedIndexable
  include Indexable(Indexable(Int32))

  property size

  def initialize(@size : Int32, @inner_size : Int32)
  end

  def unsafe_fetch(index)
    raise IndexError.new unless 0 <= index < size
    SafeIndexable.new(@inner_size)
  end
end

private class SafeStringIndexable
  include Indexable(String)

  property size

  def initialize(@size : Int32)
  end

  def unsafe_fetch(index) : String
    raise IndexError.new unless 0 <= index < size
    index.to_s
  end
end

private class SafeMixedIndexable
  include Indexable(String | Int32)

  property size

  def initialize(@size : Int32)
  end

  def unsafe_fetch(index) : String | Int32
    raise IndexError.new unless 0 <= index < size
    index.to_s
  end
end

private class SafeRecursiveIndexable
  include Indexable(SafeRecursiveIndexable | Int32)

  property size

  def initialize(@size : Int32)
  end

  def unsafe_fetch(index) : SafeRecursiveIndexable | Int32
    raise IndexError.new unless 0 <= index < size
    if (index % 2) == 0
      SafeRecursiveIndexable.new(index)
    else
      index
    end
  end
end

describe Indexable do
  describe "#index" do
    it "does index with big negative offset" do
      indexable = SafeIndexable.new(3)
      indexable.index(0, -100).should be_nil
    end

    it "does index with big offset" do
      indexable = SafeIndexable.new(3)
      indexable.index(0, 100).should be_nil
    end

    it "offset type" do
      indexable = SafeIndexable.new(3)
      indexable.index(1, 0_i64).should eq 1
      indexable.index(1, 0_i64).should be_a(Int64)
    end
  end

  describe "#index!" do
    it "offset type" do
      indexable = SafeIndexable.new(3)
      indexable.index!(1, 0_i64).should eq 1
      indexable.index!(1, 0_i64).should be_a(Int64)
    end

    it "raises if no element is found" do
      indexable = SafeIndexable.new(3)
      expect_raises(Enumerable::NotFoundError) { indexable.index!(0, -100) }
      expect_raises(Enumerable::NotFoundError) { indexable.index!(0, -4) }
      expect_raises(Enumerable::NotFoundError) { indexable.index!(0, 1) }
      expect_raises(Enumerable::NotFoundError) { indexable.index!(0, 3) }
      expect_raises(Enumerable::NotFoundError) { indexable.index!(0, 100) }

      expect_raises(Enumerable::NotFoundError) { indexable.index!(-4) { true } }
      expect_raises(Enumerable::NotFoundError) { indexable.index!(3) { true } }
      expect_raises(Enumerable::NotFoundError) { indexable.index!(2) { false } }
      expect_raises(Enumerable::NotFoundError) { indexable.index!(-3) { false } }
    end
  end

  describe "#find" do
    it "finds the element matching the block" do
      indexable = SafeIndexable.new(4)
      indexable.find { |i| i > 2 }.should eq 3
    end

    it "finds the element matching the block after given offset" do
      indexable = SafeIndexable.new(8)
      indexable.find(offset: 5) { |i| i.even? }.should eq 6
    end

    it "finds the element matching the block after given negative offset" do
      indexable = SafeIndexable.new(8)
      indexable.find(offset: -6) { |i| i.even? }.should eq 2
    end

    it "does not receive a valid negative offset, returns if_none value" do
      indexable = SafeIndexable.new(4)
      indexable.find(-1, offset: -10) { |i| i > 2 }.should eq -1
    end

    it "does not find the element matching the block" do
      indexable = SafeIndexable.new(4)
      indexable.find { |i| i > 7 }.should be_nil
    end

    it "does not find the element matching the block, returns custom if_none value" do
      indexable = SafeIndexable.new(4)
      indexable.find(if_none: -1) { |i| i > 7 }.should eq -1
    end

    it "does not find the element matching the block after given offset, returns custom if_none value" do
      indexable = SafeIndexable.new(5)
      indexable.find(-3, 3) { |i| i > 15 }.should eq -3
    end
  end

  describe "#find!" do
    it "finds the element matching the block" do
      indexable = SafeIndexable.new(4)
      indexable.find! { |i| i > 2 }.should eq 3
    end

    it "finds the element matching the block after given offset" do
      indexable = SafeIndexable.new(8)
      indexable.find!(offset: 5) { |i| i.even? }.should eq 6
    end

    it "finds the element matching the block after given negative offset" do
      indexable = SafeIndexable.new(8)
      indexable.find!(offset: -6) { |i| i.even? }.should eq 2
    end

    it "does not receive a valid negative offset, raises not found" do
      indexable = SafeIndexable.new(4)
      expect_raises(Enumerable::NotFoundError) { indexable.find!(offset: -10) { |i| i > 2 } }
    end

    it "does not find the element matching the block, raises not found" do
      indexable = SafeIndexable.new(4)
      expect_raises(Enumerable::NotFoundError) { indexable.find! { |i| i > 7 } }
    end
  end

  describe "#rindex" do
    it "does rindex with big negative offset" do
      indexable = SafeIndexable.new(3)
      indexable.rindex(0, -100).should be_nil
    end

    it "does rindex with big offset" do
      indexable = SafeIndexable.new(3)
      indexable.rindex(0, 100).should be_nil
    end

    it "offset type" do
      indexable = SafeIndexable.new(3)
      indexable.rindex(1, 2_i64).should eq 1
      indexable.rindex(1, 2_i64).should be_a(Int64)
    end
  end

  describe "#rindex!" do
    it "does rindex with big negative offset" do
      indexable = SafeIndexable.new(3)
      expect_raises Enumerable::NotFoundError do
        indexable.rindex!(0, -100)
      end
    end

    it "does rindex with big offset" do
      indexable = SafeIndexable.new(3)
      expect_raises Enumerable::NotFoundError do
        indexable.rindex!(0, 100)
      end
    end

    it "offset type" do
      indexable = SafeIndexable.new(3)
      indexable.rindex!(1, 2_i64).should eq 1
      indexable.rindex!(1, 2_i64).should be_a(Int64)
    end
  end

  it "does each" do
    indexable = SafeIndexable.new(3)
    is = [] of Int32
    indexable.each do |i|
      is << i
    end.should be_nil
    is.should eq([0, 1, 2])
  end

  it "does each_index" do
    indexable = SafeIndexable.new(3)
    is = [] of Int32
    indexable.each_index do |i|
      is << i
    end.should be_nil
    is.should eq([0, 1, 2])
  end

  it "iterates through a subset of its elements (#3386)" do
    indexable = SafeIndexable.new(5)
    elems = [] of Int32

    return_value = indexable.each(start: 2, count: 3) do |elem|
      elems << elem
    end

    elems.should eq([2, 3, 4])
    return_value.should eq(indexable)
  end

  it "iterates until its size (#3386)" do
    indexable = SafeIndexable.new(5)
    elems = [] of Int32

    indexable.each(start: 3, count: 999) do |elem|
      elems << elem
    end

    elems.should eq([3, 4])
  end

  it "iterates until its size, having mutated (#3386)" do
    indexable = SafeIndexable.new(10)
    elems = [] of Int32

    indexable.each(start: 3, count: 999) do |elem|
      # size is incremented 3 times
      indexable.size += 1 if elem <= 5
      elems << elem
    end

    # last was 9, but now is 12.
    elems.should eq([3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
  end

  it "iterates until its size, having mutated (#3386)" do
    indexable = SafeIndexable.new(10)
    elems = [] of Int32

    indexable.each(start: 3, count: 5) do |elem|
      indexable.size += 1
      elems << elem
    end

    # last element iterated is still 7.
    elems.should eq([3, 4, 5, 6, 7])
  end

  it "handles empty ranges, including those with exclude-end" do
    "foobar"[3..3].should eq("b")
    "foobar"[3...3].should eq("")

    "foobar"[3..-3].should eq("b")
    "foobar"[3...-3].should eq("")
    "foobar"[3...-5].should eq("")

    "foobar"[3_u32..3_u32].should eq("b")
    "foobar"[3_u32...3_u32].should eq("")
    "foobar"[3_u32...1_u32].should eq("")
  end

  it "handles extremely large ranges without overflowing" do
    "foobar"[3..Int32::MAX].should eq("bar")

    expect_raises IndexError, "Index out of bounds" do
      "foobar"[Int32::MAX..]
    end

    Indexable.range_to_index_and_count(Int32::MAX.., 10).should eq({Int32::MAX, 0})
  end

  it "handles extremely large collection sizes without overflowing" do
    Indexable.range_to_index_and_count((Int32::MAX - 2)..Int32::MAX, Int32::MAX).should eq({Int32::MAX - 2, 3})
    Indexable.range_to_index_and_count((Int32::MAX - 4)...Int32::MAX, Int32::MAX).should eq({Int32::MAX - 4, 4})
    Indexable.range_to_index_and_count((Int32::MAX - 4)..-2, Int32::MAX).should eq({Int32::MAX - 4, 3})
  end

  it "errors on start indices that are off the end" do
    expect_raises IndexError, "Index out of bounds" do
      "foobar"[100..1000]
    end

    expect_raises IndexError, "Index out of bounds" do
      "foobar"[Int32::MIN..Int32::MAX]
    end
  end

  it "handles exclusive ranges that go off the end" do
    "foobar"[3...100].should eq("bar")
  end

  it "iterates within a range of indices (#3386)" do
    indexable = SafeIndexable.new(5)
    elems = [] of Int32

    return_value = indexable.each(within: 2..3) do |elem|
      elems << elem
    end

    elems.should eq([2, 3])
    return_value.should eq(indexable)
  end

  it "iterates within a range of indices, no end" do
    indexable = SafeIndexable.new(5)
    elems = [] of Int32

    return_value = indexable.each(within: 2..nil) do |elem|
      elems << elem
    end

    elems.should eq([2, 3, 4])
    return_value.should eq(indexable)
  end

  it "iterates within a range of indices, no beginning" do
    indexable = SafeIndexable.new(5)

    elems = [] of Int32
    return_value = indexable.each(within: nil..2) do |elem|
      elems << elem
    end

    elems.should eq([0, 1, 2])
    return_value.should eq(indexable)
  end

  describe "#join" do
    it "joins strings (empty case)" do
      indexable = SafeStringIndexable.new(0)
      indexable.join.should eq("")
      indexable.join(", ").should eq("")
    end

    it "joins strings (non-empty case)" do
      indexable = SafeStringIndexable.new(12)
      indexable.join(", ").should eq("0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11")
      indexable.join(98).should eq("098198298398498598698798898998109811")
    end

    it "joins non-strings" do
      indexable = SafeIndexable.new(12)
      indexable.join(", ").should eq("0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11")
      indexable.join(98).should eq("098198298398498598698798898998109811")
    end

    it "joins when T has String" do
      indexable = SafeMixedIndexable.new(12)
      indexable.join(", ").should eq("0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11")
      indexable.join(98).should eq("098198298398498598698798898998109811")
    end

    it "with IO" do
      String.build do |io|
        indexable = SafeStringIndexable.new(12)
        indexable.join(io)
      end.should eq "01234567891011"
    end
  end

  describe "dig?" do
    it "gets the value at given path given splat" do
      indexable = SafeRecursiveIndexable.new(30)
      indexable.dig?(20, 10, 4, 3).should eq(3)
    end

    it "returns nil if not found" do
      indexable = SafeRecursiveIndexable.new(30)
      indexable.dig?(2, 4).should be_nil
      indexable.dig?(3, 7).should be_nil
    end
  end

  describe "dig" do
    it "gets the value at given path given splat" do
      indexable = SafeRecursiveIndexable.new(30)
      indexable.dig(20, 10, 4, 3).should eq(3)
    end

    it "raises IndexError if not found" do
      indexable = SafeRecursiveIndexable.new(30)
      expect_raises IndexError, %(Index out of bounds) do
        indexable.dig(2, 4)
      end
      expect_raises IndexError, %(Indexable value not diggable for index: 3) do
        indexable.dig(3, 7)
      end
    end
  end

  describe "fetch" do
    it "fetches with default value" do
      indexable = SafeIndexable.new(3)
      a = indexable.to_a

      indexable.fetch(2, 4).should eq(2)
      indexable.fetch(3, 4).should eq(4)
      a.should eq([0, 1, 2])
    end

    it "fetches with block" do
      indexable = SafeIndexable.new(3)
      a = indexable.to_a

      indexable.fetch(2) { |k| k * 3 }.should eq(2)
      indexable.fetch(3) { |k| k * 3 }.should eq(9)
      a.should eq([0, 1, 2])
    end
  end

  describe "#cartesian_product" do
    it "does with 1 other Indexable" do
      elems = SafeIndexable.new(2).cartesian_product(SafeIndexable.new(3))
      elems.should eq([{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}])

      elems = SafeIndexable.new(2).cartesian_product(SafeIndexable.new(0))
      elems.should be_empty

      elems = SafeIndexable.new(0).cartesian_product(SafeIndexable.new(3))
      elems.should be_empty
    end

    it "does with >1 other Indexables" do
      elems = SafeIndexable.new(2).cartesian_product(SafeStringIndexable.new(2), SafeIndexable.new(2))
      elems.should eq([
        {0, "0", 0}, {0, "0", 1}, {0, "1", 0}, {0, "1", 1},
        {1, "0", 0}, {1, "0", 1}, {1, "1", 0}, {1, "1", 1},
      ])
    end
  end

  describe ".cartesian_product" do
    it "does with an Indexable of Indexables" do
      elems = Indexable.cartesian_product(SafeNestedIndexable.new(2, 3))
      elems.should eq([[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]])

      elems = Indexable.cartesian_product(SafeNestedIndexable.new(2, 0))
      elems.should be_empty

      elems = Indexable.cartesian_product(SafeNestedIndexable.new(0, 3))
      elems.should eq([[] of Int32])

      elems = Indexable.cartesian_product(SafeNestedIndexable.new(0, 0))
      elems.should eq([[] of Int32])
    end

    it "does with a Tuple of Tuples with mixed types" do
      elems = Indexable.cartesian_product({ {1, 'a'}, {"", 4}, {5, 6} })
      elems.should be_a(Array(Array(Int32 | Char | String)))
      elems.should eq([[1, "", 5], [1, "", 6], [1, 4, 5], [1, 4, 6], ['a', "", 5], ['a', "", 6], ['a', 4, 5], ['a', 4, 6]])
    end
  end

  describe "#each_cartesian" do
    it "does with 1 other Indexable, with block" do
      r = [] of Int32 | String
      indexable = SafeIndexable.new(3)
      indexable.each_cartesian(SafeStringIndexable.new(2)) { |a, b| r << a; r << b }
      r.should eq([0, "0", 0, "1", 1, "0", 1, "1", 2, "0", 2, "1"])

      r = [] of Int32 | String
      indexable = SafeIndexable.new(3)
      indexable.each_cartesian(SafeStringIndexable.new(0)) { |a, b| r << a; r << b }
      r.should be_empty

      r = [] of Int32 | String
      indexable = SafeIndexable.new(0)
      indexable.each_cartesian(SafeStringIndexable.new(2)) { |a, b| r << a; r << b }
      r.should be_empty
    end

    it "does with 1 other Indexable, without block" do
      iter = SafeIndexable.new(3).each_cartesian(SafeStringIndexable.new(2))
      iter.next.should eq({0, "0"})
      iter.next.should eq({0, "1"})
      iter.next.should eq({1, "0"})
      iter.next.should eq({1, "1"})
      iter.next.should eq({2, "0"})
      iter.next.should eq({2, "1"})
      iter.next.should be_a(Iterator::Stop)
    end

    it "does with 1 other Indexable, without block, combined with select" do
      iter = SafeIndexable.new(3).each_cartesian(SafeStringIndexable.new(2))
      iter = iter.select { |(x, y)| x > 0 }
      iter.next.should eq({1, "0"})
      iter.next.should eq({1, "1"})
      iter.next.should eq({2, "0"})
      iter.next.should eq({2, "1"})
      iter.next.should be_a(Iterator::Stop)
    end

    it "does with >1 other Indexables, with block" do
      r = [] of Int32 | String
      i1 = SafeIndexable.new(2)
      i2 = SafeIndexable.new(3)
      i3 = SafeIndexable.new(4)
      i1.each_cartesian(i2, i3) { |a, b, c| r << a + b + c }
      r.should eq([0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6])
    end

    it "does with >1 other Indexables, without block" do
      i1 = SafeStringIndexable.new(2)
      i2 = SafeStringIndexable.new(2)
      i3 = SafeIndexable.new(2)
      iter = i1.each_cartesian(i2, i3)
      iter.next.should eq({"0", "0", 0})
      iter.next.should eq({"0", "0", 1})
      iter.next.should eq({"0", "1", 0})
      iter.next.should eq({"0", "1", 1})
      iter.next.should eq({"1", "0", 0})
      iter.next.should eq({"1", "0", 1})
      iter.next.should eq({"1", "1", 0})
      iter.next.should eq({"1", "1", 1})
      iter.next.should be_a(Iterator::Stop)
    end
  end

  describe ".each_cartesian" do
    it "does with an Indexable of Indexables, with block" do
      r = [] of Int32
      Indexable.each_cartesian(SafeNestedIndexable.new(3, 2)) { |v| r.concat(v) }
      r.should eq([0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1])

      r = [] of Int32
      Indexable.each_cartesian(SafeNestedIndexable.new(3, 0)) { |v| r.concat(v) }
      r.should be_empty

      r = [] of Int32
      Indexable.each_cartesian(SafeNestedIndexable.new(0, 2)) { |v| r.concat(v) }
      r.should be_empty

      r = [] of Int32
      Indexable.each_cartesian(SafeNestedIndexable.new(0, 0)) { |v| r.concat(v) }
      r.should be_empty
    end

    it "does with reuse = true, with block" do
      r = [] of Int32
      object_ids = Set(UInt64).new
      indexables = SafeNestedIndexable.new(3, 2)

      Indexable.each_cartesian(indexables, reuse: true) do |v|
        object_ids << v.object_id
        r.concat(v)
      end

      r.should eq([0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1])
      object_ids.size.should eq(1)
    end

    it "does with reuse = array, with block" do
      r = [] of Int32
      buf = [] of Int32
      indexables = SafeNestedIndexable.new(3, 2)

      Indexable.each_cartesian(indexables, reuse: buf) do |v|
        v.should be(buf)
        r.concat(v)
      end

      r.should eq([0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1])
    end

    it "does with an Indexable of Indexables, without block" do
      iter = Indexable.each_cartesian(SafeNestedIndexable.new(2, 3))
      iter.next.should eq([0, 0])
      iter.next.should eq([0, 1])
      iter.next.should eq([0, 2])
      iter.next.should eq([1, 0])
      iter.next.should eq([1, 1])
      iter.next.should eq([1, 2])
      iter.next.should eq([2, 0])
      iter.next.should eq([2, 1])
      iter.next.should eq([2, 2])
      iter.next.should be_a(Iterator::Stop)

      iter = Indexable.each_cartesian(SafeNestedIndexable.new(0, 3))
      iter.next.should eq([] of Int32)
      iter.next.should be_a(Iterator::Stop)
    end

    it "does with an Indexable of Indexables, without block, combined with select" do
      iter = Indexable.each_cartesian(SafeNestedIndexable.new(2, 3))
      iter = iter.select { |(x, y)| x > 0 }
      iter.next.should eq([1, 0])
      iter.next.should eq([1, 1])
      iter.next.should eq([1, 2])
      iter.next.should eq([2, 0])
      iter.next.should eq([2, 1])
      iter.next.should eq([2, 2])
      iter.next.should be_a(Iterator::Stop)

      iter = Indexable.each_cartesian(SafeNestedIndexable.new(0, 3))
      iter.next.should eq([] of Int32)
      iter.next.should be_a(Iterator::Stop)
    end

    it "does with reuse = true, without block" do
      iter = Indexable.each_cartesian(SafeNestedIndexable.new(2, 2), reuse: true)
      buf = iter.next
      buf.should eq([0, 0])
      iter.next.should be(buf)
      buf.should eq([0, 1])
      iter.next.should be(buf)
      buf.should eq([1, 0])
      iter.next.should be(buf)
      buf.should eq([1, 1])
      iter.next.should be_a(Iterator::Stop)
    end

    it "does with reuse = array, without block" do
      buf = [] of Int32
      iter = Indexable.each_cartesian(SafeNestedIndexable.new(2, 2), reuse: buf)
      iter.next.should be(buf)
      buf.should eq([0, 0])
      iter.next.should be(buf)
      buf.should eq([0, 1])
      iter.next.should be(buf)
      buf.should eq([1, 0])
      iter.next.should be(buf)
      buf.should eq([1, 1])
    end
  end

  describe "permutations" do
    it { [1, 2, 2].permutations.should eq([[1, 2, 2], [1, 2, 2], [2, 1, 2], [2, 2, 1], [2, 1, 2], [2, 2, 1]]) }
    it { SafeIndexable.new(3, 1).permutations.should eq([[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]) }
    it { SafeIndexable.new(3, 1).permutations(1).should eq([[1], [2], [3]]) }
    it { SafeIndexable.new(3, 1).permutations(2).should eq([[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]) }
    it { SafeIndexable.new(3, 1).permutations(3).should eq([[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]) }
    it { SafeIndexable.new(3, 1).permutations(0).should eq([[] of Int32]) }
    it { SafeIndexable.new(3, 1).permutations(4).should eq([] of Array(Int32)) }
    it { expect_raises(ArgumentError, "Size must be positive") { [1].permutations(-1) } }

    it "accepts a block" do
      sums = [] of Int32
      SafeIndexable.new(3, 1).each_permutation(2) do |perm|
        sums << perm.sum
      end.should be_nil
      sums.should eq([3, 4, 3, 5, 4, 5])
    end

    it "yielding dup of arrays" do
      sums = [] of Int32
      SafeIndexable.new(3, 1).each_permutation(3) do |perm|
        perm.map! &.+(1)
        sums << perm.sum
      end.should be_nil
      sums.should eq([9, 9, 9, 9, 9, 9])
    end

    it "yields with reuse = true" do
      sums = [] of Int32
      object_ids = Set(UInt64).new
      SafeIndexable.new(3, 1).each_permutation(3, reuse: true) do |perm|
        object_ids << perm.object_id
        perm.map! &.+(1)
        sums << perm.sum
      end.should be_nil
      sums.should eq([9, 9, 9, 9, 9, 9])
      object_ids.size.should eq(1)
    end

    it { expect_raises(ArgumentError, "Size must be positive") { [1].each_permutation(-1) { } } }

    it "returns iterator" do
      a = SafeIndexable.new(3, 1)
      perms = a.permutations
      iter = a.each_permutation
      perms.each do |perm|
        iter.next.should eq(perm)
      end
      iter.next.should be_a(Iterator::Stop)
    end

    it "returns iterator with given size" do
      a = SafeIndexable.new(3, 1)
      perms = a.permutations(2)
      iter = a.each_permutation(2)
      perms.each do |perm|
        iter.next.should eq(perm)
      end
      iter.next.should be_a(Iterator::Stop)
    end

    it "returns iterator with reuse = true" do
      a = SafeIndexable.new(3, 1)
      object_ids = Set(UInt64).new
      perms = a.permutations
      iter = a.each_permutation(reuse: true)
      perms.each do |perm|
        b = iter.next.as(Array)
        object_ids << b.object_id
        b.should eq(perm)
      end
      iter.next.should be_a(Iterator::Stop)
      object_ids.size.should eq(1)
    end
  end

  describe "combinations" do
    it { [1, 2, 2].combinations.should eq([[1, 2, 2]]) }
    it { SafeIndexable.new(3, 1).combinations.should eq([[1, 2, 3]]) }
    it { SafeIndexable.new(3, 1).combinations(1).should eq([[1], [2], [3]]) }
    it { SafeIndexable.new(3, 1).combinations(2).should eq([[1, 2], [1, 3], [2, 3]]) }
    it { SafeIndexable.new(3, 1).combinations(3).should eq([[1, 2, 3]]) }
    it { SafeIndexable.new(3, 1).combinations(0).should eq([[] of Int32]) }
    it { SafeIndexable.new(3, 1).combinations(4).should eq([] of Array(Int32)) }
    it { SafeIndexable.new(4, 1).combinations(3).should eq([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]) }
    it { SafeIndexable.new(4, 1).combinations(2).should eq([[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]) }
    it { expect_raises(ArgumentError, "Size must be positive") { [1].combinations(-1) } }

    it "accepts a block" do
      sums = [] of Int32
      SafeIndexable.new(3, 1).each_combination(2) do |comb|
        sums << comb.sum
      end.should be_nil
      sums.should eq([3, 4, 5])
    end

    it "yielding dup of arrays" do
      sums = [] of Int32
      SafeIndexable.new(3, 1).each_combination(3) do |comb|
        comb.map! &.+(1)
        sums << comb.sum
      end.should be_nil
      sums.should eq([9])
    end

    it "does with reuse = true" do
      sums = [] of Int32
      object_ids = Set(UInt64).new
      SafeIndexable.new(3, 1).each_combination(2, reuse: true) do |comb|
        sums << comb.sum
        object_ids << comb.object_id
      end.should be_nil
      sums.should eq([3, 4, 5])
      object_ids.size.should eq(1)
    end

    it "does with reuse = array" do
      sums = [] of Int32
      reuse = [] of Int32
      SafeIndexable.new(3, 1).each_combination(2, reuse: reuse) do |comb|
        sums << comb.sum
        comb.should be(reuse)
      end.should be_nil
      sums.should eq([3, 4, 5])
    end

    it { expect_raises(ArgumentError, "Size must be positive") { [1].each_combination(-1) { } } }

    it "returns iterator" do
      a = [1, 2, 3, 4]
      combs = a.combinations(2)
      iter = a.each_combination(2)
      combs.each do |comb|
        iter.next.should eq(comb)
      end
      iter.next.should be_a(Iterator::Stop)
    end

    it "returns iterator with reuse = true" do
      a = [1, 2, 3, 4]
      combs = a.combinations(2)
      object_ids = Set(UInt64).new
      iter = a.each_combination(2, reuse: true)
      combs.each do |comb|
        b = iter.next
        object_ids << b.object_id
        b.should eq(comb)
      end
      iter.next.should be_a(Iterator::Stop)
      object_ids.size.should eq(1)
    end

    it "returns iterator with reuse = array" do
      a = [1, 2, 3, 4]
      reuse = [] of Int32
      combs = a.combinations(2)
      iter = a.each_combination(2, reuse: reuse)
      combs.each do |comb|
        b = iter.next
        b.should be(reuse)
        b.should eq(comb)
      end
      iter.next.should be_a(Iterator::Stop)
    end
  end

  describe "repeated_combinations" do
    it { [1, 2, 2].repeated_combinations.should eq([[1, 1, 1], [1, 1, 2], [1, 1, 2], [1, 2, 2], [1, 2, 2], [1, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2]]) }
    it { SafeIndexable.new(3, 1).repeated_combinations.should eq([[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 2, 2], [1, 2, 3], [1, 3, 3], [2, 2, 2], [2, 2, 3], [2, 3, 3], [3, 3, 3]]) }
    it { SafeIndexable.new(3, 1).repeated_combinations(1).should eq([[1], [2], [3]]) }
    it { SafeIndexable.new(3, 1).repeated_combinations(2).should eq([[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]]) }
    it { SafeIndexable.new(3, 1).repeated_combinations(3).should eq([[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 2, 2], [1, 2, 3], [1, 3, 3], [2, 2, 2], [2, 2, 3], [2, 3, 3], [3, 3, 3]]) }
    it { SafeIndexable.new(3, 1).repeated_combinations(0).should eq([[] of Int32]) }
    it { SafeIndexable.new(3, 1).repeated_combinations(4).should eq([[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 1, 3], [1, 1, 2, 2], [1, 1, 2, 3], [1, 1, 3, 3], [1, 2, 2, 2], [1, 2, 2, 3], [1, 2, 3, 3], [1, 3, 3, 3], [2, 2, 2, 2], [2, 2, 2, 3], [2, 2, 3, 3], [2, 3, 3, 3], [3, 3, 3, 3]]) }
    it { expect_raises(ArgumentError, "Size must be positive") { [1].repeated_combinations(-1) } }

    it "accepts a block" do
      sums = [] of Int32
      SafeIndexable.new(3, 1).each_repeated_combination(2) do |comb|
        sums << comb.sum
      end.should be_nil
      sums.should eq([2, 3, 4, 4, 5, 6])
    end

    it "yielding dup of arrays" do
      sums = [] of Int32
      SafeIndexable.new(3, 1).each_repeated_combination(3) do |comb|
        comb.map! &.+(1)
        sums << comb.sum
      end.should be_nil
      sums.should eq([6, 7, 8, 8, 9, 10, 9, 10, 11, 12])
    end

    it { expect_raises(ArgumentError, "Size must be positive") { [1].each_repeated_combination(-1) { } } }

    it "yields with reuse = true" do
      sums = [] of Int32
      object_ids = Set(UInt64).new
      SafeIndexable.new(3, 1).each_repeated_combination(3, reuse: true) do |comb|
        object_ids << comb.object_id
        comb.map! &.+(1)
        sums << comb.sum
      end.should be_nil
      sums.should eq([6, 7, 8, 8, 9, 10, 9, 10, 11, 12])
      object_ids.size.should eq(1)
    end

    it "yields with reuse = array" do
      sums = [] of Int32
      reuse = [] of Int32
      SafeIndexable.new(3, 1).each_repeated_combination(3, reuse: reuse) do |comb|
        comb.should be(reuse)
        comb.map! &.+(1)
        sums << comb.sum
      end.should be_nil
      sums.should eq([6, 7, 8, 8, 9, 10, 9, 10, 11, 12])
    end

    it "returns iterator" do
      a = [1, 2, 3, 4]
      combs = a.repeated_combinations(2)
      iter = a.each_repeated_combination(2)
      combs.each do |comb|
        iter.next.should eq(comb)
      end
      iter.next.should be_a(Iterator::Stop)
    end

    it "returns iterator with reuse = true" do
      a = [1, 2, 3, 4]
      object_ids = Set(UInt64).new
      combs = a.repeated_combinations(2)
      iter = a.each_repeated_combination(2, reuse: true)
      combs.each do |comb|
        b = iter.next
        object_ids << b.object_id
        b.should eq(comb)
      end
      iter.next.should be_a(Iterator::Stop)
      object_ids.size.should eq(1)
    end

    it "returns iterator with reuse = array" do
      a = [1, 2, 3, 4]
      reuse = [] of Int32
      combs = a.repeated_combinations(2)
      iter = a.each_repeated_combination(2, reuse: reuse)
      combs.each do |comb|
        b = iter.next
        b.should be(reuse)
        b.should eq(comb)
      end
      iter.next.should be_a(Iterator::Stop)
    end

    describe "n > size (#14088)" do
      it_iterates "#each_repeated_combination", [[1, 1, 1], [1, 1, 2], [1, 2, 2], [2, 2, 2]], SafeIndexable.new(2, 1).each_repeated_combination(3)
      it_iterates "#each_repeated_combination", [[1, 1, 1, 1], [1, 1, 1, 2], [1, 1, 2, 2], [1, 2, 2, 2], [2, 2, 2, 2]], SafeIndexable.new(2, 1).each_repeated_combination(4)
    end
  end

  describe "#to_a" do
    it "without a block of an interface type" do
      InterfaceIndexable.new.to_a.should eq [Three.new, Four.new]
    end
  end
end
