# A container that allows accessing elements via a numeric index.
#
# Indexing starts at `0`. A negative index is assumed to be
# relative to the end of the container: `-1` indicates the last element,
# `-2` is the next to last element, and so on.
#
# Types including this module are typically `Array`-like types.
#
# ## Stability guarantees
#
# Several methods in `Indexable`, such as `#bsearch` and `#cartesian_product`,
# require the collection to be _stable_; that is, calling `#each(&)` over and
# over again should always yield the same elements, provided the collection is
# not mutated between the calls. In particular, `#each(&)` itself should not
# mutate the collection throughout the loop. Stability of an `Indexable` is
# guaranteed if the following criteria are met:
#
# * `#unsafe_fetch` and `#size` do not mutate the collection
# * `#each(&)` and `#each_index(&)` are not overridden
#
# The standard library assumes that all including types of `Indexable` are
# always stable. It is undefined behavior to implement an `Indexable` that is
# not stable or only conditionally stable.
module Indexable(T)
  include Iterable(T)
  include Enumerable(T)

  # Returns the number of elements in this container.
  abstract def size

  # Returns the element at the given *index*, without doing any bounds check.
  #
  # `Indexable` makes sure to invoke this method with *index* in `0...size`,
  # so converting negative indices to positive ones is not needed here.
  #
  # Clients never invoke this method directly. Instead, they access
  # elements with `#[](index)` and `#[]?(index)`.
  #
  # This method should only be directly invoked if you are absolutely
  # sure the index is in bounds, to avoid a bounds check for a small boost
  # of performance.
  abstract def unsafe_fetch(index : Int)

  # Returns the element at the given *index*, if in bounds,
  # otherwise executes the given block with the index and returns its value.
  #
  # ```
  # a = [:foo, :bar]
  # a.fetch(0) { :default_value }    # => :foo
  # a.fetch(2) { :default_value }    # => :default_value
  # a.fetch(2) { |index| index * 3 } # => 6
  # ```
  def fetch(index : Int, &)
    index = check_index_out_of_bounds(index) do
      return yield index
    end
    unsafe_fetch(index)
  end

  # Returns the value at the index given by *index*, or when not found the value given by *default*.
  #
  # ```
  # a = [:foo, :bar]
  # a.fetch(0, :default_value) # => :foo
  # a.fetch(2, :default_value) # => :default_value
  # ```
  @[AlwaysInline]
  def fetch(index, default)
    fetch(index) { default }
  end

  # Returns the element at the given *index*.
  #
  # Negative indices can be used to start counting from the end of the array.
  # Raises `IndexError` if trying to access an element outside the array's range.
  #
  # ```
  # ary = ['a', 'b', 'c']
  # ary[0]  # => 'a'
  # ary[2]  # => 'c'
  # ary[-1] # => 'c'
  # ary[-2] # => 'b'
  #
  # ary[3]  # raises IndexError
  # ary[-4] # raises IndexError
  # ```
  @[AlwaysInline]
  def [](index : Int)
    fetch(index) { raise IndexError.new }
  end

  # Returns the element at the given *index*.
  #
  # Negative indices can be used to start counting from the end of the array.
  # Returns `nil` if trying to access an element outside the array's range.
  #
  # ```
  # ary = ['a', 'b', 'c']
  # ary[0]?  # => 'a'
  # ary[2]?  # => 'c'
  # ary[-1]? # => 'c'
  # ary[-2]? # => 'b'
  #
  # ary[3]?  # => nil
  # ary[-4]? # => nil
  # ```
  @[AlwaysInline]
  def []?(index : Int)
    fetch(index, nil)
  end

  # Traverses the depth of a structure and returns the value.
  # Returns `nil` if not found.
  #
  # ```
  # ary = [{1, 2, 3, {4, 5, 6}}]
  # ary.dig?(0, 3, 2) # => 6
  # ary.dig?(0, 3, 3) # => nil
  # ```
  def dig?(index : Int, *subindexes)
    if (value = self[index]?) && value.responds_to?(:dig?)
      value.dig?(*subindexes)
    end
  end

  # :nodoc:
  def dig?(index : Int)
    self[index]?
  end

  # Traverses the depth of a structure and returns the value, otherwise
  # raises `IndexError`.
  #
  # ```
  # ary = [{1, 2, 3, {4, 5, 6}}]
  # ary.dig(0, 3, 2) # => 6
  # ary.dig(0, 3, 3) # raises IndexError
  # ```
  def dig(index : Int, *subindexes)
    if (value = self[index]) && value.responds_to?(:dig)
      return value.dig(*subindexes)
    end
    raise IndexError.new "Indexable value not diggable for index: #{index.inspect}"
  end

  # :nodoc:
  def dig(index : Int)
    self[index]
  end

  # By using binary search, returns the first element
  # for which the passed block returns a truthy value.
  #
  # If the block returns a falsey value, the element to be found lies
  # behind. If the block returns a truthy value, the element to be found
  # is itself or lies in front.
  #
  # Binary search needs the collection to be sorted in regards to the search
  # criterion.
  #
  # Returns `nil` if the block didn't return a truthy value for any element.
  #
  # ```
  # [2, 5, 7, 10].bsearch { |x| x >= 4 } # => 5
  # [2, 5, 7, 10].bsearch { |x| x > 10 } # => nil
  # ```
  def bsearch(& : T -> _)
    bsearch_index { |value| yield value }.try { |index| unsafe_fetch(index) }
  end

  # By using binary search, returns the index of the first element
  # for which the passed block returns a truthy value.
  #
  # If the block returns a falsey value, the element to be found lies
  # behind. If the block returns a truthy value, the element to be found
  # is itself or lies in front.
  #
  # Binary search needs the collection to be sorted in regards to the search
  # criterion.
  #
  # Returns `nil` if the block didn't return a truthy value for any element.
  #
  # ```
  # [2, 5, 7, 10].bsearch_index { |x, i| x >= 4 } # => 1
  # [2, 5, 7, 10].bsearch_index { |x, i| x > 10 } # => nil
  # ```
  def bsearch_index(& : T, Int32 -> _)
    (0...size).bsearch { |index| yield unsafe_fetch(index), index }
  end

  # Returns an `Array` of all ordered combinations of elements taken from each
  # of `self` and *others* as `Tuple`s.
  # Traversal of elements starts from the last `Indexable` argument.
  #
  # ```
  # [1, 2, 3].cartesian_product({'a', 'b'})                     # => [{1, 'a'}, {1, 'b'}, {2, 'a'}, {2, 'b'}, {3, 'a'}, {3, 'b'}]
  # ['a', 'b'].cartesian_product({1, 2}, {'c', 'd'}).map &.join # => ["a1c", "a1d", "a2c", "a2d", "b1c", "b1d", "b2c", "b2d"]
  # ```
  def cartesian_product(*others : Indexable)
    capacity = others.product(size, &.size)
    result = Array(typeof(cartesian_product_type(*others))).new(capacity)
    each_cartesian(*others) do |product|
      result << product
    end
    result
  end

  private def cartesian_product_type(*others)
    v = each_cartesian(*others).next
    raise "" if v.is_a?(Iterator::Stop)
    v
  end

  # Returns an `Array` of all ordered combinations of elements taken from each
  # of the *indexables* as `Array`s.
  # Traversal of elements starts from the last `Indexable`. If *indexables* is
  # empty, the returned product contains exactly one empty `Array`.
  #
  # `#cartesian_product` is preferred over this class method when the quantity
  # of *indexables* is known in advance.
  #
  # ```
  # Indexable.cartesian_product([[1, 2, 3], [4, 5]]) # => [[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]]
  # ```
  def self.cartesian_product(indexables : Indexable(Indexable))
    capacity = indexables.product(&.size)
    result = Array(Array(typeof(Enumerable.element_type Enumerable.element_type indexables))).new(capacity)
    each_cartesian(indexables) do |product|
      result << product
    end
    result
  end

  # Yields each ordered combination of the elements taken from each of `self`
  # and *others* as a `Tuple`.
  # Traversal of elements starts from the last `Indexable` argument.
  #
  # ```
  # ["Alice", "Bob"].each_cartesian({1, 2, 3}) do |name, n|
  #   puts "#{n}. #{name}"
  # end
  # ```
  #
  # Prints
  #
  # ```text
  # 1. Alice
  # 2. Alice
  # 3. Alice
  # 1. Bob
  # 2. Bob
  # 3. Bob
  # ```
  def each_cartesian(*others : Indexable, &block)
    Indexable.each_cartesian_impl(self, *others) { |v| yield v }
  end

  protected def self.each_cartesian_impl(*indexables : *U, &block) forall U
    lens = indexables.map &.size
    return if lens.any? &.zero?

    n = indexables.size
    indices = Array.new(n, 0)
    indices[-1] -= 1

    while true
      i = n - 1
      indices[i] += 1

      while indices[i] >= lens[i]
        indices[i] = 0
        i -= 1
        return if i < 0
        indices[i] += 1
      end

      {% begin %}
        yield Tuple.new(
          {% for i in 0...U.size %}
            indexables[{{ i }}].unsafe_fetch(indices[{{ i }}]),
          {% end %}
        )
      {% end %}
    end
  end

  # Yields each ordered combination of the elements taken from each of the
  # *indexables* as `Array`s.
  # Traversal of elements starts from the last `Indexable`. If *indexables* is
  # empty, yields an empty `Array` exactly once.
  #
  # `#each_cartesian` is preferred over this class method when the quantity of
  # *indexables* is known in advance.
  #
  # ```
  # Indexable.each_cartesian([%w[Alice Bob Carol], [1, 2]]) do |name, n|
  #   puts "#{n}. #{name}"
  # end
  # ```
  #
  # Prints
  #
  # ```text
  # 1. Alice
  # 2. Alice
  # 1. Bob
  # 2. Bob
  # 1. Carol
  # 2. Carol
  # ```
  #
  # By default, a new `Array` is created and yielded for each combination.
  #
  # * If *reuse* is an `Array`, it will be reused
  # * If *reuse* is truthy, the method will create a new `Array` and reuse it
  # * If *reuse* is falsey, no `Array`s will be reused.
  #
  # This can be used to prevent many memory allocations when each combination of
  # interest is to be used in a read-only fashion.
  def self.each_cartesian(indexables : Indexable(Indexable), reuse = false, &block)
    lens = indexables.map &.size
    return if lens.any? &.zero?

    n = indexables.size
    pool = Array.new(n) { |i| indexables.unsafe_fetch(i).unsafe_fetch(0) }
    indices = Array.new(n, 0)
    reuse = Indexable(typeof(pool.first)).check_reuse(reuse, n)

    while true
      yield pool_slice(pool, n, reuse)

      i = n

      while true
        i -= 1
        return if i < 0
        indices[i] += 1
        if move_to_next = (indices[i] >= lens[i])
          indices[i] = 0
        end
        pool[i] = indexables[i].unsafe_fetch(indices[i])
        break unless move_to_next
      end
    end
  end

  # Returns an iterator that enumerates the ordered combinations of elements
  # taken from each of `self` and *others* as `Tuple`s.
  # Traversal of elements starts from the last `Indexable` argument.
  #
  # ```
  # iter = {1, 2, 3}.each_cartesian({'a', 'b'})
  # iter.next # => {1, 'a'}
  # iter.next # => {1, 'b'}
  # iter.next # => {2, 'a'}
  # iter.next # => {2, 'b'}
  # iter.next # => {3, 'a'}
  # iter.next # => {3, 'b'}
  # iter.next # => Iterator::Stop::INSTANCE
  # ```
  def each_cartesian(*others : Indexable)
    Indexable.each_cartesian_impl(self, *others)
  end

  protected def self.each_cartesian_impl(*indexables : *U) forall U
    {% begin %}
      CartesianProductIteratorT(U, Tuple(
        {% for i in 0...U.size %}
          typeof(Enumerable.element_type(indexables[{{ i }}])),
        {% end %}
      )).new(indexables)
    {% end %}
  end

  # Returns an iterator that enumerates the ordered combinations of elements
  # taken from the *indexables* as `Array`s.
  # Traversal of elements starts from the last `Indexable`. If *indexables* is
  # empty, the returned iterator produces one empty `Array`, then stops.
  #
  # `#each_cartesian` is preferred over this class method when the quantity of
  # *indexables* is known in advance.
  #
  # ```
  # iter = Indexable.each_cartesian([%w[N S], %w[E W]])
  # iter.next # => ["N", "E"]
  # iter.next # => ["N", "W"]
  # iter.next # => ["S", "E"]
  # iter.next # => ["S", "W"]
  # iter.next # => Iterator::Stop::INSTANCE
  # ```
  #
  # By default, a new `Array` is created and returned for each combination.
  #
  # * If *reuse* is an `Array`, it will be reused
  # * If *reuse* is truthy, the method will create a new `Array` and reuse it
  # * If *reuse* is falsey, no `Array`s will be reused.
  #
  # This can be used to prevent many memory allocations when each combination of
  # interest is to be used in a read-only fashion.
  def self.each_cartesian(indexables : Indexable(Indexable), reuse = false)
    CartesianProductIteratorN(typeof(indexables), typeof(Enumerable.element_type Enumerable.element_type indexables)).new(indexables, reuse)
  end

  private class CartesianProductIteratorT(Is, Ts)
    include Iterator(Ts)

    @indices : Array(Int32)

    def initialize(@indexables : Is)
      @indices = Array.new(@indexables.size, 0)
      @indices[-1] -= 1
    end

    def next
      i = @indices.size - 1
      @indices[i] += 1

      while @indices[i] >= @indexables[i].size
        @indices[i] = 0
        i -= 1
        return stop if i < 0
        @indices[i] += 1
      end

      {% begin %}
        Ts.new(
          {% for i in 0...Is.size %}
            @indexables[{{ i }}].unsafe_fetch(@indices[{{ i }}]),
          {% end %}
        )
      {% end %}
    end
  end

  private class CartesianProductIteratorN(Is, T)
    include Iterator(Array(T))

    @indices : Array(Int32)
    @pool : Array(T)
    @reuse : Array(T)?

    def initialize(@indexables : Is, reuse)
      n = @indexables.size
      @pool = Array.new(n) { |i| indexables.unsafe_fetch(i).unsafe_fetch(0) }
      @indices = Array.new({1, n}.max, 0)
      @indices[-1] -= 1

      if reuse
        if reuse.is_a?(Array(T))
          @reuse = reuse
        else
          @reuse = Array(T).new(n)
        end
      end
    end

    def next
      if @indexables.empty?
        return stop if @indices[-1] == 0
        @indices[-1] += 1
        return pool_slice(@pool, 0, @reuse)
      end

      i = @indices.size - 1
      @indices[i] += 1

      while @indices[i] >= @indexables[i].size
        @indices[i] = 0
        @pool[i] = @indexables[i].unsafe_fetch(0)
        i -= 1
        return stop if i < 0
        @indices[i] += 1
      end

      @pool[i] = @indexables[i].unsafe_fetch(@indices[i])
      pool_slice(@pool, @indexables.size, @reuse)
    end
  end

  # Calls the given block once for each element in `self`, passing that
  # element as a parameter.
  #
  # ```
  # a = ["a", "b", "c"]
  # a.each { |x| print x, " -- " }
  # ```
  #
  # produces:
  #
  # ```text
  # a -- b -- c --
  # ```
  def each(& : T ->)
    each_index do |i|
      yield unsafe_fetch(i)
    end
  end

  # Returns an `Iterator` for the elements of `self`.
  #
  # ```
  # a = ["a", "b", "c"]
  # iter = a.each
  # iter.next # => "a"
  # iter.next # => "b"
  # ```
  #
  # The returned iterator keeps a reference to `self`: if the array
  # changes, the returned values of the iterator change as well.
  def each
    ItemIterator(self, T).new(self)
  end

  # Calls the given block once for `count` number of elements in `self`
  # starting from index `start`, passing each element as a parameter.
  #
  # Negative indices count backward from the end of the array. (-1 is the
  # last element).
  #
  # Raises `IndexError` if the starting index is out of range.
  # Raises `ArgumentError` if `count` is a negative number.
  #
  # ```
  # array = ["a", "b", "c", "d", "e"]
  # array.each(start: 1, count: 3) { |x| print x, " -- " }
  # ```
  #
  # produces:
  #
  # ```text
  # b -- c -- d --
  # ```
  def each(*, start : Int, count : Int, & : T ->)
    each_index(start: start, count: count) do |i|
      yield unsafe_fetch(i)
    end
  end

  # Calls the given block once for all elements at indices within the given
  # `range`, passing each element as a parameter.
  #
  # Raises `IndexError` if the starting index is out of range.
  #
  # ```
  # array = ["a", "b", "c", "d", "e"]
  # array.each(within: 1..3) { |x| print x, " -- " }
  # ```
  #
  # produces:
  #
  # ```text
  # b -- c -- d --
  # ```
  def each(*, within range : Range, & : T ->)
    start, count = Indexable.range_to_index_and_count(range, size) || raise IndexError.new
    each(start: start, count: count) { |element| yield element }
  end

  # Calls the given block once for each index in `self`, passing that
  # index as a parameter.
  #
  # ```
  # a = ["a", "b", "c"]
  # a.each_index { |x| print x, " -- " }
  # ```
  #
  # produces:
  #
  # ```text
  # 0 -- 1 -- 2 --
  # ```
  def each_index(& : Int32 ->) : Nil
    i = 0
    while i < size
      yield i
      i += 1
    end
  end

  # Returns an `Iterator` for each index in `self`.
  #
  # ```
  # a = ["a", "b", "c"]
  # iter = a.each_index
  # iter.next # => 0
  # iter.next # => 1
  # ```
  #
  # The returned iterator keeps a reference to `self`. If the array
  # changes, the returned values of the iterator will change as well.
  def each_index
    IndexIterator.new(self)
  end

  # Calls the given block once for `count` number of indices in `self`
  # starting from index `start`, passing each index as a parameter.
  #
  # Negative indices count backward from the end of the array. (-1 is the
  # last element).
  #
  # Raises `IndexError` if the starting index is out of range.
  # Raises `ArgumentError` if `count` is a negative number.
  #
  # ```
  # array = ["a", "b", "c", "d", "e"]
  # array.each_index(start: -3, count: 2) { |x| print x, " -- " }
  # ```
  #
  # produces:
  #
  # ```text
  # 2 -- 3 --
  # ```
  def each_index(*, start : Int, count : Int, &)
    # We cannot use `normalize_start_and_count` here because `self` may be
    # mutated to contain enough elements during iteration even if there weren't
    # initially `count` elements.
    raise ArgumentError.new "Negative count: #{count}" if count < 0

    start += size if start < 0
    raise IndexError.new unless 0 <= start <= size

    i = start
    # `count` and size comparison must be done every iteration because
    # `self` can mutate in the block.
    while i < Math.min(start + count, size)
      yield i
      i += 1
    end
    self
  end

  # Optimized version of `Enumerable#join` that performs better when
  # all of the elements in this indexable are strings: the total string
  # bytesize to return can be computed before creating the final string,
  # which performs better because there's no need to do reallocations.
  def join(separator : String | Char | Number = "") : String
    return "" if empty?

    {% if T == String %}
      join_strings(separator)
    {% elsif String < T %}
      if all?(String)
        join_strings(separator)
      else
        super(separator)
      end
    {% else %}
      super(separator)
    {% end %}
  end

  private def join_strings(separator)
    separator = separator.to_s

    # The total bytesize of the string to return is:
    length =
      ((self.size - 1) * separator.bytesize) + # the bytesize of all separators
        self.sum(&.to_s.bytesize)              # the bytesize of all the elements

    String.new(length) do |buffer|
      # Also compute the UTF-8 size if we can
      size = 0
      size_known = true

      each_with_index do |elem, i|
        # elem is guaranteed to be a String, but the compiler doesn't know this
        # if we enter via the all?(String) branch.
        elem = elem.to_s

        # Copy separator to buffer
        if i != 0
          buffer.copy_from(separator.to_unsafe, separator.bytesize)
          buffer += separator.bytesize
        end

        # Copy element to buffer
        buffer.copy_from(elem.to_unsafe, elem.bytesize)
        buffer += elem.bytesize

        # Check whether we'll know the final UTF-8 size
        if elem.size_known?
          size += elem.size
        else
          size_known = false
        end
      end

      # Add size of all separators
      size += (self.size - 1) * separator.size if size_known

      {length, size_known ? size : 0}
    end
  end

  # Returns an `Array` with the results of running *block* against each element of the collection.
  #
  # ```
  # {1, 2, 3}.to_a { |i| i * 2 } # => [2, 4, 6]
  # ```
  def to_a(& : T -> U) : Array(U) forall U
    ary = Array(U).new(size)
    each { |e| ary << yield e }
    ary
  end

  # Returns `true` if `self` is empty, `false` otherwise.
  #
  # ```
  # ([] of Int32).empty? # => true
  # ([1]).empty?         # => false
  # ```
  def empty? : Bool
    size == 0
  end

  # Optimized version of `equals?` used when `other` is also an `Indexable`.
  def equals?(other : Indexable, &) : Bool
    return false if size != other.size
    each_with_index do |item, i|
      return false unless yield(item, other.unsafe_fetch(i))
    end
    true
  end

  # Determines if `self` equals *other* according to a comparison
  # done by the given block.
  #
  # If `self`'s size is the same as *other*'s size, this method yields
  # elements from `self` and *other* in tandem: if the block returns true
  # for all of them, this method returns `true`. Otherwise it returns `false`.
  #
  # ```
  # a = [1, 2, 3]
  # b = ["a", "ab", "abc"]
  # a.equals?(b) { |x, y| x == y.size } # => true
  # a.equals?(b) { |x, y| x == y }      # => false
  # ```
  def equals?(other, &)
    return false if size != other.size
    each_with_index do |item, i|
      return false unless yield(item, other[i])
    end
    true
  end

  # :inherit:
  def first(&)
    size == 0 ? yield : unsafe_fetch(0)
  end

  # See `Object#hash(hasher)`
  def hash(hasher)
    hasher = size.hash(hasher)
    each do |elem|
      hasher = elem.hash(hasher)
    end
    hasher
  end

  # Returns the index of the first appearance of *object* in `self`
  # starting from the given *offset*, or `nil` if *object* is not in `self`.
  #
  # ```
  # [1, 2, 3, 1, 2, 3].index(2, offset: 2) # => 4
  # ```
  def index(object, offset : Int = 0)
    index(offset) { |e| e == object }
  end

  # Returns the index of the first object in `self` for which the block
  # is truthy, starting from the given *offset*, or `nil` if no match
  # is found.
  #
  # ```
  # [1, 2, 3, 1, 2, 3].index(offset: 2) { |x| x < 2 } # => 3
  # ```
  def index(offset : Int = 0, & : T ->)
    offset += size if offset < 0
    return nil if offset < 0

    offset.upto(size - 1) do |i|
      if yield unsafe_fetch(i)
        return i
      end
    end
    nil
  end

  # Returns the index of the first appearance of *obj* in `self`
  # starting from the given *offset*. Raises `Enumerable::NotFoundError` if
  # *obj* is not in `self`.
  #
  # ```
  # [1, 2, 3, 1, 2, 3].index!(2, offset: 2) # => 4
  # ```
  def index!(obj, offset : Int = 0)
    index(obj, offset) || raise Enumerable::NotFoundError.new
  end

  # Returns the index of the first object in `self` for which the block
  # is truthy, starting from the given *offset*. Raises
  # `Enumerable::NotFoundError` if no match is found.
  #
  # ```
  # [1, 2, 3, 1, 2, 3].index!(offset: 2) { |x| x < 2 } # => 3
  # ```
  def index!(offset : Int = 0, & : T ->)
    index(offset) { |e| yield e } || raise Enumerable::NotFoundError.new
  end

  # Returns the first element in the indexable for which the passed block
  # is truthy, starting from the given *offset*.
  #
  # Accepts an optional parameter *if_none*, to set what gets returned if
  # no element is found (defaults to `nil`).
  #
  # ```
  # [1, 2, 3, 4].find { |i| i > 2 }        # => 3
  # [1, 2, 3, 4].find(-1) { |i| i > 8 }    # => -1
  # [1, 2, 3, 4].find(-1, 2) { |i| i < 2 } # => -1
  # ```
  def find(if_none = nil, *, offset : Int, & : T ->)
    offset += size if offset < 0
    return if_none if offset < 0

    offset.upto(size - 1) do |i|
      elem = unsafe_fetch(i)
      if yield elem
        return elem
      end
    end

    if_none
  end

  # :ditto:
  def find(if_none, _offset offset : Int, & : T ->)
    find(if_none, offset: offset) { |e| yield e }
  end

  # Returns the first element in the indexable for which the passed block
  # is truthy, starting from the given *offset*.
  # Raises `Enumerable::NotFoundError` if there is no element for which the block is truthy.
  #
  # ```
  # [1, 2, 3, 4].find! { |i| i > 2 }     # => 3
  # [1, 2, 3, 4].find!(3) { |i| i > 2 }  # => 4
  # [1, 2, 3, 4].find! { |i| i > 8 }     # => raises Enumerable::NotFoundError
  # [1, 2, 3, 4].find!(-5) { |i| i > 2 } # => raises Enumerable::NotFoundError
  # ```
  def find!(offset : Int = 0, & : T ->)
    find(offset: offset) { |i| yield i } || raise Enumerable::NotFoundError.new
  end

  # Returns the last element of `self` if it's not empty, or raises `IndexError`.
  #
  # ```
  # ([1, 2, 3]).last   # => 3
  # ([] of Int32).last # raises IndexError
  # ```
  def last : T
    last { raise IndexError.new }
  end

  # Returns the last element of `self` if it's not empty, or the given block's value.
  #
  # ```
  # ([1, 2, 3]).last { 4 }   # => 3
  # ([] of Int32).last { 4 } # => 4
  # ```
  def last(&)
    size == 0 ? yield : unsafe_fetch(size - 1)
  end

  # Returns the last element of `self` if it's not empty, or `nil`.
  #
  # ```
  # ([1, 2, 3]).last?   # => 3
  # ([] of Int32).last? # => nil
  # ```
  def last? : T?
    last { nil }
  end

  # Same as `#each`, but works in reverse.
  def reverse_each(& : T ->) : Nil
    (size - 1).downto(0) do |i|
      yield unsafe_fetch(i)
    end
  end

  # Returns an `Iterator` over the elements of `self` in reverse order.
  def reverse_each
    ReverseItemIterator(self, T).new(self)
  end

  # Returns the index of the last appearance of *value* in `self`, or
  # `nil` if the value is not in `self`.
  #
  # If *offset* is given, it defines the position to _end_ the search
  # (elements beyond this point are ignored).
  #
  # ```
  # [1, 2, 3, 2, 3].rindex(2)            # => 3
  # [1, 2, 3, 2, 3].rindex(2, offset: 2) # => 1
  # ```
  def rindex(value, offset = size - 1)
    rindex(offset) { |elem| elem == value }
  end

  # :ditto:
  #
  # Raises `Enumerable::NotFoundError` if *value* is not in `self`.
  def rindex!(value, offset = size - 1)
    rindex(value, offset) || raise Enumerable::NotFoundError.new
  end

  # Returns the index of the first object in `self` for which the block
  # is truthy, starting from the last object, or `nil` if no match
  # is found.
  #
  # If *offset* is given, the search starts from that index towards the
  # first elements in `self`.
  #
  # ```
  # [1, 2, 3, 2, 3].rindex { |x| x < 3 }            # => 3
  # [1, 2, 3, 2, 3].rindex(offset: 2) { |x| x < 3 } # => 1
  # ```
  def rindex(offset = size - 1, & : T ->)
    offset += size if offset < 0
    return nil if offset >= size

    offset.downto(0) do |i|
      if yield unsafe_fetch(i)
        return i
      end
    end
    nil
  end

  # :ditto:
  #
  # Raises `Enumerable::NotFoundError` if no match is found.
  def rindex!(offset = size - 1, & : T ->)
    rindex(offset) { |e| yield e } || raise Enumerable::NotFoundError.new
  end

  # Optimized version of `Enumerable#sample` that runs in O(1) time.
  #
  # ```
  # a = [1, 2, 3]
  # a.sample # => 3
  # a.sample # => 1
  # ```
  #
  # Uses the *random* instance when provided if the randomness needs to be
  # controlled or to follow some traits. For example the following sample will
  # always return the same value:
  #
  # ```
  # a = [1, 2, 3]
  # a.sample(Random.new(1)) # => 2
  # a.sample(Random.new(1)) # => 2
  # ```
  def sample(random : Random? = nil)
    raise IndexError.new("Can't sample empty collection") if size == 0
    rng = random || Random.thread_default
    unsafe_fetch(rng.rand(size))
  end

  # :inherit:
  #
  # If `self` is not empty and `n` is equal to 1, calls `sample(random)` exactly
  # once. Thus, *random* will be left in a different state compared to the
  # implementation in `Enumerable`.
  def sample(n : Int, random : Random? = nil) : Array(T)
    return super unless n == 1

    if empty?
      [] of T
    else
      [sample(random)]
    end
  end

  # Returns a `Tuple` populated with the elements at the given indexes.
  # Raises `IndexError` if any index is invalid.
  #
  # ```
  # ["a", "b", "c", "d"].values_at(0, 2) # => {"a", "c"}
  # ```
  def values_at(*indexes : Int)
    indexes.map { |index| self[index] }
  end

  private def check_index_out_of_bounds(index)
    check_index_out_of_bounds(index) { raise IndexError.new }
  end

  private def check_index_out_of_bounds(index, &)
    index += size if index < 0
    if 0 <= index < size
      index
    else
      yield
    end
  end

  private def normalize_start_and_count(start, count)
    Indexable.normalize_start_and_count(start, count, size)
  end

  private def normalize_start_and_count(start, count, &)
    Indexable.normalize_start_and_count(start, count, size) { yield }
  end

  # :nodoc:
  def self.normalize_start_and_count(start, count, collection_size, &)
    raise ArgumentError.new "Negative count: #{count}" if count < 0
    start += collection_size if start < 0
    if 0 <= start <= collection_size
      count = {count, collection_size - start}.min
      {start, count}
    else
      yield
    end
  end

  # :nodoc:
  def self.normalize_start_and_count(start, count, collection_size)
    normalize_start_and_count(start, count, collection_size) { raise IndexError.new }
  end

  # :nodoc:
  def self.range_to_index_and_count(range, collection_size)
    start_index = range.begin
    if start_index.nil?
      start_index = 0
    else
      start_index += collection_size if start_index < 0

      return nil if start_index < 0
    end

    count = 0

    end_index = range.end
    if end_index.nil?
      if start_index <= collection_size
        count = collection_size - start_index
      end
    else
      if end_index < 0
        end_index += collection_size
      end

      # must be careful with computation here, since we may be dealing with
      # unsigned ints or ints close to MAX.
      if end_index >= start_index
        count = end_index - start_index
        count += 1 unless range.excludes_end? || collection_size < count
      end
    end

    {start_index, count}
  end

  # Returns an `Array` with all possible permutations of *size* of `self`.
  #
  # ```
  # a = [1, 2, 3]
  # a.permutations    # => [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
  # a.permutations(1) # => [[1],[2],[3]]
  # a.permutations(2) # => [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]
  # a.permutations(3) # => [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
  # a.permutations(0) # => [[]]
  # a.permutations(4) # => []
  # ```
  def permutations(size : Int = self.size) : Array(Array(T))
    ary = [] of Array(T)
    each_permutation(size) do |a|
      ary << a
    end
    ary
  end

  # Yields each possible permutation of *size* of `self`.
  #
  # ```
  # a = [1, 2, 3]
  # sums = [] of Int32
  # a.each_permutation(2) { |p| sums << p.sum } # => nil
  # sums                                        # => [3, 4, 3, 5, 4, 5]
  # ```
  #
  # By default, a new array is created and yielded for each permutation.
  # If *reuse* is given, the array can be reused: if *reuse* is
  # an `Array`, this array will be reused; if *reuse* if truthy,
  # the method will create a new array and reuse it. This can be
  # used to prevent many memory allocations when each slice of
  # interest is to be used in a read-only fashion.
  def each_permutation(size : Int = self.size, reuse = false, &) : Nil
    n = self.size
    return if size > n

    raise ArgumentError.new("Size must be positive") if size < 0

    pool = dup_as_array(self)
    reuse = Indexable(T).check_reuse(reuse, size)
    cycles = (n - size + 1..n).to_a.reverse!
    yield pool_slice(pool, size, reuse)

    while true
      stop = true
      i = size - 1
      while i >= 0
        ci = (cycles[i] -= 1)
        if ci == 0
          e = pool[i]
          (i + 1).upto(n - 1) { |j| pool[j - 1] = pool[j] }
          pool[n - 1] = e
          cycles[i] = n - i
        else
          pool.swap i, -ci
          yield pool_slice(pool, size, reuse)
          stop = false
          break
        end
        i -= 1
      end

      return if stop
    end
  end

  # Returns an `Iterator` over each possible permutation of *size* of `self`.
  #
  # ```
  # iter = [1, 2, 3].each_permutation
  # iter.next # => [1, 2, 3]
  # iter.next # => [1, 3, 2]
  # iter.next # => [2, 1, 3]
  # iter.next # => [2, 3, 1]
  # iter.next # => [3, 1, 2]
  # iter.next # => [3, 2, 1]
  # iter.next # => #<Iterator::Stop>
  # ```
  #
  # By default, a new array is created and returned for each permutation.
  # If *reuse* is given, the array can be reused: if *reuse* is
  # an `Array`, this array will be reused; if *reuse* if truthy,
  # the method will create a new array and reuse it. This can be
  # used to prevent many memory allocations when each slice of
  # interest is to be used in a read-only fashion.
  def each_permutation(size : Int = self.size, reuse = false)
    raise ArgumentError.new("Size must be positive") if size < 0

    PermutationIterator(self, T).new(self, size.to_i, Indexable(T).check_reuse(reuse, size))
  end

  # Returns an `Array` with all possible combinations of *size* of `self`.
  #
  # ```
  # a = [1, 2, 3]
  # a.combinations    # => [[1, 2, 3]]
  # a.combinations(1) # => [[1], [2], [3]]
  # a.combinations(2) # => [[1, 2], [1, 3], [2, 3]]
  # a.combinations(3) # => [[1, 2, 3]]
  # a.combinations(0) # => [[]]
  # a.combinations(4) # => []
  # ```
  def combinations(size : Int = self.size)
    ary = [] of Array(T)
    each_combination(size) do |a|
      ary << a
    end
    ary
  end

  # Yields each possible combination of *size* of `self`.
  #
  # ```
  # a = [1, 2, 3]
  # sums = [] of Int32
  # a.each_combination(2) { |p| sums << p.sum } # => nil
  # sums                                        # => [3, 4, 5]
  # ```
  #
  # By default, a new array is created and yielded for each combination.
  # If *reuse* is given, the array can be reused: if *reuse* is
  # an `Array`, this array will be reused; if *reuse* if truthy,
  # the method will create a new array and reuse it. This can be
  # used to prevent many memory allocations when each slice of
  # interest is to be used in a read-only fashion.
  def each_combination(size : Int = self.size, reuse = false, &) : Nil
    n = self.size
    return if size > n
    raise ArgumentError.new("Size must be positive") if size < 0

    reuse = Indexable(T).check_reuse(reuse, size)
    copy = self.dup
    pool = dup_as_array(self)

    indices = (0...size).to_a

    yield pool_slice(pool, size, reuse)

    while true
      stop = true
      i = size - 1
      while i >= 0
        if indices[i] != i + n - size
          stop = false
          break
        end
        i -= 1
      end

      return if stop

      indices[i] += 1
      pool[i] = copy[indices[i]]

      (i + 1).upto(size - 1) do |j|
        indices[j] = indices[j - 1] + 1
        pool[j] = copy[indices[j]]
      end

      yield pool_slice(pool, size, reuse)
    end
  end

  # Returns an `Iterator` over each possible combination of *size* of `self`.
  #
  # ```
  # iter = [1, 2, 3, 4].each_combination(3)
  # iter.next # => [1, 2, 3]
  # iter.next # => [1, 2, 4]
  # iter.next # => [1, 3, 4]
  # iter.next # => [2, 3, 4]
  # iter.next # => #<Iterator::Stop>
  # ```
  #
  # By default, a new array is created and returned for each combination.
  # If *reuse* is given, the array can be reused: if *reuse* is
  # an `Array`, this array will be reused; if *reuse* if truthy,
  # the method will create a new array and reuse it. This can be
  # used to prevent many memory allocations when each slice of
  # interest is to be used in a read-only fashion.
  def each_combination(size : Int = self.size, reuse = false)
    raise ArgumentError.new("Size must be positive") if size < 0

    CombinationIterator(self, T).new(self, size.to_i, Indexable(T).check_reuse(reuse, size))
  end

  # Returns an `Array` with all possible combinations with repeated elements of
  # *size* of `self`.
  #
  # ```
  # a = [1, 2, 3]
  #
  # pp a.repeated_combinations
  # pp a.repeated_combinations(2)
  # ```
  #
  # produces:
  #
  # ```text
  # [[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]]
  # [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]]
  # ```
  def repeated_combinations(size : Int = self.size) : Array(Array(T))
    ary = [] of Array(T)
    each_repeated_combination(size) do |a|
      ary << a
    end
    ary
  end

  # Yields each possible combination with repeated elements of *size* of
  # `self`.
  #
  # ```
  # a = [1, 2, 3]
  # sums = [] of Int32
  # a.each_repeated_combination(2) { |p| sums << p.sum } # => nil
  # sums                                                 # => [2, 3, 4, 4, 5, 6]
  # ```
  #
  # By default, a new array is created and yielded for each combination.
  # If *reuse* is given, the array can be reused: if *reuse* is
  # an `Array`, this array will be reused; if *reuse* if truthy,
  # the method will create a new array and reuse it. This can be
  # used to prevent many memory allocations when each slice of
  # interest is to be used in a read-only fashion.
  def each_repeated_combination(size : Int = self.size, reuse = false, &) : Nil
    n = self.size
    return if size > n && n == 0
    raise ArgumentError.new("Size must be positive") if size < 0

    reuse = Indexable(T).check_reuse(reuse, size)
    copy = self.dup
    indices = Array.new(size, 0)
    pool = indices.map { |i| copy[i] }

    yield pool_slice(pool, size, reuse)

    while true
      stop = true

      i = size - 1
      while i >= 0
        if indices[i] != n - 1
          stop = false
          break
        end
        i -= 1
      end
      return if stop

      ii = indices[i] + 1
      tmp = copy[ii]
      indices.fill(i, size - i) { ii }
      pool.fill(i, size - i) { tmp }

      yield pool_slice(pool, size, reuse)
    end
  end

  # Returns an `Iterator` over each possible combination with repeated elements
  # of *size* of `self`.
  #
  # ```
  # iter = [1, 2, 3].each_repeated_combination(2)
  # iter.next # => [1, 1]
  # iter.next # => [1, 2]
  # iter.next # => [1, 3]
  # iter.next # => [2, 2]
  # iter.next # => [2, 3]
  # iter.next # => [3, 3]
  # iter.next # => #<Iterator::Stop>
  # ```
  #
  # By default, a new array is created and returned for each combination.
  # If *reuse* is given, the array can be reused: if *reuse* is
  # an `Array`, this array will be reused; if *reuse* if truthy,
  # the method will create a new array and reuse it. This can be
  # used to prevent many memory allocations when each slice of
  # interest is to be used in a read-only fashion.
  def each_repeated_combination(size : Int = self.size, reuse = false)
    raise ArgumentError.new("Size must be positive") if size < 0

    RepeatedCombinationIterator(self, T).new(self, size.to_i, Indexable(T).check_reuse(reuse, size))
  end

  protected def self.check_reuse(reuse, size)
    if reuse
      unless reuse.is_a?(Array)
        reuse = Array(T).new(size)
      end
    else
      reuse = nil
    end
    reuse
  end

  private class ItemIterator(A, T)
    include Iterator(T)

    def initialize(@array : A, @index = 0)
    end

    def next
      if @index >= @array.size
        stop
      else
        value = @array[@index]
        @index += 1
        value
      end
    end
  end

  private class ReverseItemIterator(A, T)
    include Iterator(T)

    def initialize(@array : A, @index : Int32 = array.size - 1)
    end

    def next
      if @index < 0
        stop
      else
        value = @array[@index]
        @index -= 1
        value
      end
    end
  end

  private class IndexIterator(A)
    include Iterator(Int32)

    def initialize(@array : A, @index = 0)
    end

    def next
      if @index >= @array.size
        stop
      else
        value = @index
        @index += 1
        value
      end
    end
  end

  private class PermutationIterator(A, T)
    include Iterator(Array(T))

    @size : Int32
    @n : Int32
    @cycles : Array(Int32)
    @pool : Array(T)
    @stop : Bool
    @i : Int32
    @first : Bool
    @reuse : Array(T)?

    def initialize(a : A, @size, @reuse : Array(T)?)
      @n = a.size
      @cycles = (@n - @size + 1..@n).to_a.reverse!
      @pool = dup_as_array(a)
      @stop = @size > @n
      @i = @size - 1
      @first = true
    end

    def next
      return stop if @stop

      if @first
        @first = false
        return pool_slice(@pool, @size, @reuse)
      end

      while @i >= 0
        ci = (@cycles[@i] -= 1)
        if ci == 0
          e = @pool[@i]
          (@i + 1).upto(@n - 1) { |j| @pool[j - 1] = @pool[j] }
          @pool[@n - 1] = e
          @cycles[@i] = @n - @i
        else
          @pool.swap @i, -ci
          value = pool_slice(@pool, @size, @reuse)
          @i = @size - 1
          return value
        end
        @i -= 1
      end

      @stop = true
      stop
    end
  end

  private class CombinationIterator(A, T)
    include Iterator(Array(T))

    @size : Int32
    @n : Int32
    @copy : Array(T)
    @pool : Array(T)
    @indices : Array(Int32)
    @stop : Bool
    @i : Int32
    @first : Bool
    @reuse : Array(T)?

    def initialize(a : A, @size, @reuse : Array(T)?)
      @n = a.size
      @copy = a.dup
      @pool = dup_as_array(a)
      @indices = (0...@size).to_a
      @stop = @size > @n
      @i = @size - 1
      @first = true
    end

    def next
      return stop if @stop

      if @first
        @first = false
        return pool_slice(@pool, @size, @reuse)
      end

      while @i >= 0
        if @indices[@i] != @i + @n - @size
          @indices[@i] += 1
          @pool[@i] = @copy[@indices[@i]]

          (@i + 1).upto(@size - 1) do |j|
            @indices[j] = @indices[j - 1] + 1
            @pool[j] = @copy[@indices[j]]
          end

          value = pool_slice(@pool, @size, @reuse)
          @i = @size - 1
          return value
        end
        @i -= 1
      end

      @stop = true
      stop
    end
  end

  private class RepeatedCombinationIterator(A, T)
    include Iterator(Array(T))

    @size : Int32
    @n : Int32
    @copy : A
    @indices : Array(Int32)
    @pool : Array(T)
    @stop : Bool
    @i : Int32
    @first : Bool
    @reuse : Array(T)?

    def initialize(array : A, @size, @reuse : Array(T)?)
      @n = array.size
      @copy = array.dup
      @indices = Array.new(@size, 0)
      @pool = @indices.map { |i| @copy[i] }
      @stop = false
      @i = @size - 1
      @first = true
    end

    def next
      return stop if @stop

      if @first
        @first = false
        return pool_slice(@pool, @size, @reuse)
      end

      while @i >= 0
        if @indices[@i] != @n - 1
          ii = @indices[@i] + 1
          tmp = @copy[ii]
          @indices.fill(@i, @size - @i) { ii }
          @pool.fill(@i, @size - @i) { tmp }

          value = pool_slice(@pool, @size, @reuse)
          @i = @size - 1
          return value
        end
        @i -= 1
      end

      @stop = true
      stop
    end
  end
end

private def pool_slice(pool, size, reuse)
  if reuse
    reuse.clear
    size.times { |i| reuse << pool[i] }
    reuse
  else
    pool[0, size]
  end
end

private def dup_as_array(a)
  a.is_a?(Array) ? a.dup : a.to_a
end

require "./indexable/*"
