class Array
  ##
  # call-seq:
  #    ary.uniq!                -> ary or nil
  #    ary.uniq! { |item| ... } -> ary or nil
  #
  # Removes duplicate elements from `self`.
  # Returns `nil` if no changes are made (that is, no
  # duplicates are found).
  #
  #    a = [ "a", "a", "b", "b", "c" ]
  #    a.uniq!   #=> ["a", "b", "c"]
  #    b = [ "a", "b", "c" ]
  #    b.uniq!   #=> nil
  #    c = [["student","sam"], ["student","george"], ["teacher","matz"]]
  #    c.uniq! { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
  #
  def uniq!(&block)
    if block
      hash = {}
      result = []
      self.each do |val|
        key = block.call(val)
        unless hash.key?(key)
          hash[key] = true
          result << val
        end
      end

      if result.size == self.size
        nil
      else
        self.replace(result)
      end
    else
      __uniq!
    end
  end

  ##
  # call-seq:
  #    ary.uniq                -> new_ary
  #    ary.uniq { |item| ... } -> new_ary
  #
  # Returns a new array by removing duplicate values in `self`.
  #
  #    a = [ "a", "a", "b", "b", "c" ]
  #    a.uniq   #=> ["a", "b", "c"]
  #
  #    b = [["student","sam"], ["student","george"], ["teacher","matz"]]
  #    b.uniq { |s| s.first } # => [["student", "sam"], ["teacher", "matz"]]
  #
  def uniq(&block)
    if block
      ary = self.dup
      ary.uniq!(&block)
      ary
    else
      __uniq
    end
  end

  # for efficiency
  def reverse_each(&block)
    return to_enum(:reverse_each) unless block

    i = self.size - 1
    while i>=0
      block.call(self[i])
      i -= 1
    end
    self
  end

  ##
  #  call-seq:
  #     ary.fetch(index)                    -> obj
  #     ary.fetch(index, default)           -> obj
  #     ary.fetch(index) { |index| block }  -> obj
  #
  #  Tries to return the element at position `index`, but throws an IndexError
  #  exception if the referenced `index` lies outside of the array bounds. This
  #  error can be prevented by supplying a second argument, which will act as a
  #  `default` value.
  #
  #  Alternatively, if a block is given it will only be executed when an
  #  invalid `index` is referenced.
  #
  #  Negative values of `index` count from the end of the array.
  #
  #     a = [ 11, 22, 33, 44 ]
  #     a.fetch(1)               #=> 22
  #     a.fetch(-1)              #=> 44
  #     a.fetch(4, 'cat')        #=> "cat"
  #     a.fetch(100) { |i| puts "#{i} is out of bounds" }
  #                              #=> "100 is out of bounds"
  #

  def fetch(n, ifnone=NONE, &block)
    #warn "block supersedes default value argument" if !n.nil? && ifnone != NONE && block

    if block
      # Block case: use shared index helper + Ruby block handling
      normalized_index = __normalize_index(n)
      if normalized_index
        self[normalized_index]
      else
        block.call(n)
      end
    else
      # Fast C implementation for non-block cases
      __fetch(n, ifnone, NONE)
    end
  end

  ##
  #  call-seq:
  #     ary.fill(obj)                                 -> ary
  #     ary.fill(obj, start [, length])               -> ary
  #     ary.fill(obj, range )                         -> ary
  #     ary.fill { |index| block }                    -> ary
  #     ary.fill(start [, length] ) { |index| block } -> ary
  #     ary.fill(range) { |index| block }             -> ary
  #
  #  The first three forms set the selected elements of `self` (which
  #  may be the entire array) to `obj`.
  #
  #  A `start` of `nil` is equivalent to zero.
  #
  #  A `length` of `nil` is equivalent to the length of the array.
  #
  #  The last three forms fill the array with the value of the given block,
  #  which is passed the absolute index of each element to be filled.
  #
  #  Negative values of `start` count from the end of the array, where +-1+ is
  #  the last element.
  #
  #     a = [ "a", "b", "c", "d" ]
  #     a.fill("x")              #=> ["x", "x", "x", "x"]
  #     a.fill("w", -1)          #=> ["x", "x", "x", "w"]
  #     a.fill("z", 2, 2)        #=> ["x", "x", "z", "z"]
  #     a.fill("y", 0..1)        #=> ["y", "y", "z", "z"]
  #     a.fill { |i| i*i }       #=> [0, 1, 4, 9]
  #     a.fill(-2) { |i| i*i*i } #=> [0, 1, 8, 27]
  #     a.fill(1, 2) { |i| i+1 } #=> [0, 2, 3, 27]
  #     a.fill(0..1) { |i| i+1 } #=> [1, 2, 3, 27]
  #

  def fill(arg0=nil, arg1=nil, arg2=nil, &block)
    if arg0.nil? && arg1.nil? && arg2.nil? && !block
      raise ArgumentError, "wrong number of arguments (given 0, expected 1..3)"
    end

    # Use shared C argument parser for all cases
    start, length = __fill_parse_arg(arg0, arg1, arg2, &block)

    if block
      # Block-based filling in Ruby
      i = start
      while i < start + length
        self[i] = block.call(i)
        i += 1
      end
    else
      # Use fast C implementation for value filling
      __fill_exec(start, length, arg0)
    end

    self
  end

  ##
  #  call-seq:
  #     ary.delete_if { |item| block }  -> ary
  #     ary.delete_if                   -> Enumerator
  #
  #  Deletes every element of `self` for which block evaluates to `true`.
  #
  #  The array is changed instantly every time the block is called, not after
  #  the iteration is over.
  #
  #  See also Array#reject!
  #
  #  If no block is given, an Enumerator is returned instead.
  #
  #     scores = [ 97, 42, 75 ]
  #     scores.delete_if {|score| score < 80 }   #=> [97]

  def delete_if(&block)
    return to_enum(:delete_if) unless block

    result = []
    idx = 0
    len = size
    while idx < len
      elem = self[idx]
      result << elem unless block.call(elem)
      idx += 1
    end

    self.replace(result)
  end

  ##
  #  call-seq:
  #     ary.reject! { |item| block }  -> ary or nil
  #     ary.reject!                   -> Enumerator
  #
  #  Equivalent to Array#delete_if, deleting elements from `self` for which the
  #  block evaluates to `true`, but returns `nil` if no changes were made.
  #
  #  The array is changed instantly every time the block is called, not after
  #  the iteration is over.
  #
  #  See also Enumerable#reject and Array#delete_if.
  #
  #  If no block is given, an Enumerator is returned instead.

  def reject!(&block)
    return to_enum(:reject!) unless block

    result = []
    idx = 0
    len = size
    while idx < len
      elem = self[idx]
      result << elem unless block.call(elem)
      idx += 1
    end

    return nil if len == result.size

    self.replace(result)
  end



  ##
  #  call-seq:
  #     ary.bsearch {|x| block }  -> elem
  #
  #  By using binary search, finds a value from this array which meets
  #  the given condition in O(log n) where n is the size of the array.
  #
  #  You can use this method in two use cases: a find-minimum mode and
  #  a find-any mode. In either case, the elements of the array must be
  #  monotone (or sorted) with respect to the block.
  #
  #  In find-minimum mode (this is a good choice for typical use case),
  #  the block must return true or false, and there must be an index i
  #  (0 <= i <= ary.size) so that:
  #
  #  - the block returns false for any element whose index is less than
  #    i, and
  #  - the block returns true for any element whose index is greater
  #    than or equal to i.
  #
  #  This method returns the i-th element. If i is equal to ary.size,
  #  it returns nil.
  #
  #     ary = [0, 4, 7, 10, 12]
  #     ary.bsearch {|x| x >=   4 } #=> 4
  #     ary.bsearch {|x| x >=   6 } #=> 7
  #     ary.bsearch {|x| x >=  -1 } #=> 0
  #     ary.bsearch {|x| x >= 100 } #=> nil
  #
  #  In find-any mode (this behaves like libc's bsearch(3)), the block
  #  must return a number, and there must be two indices i and j
  #  (0 <= i <= j <= ary.size) so that:
  #
  #  - the block returns a positive number for ary[k] if 0 <= k < i,
  #  - the block returns zero for ary[k] if i <= k < j, and
  #  - the block returns a negative number for ary[k] if
  #    j <= k < ary.size.
  #
  #  Under this condition, this method returns any element whose index
  #  is within i...j. If i is equal to j (i.e., there is no element
  #  that satisfies the block), this method returns nil.
  #
  #     ary = [0, 4, 7, 10, 12]
  #     # try to find v such that 4 <= v < 8
  #     ary.bsearch {|x| 1 - (x / 4).truncate } #=> 4 or 7
  #     # try to find v such that 8 <= v < 10
  #     ary.bsearch {|x| 4 - (x / 2).truncate } #=> nil
  #
  #  You must not mix the two modes at a time; the block must always
  #  return either true/false, or always return a number. It is
  #  undefined which value is actually picked up at each iteration.

  def bsearch(&block)
    return to_enum(:bsearch) unless block

    if idx = bsearch_index(&block)
      self[idx]
    else
      nil
    end
  end

  ##
  #  call-seq:
  #     ary.bsearch_index {|x| block }  -> int or nil
  #
  #  By using binary search, finds an index of a value from this array which
  #  meets the given condition in O(log n) where n is the size of the array.
  #
  #  It supports two modes, depending on the nature of the block and they are
  #  exactly the same as in the case of #bsearch method with the only difference
  #  being that this method returns the index of the element instead of the
  #  element itself. For more details consult the documentation for #bsearch.

  def bsearch_index(&block)
    return to_enum(:bsearch_index) unless block

    low = 0
    high = size
    satisfied = false

    while low < high
      mid = ((low+high)/2).truncate
      res = block.call(self[mid])

      case res
      when 0 # find-any mode: Found!
        return mid
      when Numeric # find-any mode: Continue...
        in_lower_half = res < 0
      when true # find-min mode
        in_lower_half = true
        satisfied = true
      when false, nil # find-min mode
        in_lower_half = false
      else
        raise TypeError, 'invalid block result (must be numeric, true, false or nil)'
      end

      if in_lower_half
        high = mid
      else
        low = mid + 1
      end
    end

    satisfied ? low : nil
  end

  ##
  #  call-seq:
  #     ary.keep_if { |item| block } -> ary
  #     ary.keep_if                  -> Enumerator
  #
  #  Deletes every element of `self` for which the given block evaluates to
  #  `false`.
  #
  #  See also Array#select!
  #
  #  If no block is given, an Enumerator is returned instead.
  #
  #     a = [1, 2, 3, 4, 5]
  #     a.keep_if { |val| val > 3 } #=> [4, 5]

  def keep_if(&block)
    return to_enum(:keep_if) unless block

    result = []
    idx = 0
    len = size
    while idx < len
      elem = self[idx]
      result << elem if block.call(elem)
      idx += 1
    end

    self.replace(result)
  end

  ##
  #  call-seq:
  #     ary.select!  {|item| block } -> ary or nil
  #     ary.select!                  -> Enumerator
  #
  #  Invokes the given block passing in successive elements from `self`,
  #  deleting elements for which the block returns a `false` value.
  #
  #  If changes were made, it will return `self`, otherwise it returns `nil`.
  #
  #  See also Array#keep_if
  #
  #  If no block is given, an Enumerator is returned instead.

  def select!(&block)
    return to_enum(:select!) unless block

    result = []
    idx = 0
    len = size
    while idx < len
      elem = self[idx]
      result << elem if block.call(elem)
      idx += 1
    end

    return nil if len == result.size

    self.replace(result)
  end

  ##
  # call-seq:
  #   ary.dig(idx, ...)                 -> object
  #
  # Extracts the nested value specified by the sequence of *idx*
  # objects by calling `dig` at each step, returning `nil` if any
  # intermediate step is `nil`.
  #
  def dig(idx,*args)
    idx = idx.__to_int
    n = self[idx]
    if args.size > 0
      n&.dig(*args)
    else
      n
    end
  end

  ##
  # call-seq:
  #    ary.permutation { |p| block }          -> ary
  #    ary.permutation                        -> Enumerator
  #    ary.permutation(n) { |p| block }       -> ary
  #    ary.permutation(n)                     -> Enumerator
  #
  # When invoked with a block, yield all permutations of length `n` of the
  # elements of the array, then return the array itself.
  #
  # If `n` is not specified, yield all permutations of all elements.
  #
  # The implementation makes no guarantees about the order in which the
  # permutations are yielded.
  #
  # If no block is given, an Enumerator is returned instead.
  #
  # Examples:
  #
  #  a = [1, 2, 3]
  #  a.permutation.to_a    #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
  #  a.permutation(1).to_a #=> [[1],[2],[3]]
  #  a.permutation(2).to_a #=> [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]
  #  a.permutation(3).to_a #=> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
  #  a.permutation(0).to_a #=> [[]] # one permutation of length 0
  #  a.permutation(4).to_a #=> []   # no permutations of length 4
  def permutation(n=self.size, &block)
    n = n.__to_int
    return to_enum(:permutation, n) unless block
    size = self.size
    if n == 0
      yield []
    elsif 0 < n && n <= size
      i = 0
      while i<size
        result = [self[i]]
        if n-1 > 0
          ary = self[0...i] + self[i+1..-1]
          ary.permutation(n-1) do |c|
            yield result + c
          end
        else
          yield result
        end
        i += 1
      end
    end
    self
  end

  ##
  # call-seq:
  #    ary.combination(n) { |c| block }    -> ary
  #    ary.combination(n)                  -> Enumerator
  #
  # When invoked with a block, yields all combinations of length `n` of elements
  # from the array and then returns the array itself.
  #
  # The implementation makes no guarantees about the order in which the
  # combinations are yielded.
  #
  # If no block is given, an Enumerator is returned instead.
  #
  # Examples:
  #
  #    a = [1, 2, 3, 4]
  #    a.combination(1).to_a  #=> [[1],[2],[3],[4]]
  #    a.combination(2).to_a  #=> [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
  #    a.combination(3).to_a  #=> [[1,2,3],[1,2,4],[1,3,4],[2,3,4]]
  #    a.combination(4).to_a  #=> [[1,2,3,4]]
  #    a.combination(0).to_a  #=> [[]] # one combination of length 0
  #    a.combination(5).to_a  #=> []   # no combinations of length 5

  def combination(n, &block)
    n = n.__to_int
    return to_enum(:combination, n) unless block
    size = self.size
    if n == 0
      yield []
    elsif n == 1
      i = 0
      while i<size
        yield [self[i]]
        i += 1
      end
    elsif n <= size
      i = 0
      while i<size
        result = [self[i]]
        self[i+1..-1].combination(n-1) do |c|
          yield result + c
        end
        i += 1
      end
    end
    self
  end

  ##
  # call-seq:
  #    ary.transpose -> new_ary
  #
  # Assumes that self is an array of arrays and transposes the rows and columns.
  #
  # If the length of the subarrays don't match, an IndexError is raised.
  #
  # Examples:
  #
  #    a = [[1,2], [3,4], [5,6]]
  #    a.transpose   #=> [[1, 3, 5], [2, 4, 6]]

  def transpose
    return [] if empty?

    column_count = nil
    self.each do |row|
      raise TypeError unless row.is_a?(Array)
      column_count ||= row.size
      raise IndexError, 'element size differs' unless column_count == row.size
    end

    Array.new(column_count) do |column_index|
      self.map {|row| row[column_index] }
    end
  end

  ##
  #  call-seq:
  #    ary.to_h                ->   Hash
  #    ary.to_h{|item| ... }   ->   Hash
  #
  # Returns the result of interpreting *array* as an array of
  # `[key, value]` pairs. If a block is given, it should
  # return `[key, value]` pairs to construct a hash.
  #
  #     [[:foo, :bar], [1, 2]].to_h
  #       # => {:foo => :bar, 1 => 2}
  #     [1, 2].to_h{|x| [x, x*2]}
  #       # => {1 => 2, 2 => 4}
  #
  def to_h(&blk)
    h = {}
    self.each do |v|
      v = blk.call(v) if blk
      raise TypeError, "wrong element type #{v.class}" unless Array === v
      raise ArgumentError, "wrong array length (expected 2, was #{v.length})" unless v.length == 2
      h[v[0]] = v[1]
    end
    h
  end

  alias append push
  alias prepend unshift
  alias filter! select!

  ##
  # call-seq:
  #   ary.fetch_values(idx, ...)                 -> array
  #   ary.fetch_values(idx, ...) { |i| block } -> array
  #
  # Returns an array containing the values associated with the given indexes.
  # but also raises `IndexError` when one of indexes can't be found.
  # Also see `Array#values_at` and `Array#fetch`.
  #
  #   a = ["cat", "dog", "cow"]
  #
  #   a.fetch_values(2, 0)                #=> ["cow", "cat"]
  #   a.fetch_values(2, 5)                # raises KeyError
  #   a.fetch_values(2, 5) {|i| "BIRD" }  #=> ["cow", "BIRD"]
  #
  def fetch_values(*idx, &block)
    if block
      idx.map do |i|
        self.fetch(i, &block)
      end
    else
      # Fast path: use C implementation for non-block cases
      idx.map do |i|
        __fetch(i, NONE, NONE)
      end
    end
  end

  ##
  # call-seq:
  #   ary.product(*arys)                  ->   array
  #   ary.product(*arys) { |item| ... }   ->   self
  def product(*arys, &block)
    size = arys.size
    i = size
    while i > 0
      i -= 1
      unless arys[i].kind_of?(Array)
        raise TypeError, "no implicit conversion into Array"
      end
    end

    i = size
    total = self.size
    total *= arys[i -= 1].size while i > 0

    if block
      i = 0
      while i < total
        group = self.__product_group(arys, i, size + 1)
        block.call(group)
        i += 1
      end
      return self
    else
      result = [nil] * total
      i = 0
      while i < total
        group = self.__product_group(arys, i, size + 1)
        result[i] = group
        i += 1
      end
      return result
    end
  end

  ##
  # call-seq:
  #   ary.repeated_combination(n) { |combination| ... } -> ary
  #   ary.repeated_combination(n)                         -> Enumerator
  #
  # When invoked with a block, yields all length `n` combinations of elements
  # from the array, with replacement, and then returns the array itself.
  #
  # This means that, unlike `combination`, elements can be chosen more than once.
  #
  # The implementation makes no guarantees about the order in which the
  # combinations are yielded.
  #
  # If no block is given, an Enumerator is returned instead.
  #
  # Examples:
  #
  #   a = [1, 2, 3]
  #   a.repeated_combination(2).to_a #=> [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]]
  def repeated_combination(n, &block)
    raise TypeError, "no implicit conversion into Integer" unless 0 <=> n
    return to_enum(:repeated_combination, n) unless block
    __repeated_combination(n, false, &block)
  end

  ##
  # call-seq:
  #   ary.repeated_permutation(n) { |permutation| ... } -> ary
  #   ary.repeated_permutation(n)                         -> Enumerator
  #
  # When invoked with a block, yields all length `n` permutations of elements
  # from the array, with replacement, and then returns the array itself.
  #
  # This means that, unlike `permutation`, elements can be chosen more than once.
  #
  # The implementation makes no guarantees about the order in which the
  # permutations are yielded.
  #
  # If no block is given, an Enumerator is returned instead.
  #
  # Examples:
  #
  #   a = [1, 2]
  #   a.repeated_permutation(2).to_a #=> [[1,1],[1,2],[2,1],[2,2]]
  def repeated_permutation(n, &block)
    n = n.__to_int
    raise TypeError, "no implicit conversion into Integer" unless 0 <=> n
    return to_enum(:repeated_permutation, n) unless block
    __repeated_combination(n, true, &block)
  end

  def __repeated_combination(n, permutation, &block)
    n = n.__to_int
    case n
    when 0
      yield []
    when 1
      # Keep fast Ruby path for n=1
      i = 0
      while i < self.size
        yield [self[i]]
        i += 1
      end
    else
      if n > 0
        # Use C iterator for complex cases
        state = __combination_init(n, permutation)
        while (indices = __combination_next(state))
          # Convert indices to elements in Ruby
          tmp = [nil] * n
          i = 0
          while i < n
            tmp[i] = self[indices[i]]
            i += 1
          end
          yield tmp
        end
      end
    end

    self
  end

  ##
  # call-seq:
  #    ary.find(ifnone = nil) { |elem| block } -> obj or nil
  #    ary.find(ifnone = nil)                  -> Enumerator
  #
  # Returns the first element for which the block returns a true value.
  # If no element matches and +ifnone+ is given, calls +ifnone+ and
  # returns its result. Otherwise returns +nil+.
  #
  # This is an optimized version of Enumerable#find for arrays.
  #
  #    [1, 2, 3, 4].find { |x| x > 2 }        #=> 3
  #    [1, 2, 3, 4].find { |x| x > 10 }       #=> nil
  #    [1, 2, 3, 4].find(->{0}) { |x| x > 10 } #=> 0
  #
  def find(ifnone=nil, &block)
    return to_enum(:find, ifnone) unless block

    idx = 0
    len = self.size
    while idx < len
      elem = self[idx]
      return elem if block.call(elem)
      idx += 1
    end
    ifnone&.call
  end

  ##
  # call-seq:
  #    ary.rfind(ifnone = nil) { |elem| block } -> obj or nil
  #    ary.rfind(ifnone = nil)                  -> Enumerator
  #
  # Returns the last element for which the block returns a true value.
  # Searches from the end of the array to the beginning.
  # If no element matches and +ifnone+ is given, calls +ifnone+ and
  # returns its result. Otherwise returns +nil+.
  #
  #    [1, 2, 3, 4, 3].rfind { |x| x == 3 }     #=> 3 (the last one)
  #    [1, 2, 3, 4].rfind { |x| x > 2 }         #=> 4
  #    [1, 2, 3, 4].rfind { |x| x > 10 }        #=> nil
  #    [1, 2, 3, 4].rfind(->{0}) { |x| x > 10 } #=> 0
  #
  def rfind(ifnone=nil, &block)
    return to_enum(:rfind, ifnone) unless block

    idx = self.size - 1
    while idx >= 0
      elem = self[idx]
      return elem if block.call(elem)
      idx -= 1
    end
    ifnone&.call
  end
end
