# A tuple is a fixed-size, immutable, stack-allocated sequence of values
# of possibly different types.
#
# You can think of a `Tuple` as an immutable `Array` whose types for each position
# are known at compile time.
#
# A tuple can be created with the usual `new` method or with a tuple literal:
#
# ```
# tuple = {1, "hello", 'x'} # Tuple(Int32, String, Char)
# tuple[0]                  # => 1
# tuple[1]                  # => "hello"
# tuple[2]                  # => 'x'
# ```
#
# See [`Tuple` literals](https://crystal-lang.org/reference/syntax_and_semantics/literals/tuple.html) in the language reference.
#
# The compiler knows what types are in each position, so when indexing
# a tuple with an integer literal the compiler will return
# the value in that index and with the expected type, like in the above
# snippet. Indexing with an integer literal outside the bounds of the tuple
# will give a compile-time error.
#
# Indexing with an integer value that is only known at runtime will return
# a value whose type is the union of all the types in the tuple, and might raise
# `IndexError`.
#
# Indexing with `#[]?` does not make the return value nilable if the index is
# known to be within bounds:
#
# ```
# tuple = {1, "hello", 'x'}
# tuple[0]?         # => 1
# typeof(tuple[0]?) # => Int32
# ```
#
# Indexing with a range literal known at compile-time is also allowed, and the
# returned value will have the correct sub-tuple type:
#
# ```
# tuple = {1, "hello", 'x'} # Tuple(Int32, String, Char)
# sub = tuple[0..1]         # => {1, "hello"}
# typeof(sub)               # => Tuple(Int32, String)
# ```
#
# `Tuple`'s own instance classes may also be indexed in a similar manner,
# returning their element types instead:
#
# ```
# tuple = Tuple(Int32, String, Char)
# tuple[0]   # => Int32
# tuple[3]?  # => nil
# tuple[1..] # => Tuple(String, Char)
# ```
#
# Tuples are the preferred way to return fixed-size multiple return
# values because no memory is needed to be allocated for them:
#
# ```
# def one_and_hello
#   {1, "hello"}
# end
#
# one, hello = one_and_hello
# one   # => 1
# hello # => "hello"
# ```
#
# Good examples of the above are `Number#divmod` and `Enumerable#minmax`.
#
# Tuples can be splat with the `*` operator and passed to methods:
#
# ```
# def multiply(string, value)
#   string * value
# end
#
# tuple = {"hey", 2}
# value = multiply(*tuple) # same as multiply tuple[0], tuple[1]
# value                    # => "heyhey"
# ```
#
# Finally, when using a splat argument in a method definition its type
# will be a tuple of the call arguments:
#
# ```
# def splat_test(*args)
#   args
# end
#
# tuple = splat_test 1, "hello", 'x'
# tuple.class # => Tuple(Int32, String, Char)
# tuple       # => {1, "hello", 'x'}
# ```
struct Tuple
  include Indexable(Union(*T))
  include Comparable(Tuple)

  # Creates a tuple that will contain the given values.
  #
  # This method is useful in macros and generic code because with it you can
  # create empty tuples, something that you can't do with a tuple literal.
  #
  # ```
  # Tuple.new(1, "hello", 'x') #=> {1, "hello", 'x'}
  # Tuple.new                  #=> {}
  #
  # {}                         # syntax error
  # ```
  def self.new(*args : *T)
    {% if @type.name(generic_args: false) == "Tuple" %}
      # deduced type vars
      args
    {% elsif @type.name(generic_args: false) == "Tuple()" %}
      # special case: empty tuple
      # TODO: check against `Tuple()` directly after 1.4.0
      args
    {% else %}
      # explicitly provided type vars
      # following `typeof` is needed to access private types
      {% begin %}
        {
          {% for i in 0...@type.size %}
            args[{{ i }}].as(typeof(element_type({{ i }}))),
          {% end %}
        }
      {% end %}
    {% end %}
  end

  # Creates a tuple from the given array, with elements casted to the given types.
  #
  # ```
  # Tuple(String, Int64).from(["world", 2_i64])       # => {"world", 2_i64}
  # Tuple(String, Int64).from(["world", 2_i64]).class # => Tuple(String, Int64)
  # ```
  #
  # See also: `#from`.
  def self.from(array : Array) : self
    {% begin %}
    Tuple.new(*{{T}}).from(array)
    {% end %}
  end

  # Expects to be called on a tuple of types, creates a tuple from the given array,
  # with types casted appropriately.
  #
  # This allows you to easily pass an array as individual arguments to a method.
  #
  # ```
  # require "json"
  #
  # def speak_about(thing : String, n : Int64)
  #   "I see #{n} #{thing}s"
  # end
  #
  # data = JSON.parse(%(["world", 2])).as_a.map(&.raw)
  # speak_about(*{String, Int64}.from(data)) # => "I see 2 worlds"
  # ```
  def from(array : Array)
    if size != array.size
      raise ArgumentError.new "Expected array of size #{size} but one of size #{array.size} was given."
    end

    {% begin %}
    Tuple.new(
    {% for i in 0...@type.size %}
      self[{{i}}].cast(array[{{i}}]),
    {% end %}
    )
    {% end %}
  end

  def unsafe_fetch(index : Int)
    self[index]
  end

  # Returns the element at the given *index*. Read the type docs to understand
  # the difference between indexing with a number literal or a variable.
  #
  # ```
  # tuple = {1, "hello", 'x'}
  # tuple[0]         # => 1
  # typeof(tuple[0]) # => Int32
  # tuple[3]         # Error: index out of bounds for Tuple(Int32, String, Char) (3 not in -3..2)
  #
  # i = 0
  # tuple[i]         # => 1
  # typeof(tuple[i]) # => (Char | Int32 | String)
  #
  # i = 3
  # tuple[i] # raises IndexError
  # ```
  def [](index : Int)
    at(index)
  end

  # Returns the element type at the given *index*. Read the type docs to
  # understand the difference between indexing with a number literal or a
  # variable.
  #
  # ```
  # alias Foo = Tuple(Int32, String)
  # Foo[0]      # => Int32
  # Foo[0].zero # => 0
  # Foo[2]      # Error: index out of bounds for Tuple(Int32, String).class (2 not in -2..1)
  #
  # i = 0
  # Foo[i]      # => Int32
  # Foo[i].zero # Error: undefined method 'zero' for String.class (compile-time type is (Int32.class | String.class))
  #
  # i = 2
  # Foo[i] # raises IndexError
  # ```
  def self.[](index : Int)
    self[index]? || raise IndexError.new
  end

  # Returns the element at the given *index* or `nil` if out of bounds. Read the
  # type docs to understand the difference between indexing with a number
  # literal or a variable.
  #
  # ```
  # tuple = {1, "hello", 'x'}
  # tuple[0]?         # => 1
  # typeof(tuple[0]?) # => Int32
  # tuple[3]?         # => nil
  # typeof(tuple[3]?) # => Nil
  #
  # i = 0
  # tuple[i]?         # => 1
  # typeof(tuple[i]?) # => (Char | Int32 | String | Nil)
  #
  # i = 3
  # tuple[i]? # => nil
  # ```
  def []?(index : Int)
    at(index) { nil }
  end

  # Returns the element type at the given *index* or `nil` if out of bounds.
  # Read the type docs to understand the difference between indexing with a
  # number literal or a variable.
  #
  # ```
  # alias Foo = Tuple(Int32, String)
  # Foo[0]?         # => Int32
  # Foo[0]?.zero    # => 0
  # Foo[2]?         # => nil
  # typeof(Foo[2]?) # => Nil
  #
  # i = 0
  # Foo[i]?      # => Int32
  # Foo[i]?.zero # Error: undefined method 'zero' for String.class (compile-time type is (Int32.class | String.class | Nil))
  #
  # i = 2
  # Foo[i]? # => nil
  # ```
  def self.[]?(index : Int)
    # following `typeof` is needed to access private types
    {% begin %}
      case index
      {% for i in 0...T.size %}
      when {{ i }}, {{ i - T.size }}
        typeof(element_type({{ i }}))
      {% end %}
      end
    {% end %}
  end

  # Returns all elements that are within the given *range*. *range* must be a
  # range literal whose value is known at compile-time.
  #
  # Negative indices count backward from the end of the tuple (-1 is the last
  # element). Additionally, an empty tuple is returned when the starting index
  # for an element range is at the end of the tuple.
  #
  # Raises a compile-time error if `range.begin` is out of range.
  #
  # ```
  # tuple = {1, "hello", 'x'}
  # tuple[0..1] # => {1, "hello"}
  # tuple[-2..] # => {"hello", 'x'}
  # tuple[...1] # => {1}
  # tuple[4..]  # Error: begin index out of bounds for Tuple(Int32, String, Char) (4 not in -3..3)
  #
  # i = 0
  # tuple[i..2] # Error: Tuple#[](Range) can only be called with range literals known at compile-time
  #
  # i = 0..2
  # tuple[i] # Error: Tuple#[](Range) can only be called with range literals known at compile-time
  # ```
  def [](range : Range)
    {% raise "Tuple#[](Range) can only be called with range literals known at compile-time" %}
  end

  # Returns all element types that are within the given *range*. *range* must be
  # a range literal whose value is known at compile-time.
  #
  # Negative indices count backward from the end of the tuple (-1 is the last
  # element). Additionally, an empty tuple is returned when the starting index
  # for an element range is at the end of the tuple.
  #
  # Raises a compile-time error if `range.begin` is out of range.
  #
  # ```
  # alias Foo = Tuple(Int32, String, Char)
  # Foo[0..1] # => Tuple(Int32, String)
  # Foo[-2..] # => Tuple(String, Char)
  # Foo[...1] # => Tuple(Int32)
  # Foo[4..]  # Error: begin index out of bounds for Tuple(Int32, String, Char).class (4 not in -3..3)
  #
  # i = 0
  # Foo[i..2] # Error: Tuple.[](Range) can only be called with range literals known at compile-time
  #
  # i = 0..2
  # Foo[i] # Error: Tuple.[](Range) can only be called with range literals known at compile-time
  # ```
  def self.[](range : Range)
    {% raise "Tuple.[](Range) can only be called with range literals known at compile-time" %}
  end

  # Returns the element at the given *index* or raises IndexError if out of bounds.
  #
  # ```
  # tuple = {1, "hello", 'x'}
  # tuple.at(0) # => 1
  # tuple.at(3) # raises IndexError
  # ```
  def at(index : Int)
    at(index) { raise IndexError.new }
  end

  # Returns the element at the given *index* or the value returned by the block if
  # out of bounds.
  #
  # ```
  # tuple = {1, "hello", 'x'}
  # tuple.at(0) { 10 } # => 1
  # tuple.at(3) { 10 } # => 10
  # ```
  def at(index : Int, &)
    index += size if index < 0
    {% for i in 0...T.size %}
      return self[{{i}}] if {{i}} == index
    {% end %}
    yield
  end

  # Yields each of the elements in this tuple.
  #
  # ```
  # tuple = {1, "hello", 'x'}
  # tuple.each do |value|
  #   puts value
  # end
  # ```
  #
  # Output:
  #
  # ```text
  # 1
  # "hello"
  # 'x'
  # ```
  def each(& : Union(*T) ->) : Nil
    {% for i in 0...T.size %}
      yield self[{{i}}]
    {% end %}
  end

  # Returns `true` if this tuple has the same size as the other tuple
  # and their elements are equal to each other when compared with `==`.
  #
  # ```
  # t1 = {1, "hello"}
  # t2 = {1.0, "hello"}
  # t3 = {2, "hello"}
  #
  # t1 == t2 # => true
  # t1 == t3 # => false
  # ```
  def ==(other : self)
    {% for i in 0...T.size %}
      return false unless self[{{i}}] == other[{{i}}]
    {% end %}
    true
  end

  # :ditto:
  def ==(other : Tuple)
    return false unless size == other.size

    size.times do |i|
      return false unless self[i] == other[i]
    end
    true
  end

  def ==(other)
    false
  end

  # Returns `true` if case equality holds for the elements in `self` and *other*.
  #
  # ```
  # {1, 2} === {1, 2} # => true
  # {1, 2} === {1, 3} # => false
  # ```
  #
  # See also: `Object#===`.
  def ===(other : self)
    {% for i in 0...T.size %}
      return false unless self[{{i}}] === other[{{i}}]
    {% end %}
    true
  end

  # Returns `true` if `self` and *other* have the same size and case equality holds
  # for the elements in `self` and *other*.
  #
  # ```
  # {1, 2} === {1, 2, 3}             # => false
  # {/o+/, "bar"} === {"foo", "bar"} # => true
  # ```
  #
  # See also: `Object#===`.
  def ===(other : Tuple)
    return false unless size == other.size

    size.times do |i|
      return false unless self[i] === other[i]
    end
    true
  end

  # The comparison operator.
  #
  # Each object in each tuple is compared using the `<=>` operator.
  #
  # Tuples are compared in an "element-wise" manner; the first element of this tuple is
  # compared with the first one of *other* using the `<=>` operator, then each of the second elements,
  # etc. As soon as the result of any such comparison is non-zero
  # (i.e. the two corresponding elements are not equal), that result is returned for the whole tuple comparison.
  #
  # If all the elements are equal, then the result is based on a comparison of the tuple sizes.
  # Thus, two tuples are "equal" according to `<=>` if, and only if, they have the same size
  # and the value of each element is equal to the value of the corresponding element in the other tuple.
  #
  # ```
  # {"a", "a", "c"} <=> {"a", "b", "c"} # => -1
  # {1, 2, 3, 4, 5, 6} <=> {1, 2}       # => 1
  # {1, 2} <=> {1, 2.0}                 # => 0
  # ```
  def <=>(other : self)
    {% for i in 0...T.size %}
      cmp = self[{{i}}] <=> other[{{i}}]
      return cmp unless cmp == 0
    {% end %}
    0
  end

  # :ditto:
  def <=>(other : Tuple)
    min_size = Math.min(size, other.size)
    min_size.times do |i|
      cmp = self[i] <=> other[i]
      return cmp unless cmp == 0
    end
    size <=> other.size
  end

  # See `Object#hash(hasher)`
  def hash(hasher)
    {% for i in 0...T.size %}
      hasher = self[{{i}}].hash(hasher)
    {% end %}
    hasher
  end

  # Returns a tuple containing cloned elements of this tuple using the `clone` method.
  def clone
    {% begin %}
      Tuple.new(
        {% for i in 0...T.size %}
          self[{{i}}].clone,
        {% end %}
      )
    {% end %}
  end

  # Returns a tuple that contains `self`'s elements followed by *other*'s elements.
  #
  # ```
  # t1 = {1, 2}
  # t2 = {"foo", "bar"}
  # t3 = t1 + t2
  # t3         # => {1, 2, "foo", "bar"}
  # typeof(t3) # => Tuple(Int32, Int32, String, String)
  # ```
  def +(other : Tuple)
    plus_implementation(other)
  end

  private def plus_implementation(other : U) forall U
    {% begin %}
      Tuple.new(
        {% for i in 0...@type.size %}
          self[{{i}}],
        {% end %}
        {% for i in 0...U.size %}
          other[{{i}}],
        {% end %}
      )
    {% end %}
  end

  # Returns the number of elements in this tuple.
  #
  # ```
  # {'a', 'b'}.size # => 2
  # ```
  def size
    {{T.size}}
  end

  # Returns the types of this tuple type.
  #
  # ```
  # tuple = {1, "hello", 'x'}
  # tuple.class.types # => {Int32, String, Char}
  # ```
  def self.types
    Tuple.new(*{{T}})
  end

  # Same as `to_s`.
  def inspect : String
    to_s
  end

  # Returns an `Array` with all the elements in the tuple.
  #
  # ```
  # {1, 2, 3, 4, 5}.to_a # => [1, 2, 3, 4, 5]
  # ```
  def to_a : Array(Union(*T))
    super
  end

  # Returns an `Array` with the results of running *block* against each element of the tuple.
  #
  # ```
  # {1, 2, 3, 4, 5}).to_a { |i| i * 2 } # => [2, 4, 6, 8, 10]
  # ```
  def to_a(& : Union(*T) -> U) forall U
    Array(U).build(size) do |buffer|
      {% for i in 0...T.size %}
        buffer[{{i}}] = yield self[{{i}}]
      {% end %}
      size
    end
  end

  # Returns a `StaticArray` with the same elements.
  #
  # The element type is `Union(*T)`.
  #
  # ```
  # {1, 'a', true}.to_static_array # => StaticArray[1, 'a', true]
  # ```
  @[AlwaysInline]
  def to_static_array : StaticArray
    {% begin %}
      ary = uninitialized StaticArray(Union(*T), {{ T.size }})
      each_with_index do |value, i|
        ary.to_unsafe[i] = value
      end
      ary
    {% end %}
  end

  # Appends a string representation of this tuple to the given `IO`.
  #
  # ```
  # tuple = {1, "hello"}
  # tuple.to_s # => "{1, \"hello\"}"
  # ```
  def to_s(io : IO) : Nil
    needs_padding = curly_like?(self.first?)

    io << '{'
    io << ' ' if needs_padding

    join io, ", ", &.inspect(io)

    io << ' ' if needs_padding
    io << '}'
  end

  private def curly_like?(object)
    case object
    when Hash, Tuple, NamedTuple
      true
    else
      false
    end
  end

  def pretty_print(pp) : Nil
    pp.list("{", self, "}")
  end

  # Returns a new tuple where elements are mapped by the given block.
  #
  # ```
  # tuple = {1, 2.5, "a"}
  # tuple.map &.to_s # => {"1", "2.5", "a"}
  # ```
  def map(& : Union(*T) ->)
    {% begin %}
      Tuple.new(
        {% for i in 0...T.size %}
          (yield self[{{i}}]),
        {% end %}
      )
   {% end %}
  end

  # Like `map`, but the block gets passed both the element and its index.
  #
  # ```
  # tuple = {1, 2.5, "a"}
  # tuple.map_with_index { |e, i| "tuple[#{i}]: #{e}" } # => {"tuple[0]: 1", "tuple[1]: 2.5", "tuple[2]: a"}
  # ```
  #
  # Accepts an optional *offset* parameter, which tells it to start counting
  # from there.
  def map_with_index(offset = 0, &)
    {% begin %}
      Tuple.new(
        {% for i in 0...T.size %}
          (yield self[{{i}}], offset + {{i}}),
        {% end %}
      )
    {% end %}
  end

  # Returns a new tuple where the elements are in reverse order.
  #
  # ```
  # tuple = {1, 2.5, "a"}
  # tuple.reverse # => {"a", 2.5, 1}
  # ```
  def reverse
    {% begin %}
      Tuple.new(
        {% for i in 1..T.size %}
          self[{{T.size - i}}],
        {% end %}
      )
    {% end %}
  end

  # Yields each of the elements in this tuple in reverse order.
  #
  # ```
  # tuple = {1, "hello", 'x'}
  # tuple.reverse_each do |value|
  #   puts value
  # end
  # ```
  #
  # Output:
  #
  # ```text
  # 'x'
  # "hello"
  # 1
  # ```
  def reverse_each(& : Union(*T) ->)
    {% for i in 1..T.size %}
      yield self[{{T.size - i}}]
    {% end %}
    nil
  end

  # :inherit:
  def reduce(&)
    {% if T.empty? %}
      raise Enumerable::EmptyError.new
    {% else %}
      memo = self[0]
      {% for i in 1...T.size %}
        memo = yield memo, self[{{ i }}]
      {% end %}
      memo
    {% end %}
  end

  # :inherit:
  def reduce(memo, &)
    {% for i in 0...T.size %}
      memo = yield memo, self[{{ i }}]
    {% end %}
    memo
  end

  # :inherit:
  def reduce?(&)
    {% unless T.empty? %}
      reduce { |memo, elem| yield memo, elem }
    {% end %}
  end

  # Returns the first element of this tuple. Doesn't compile
  # if the tuple is empty.
  #
  # ```
  # tuple = {1, 2.5}
  # tuple.first # => 1
  # ```
  def first
    self[0]
  end

  # Returns the first element of this tuple, or `nil` if this
  # is the empty tuple.
  #
  # ```
  # tuple = {1, 2.5}
  # tuple.first? # => 1
  #
  # empty = Tuple.new
  # empty.first? # => nil
  # ```
  def first?
    {% if T.size == 0 %}
      nil
    {% else %}
      self[0]
    {% end %}
  end

  # Returns the last element of this tuple. Doesn't compile
  # if the tuple is empty.
  #
  # ```
  # tuple = {1, 2.5}
  # tuple.last # => 2.5
  # ```
  def last
    {% begin %}
      self[{{T.size - 1}}]
    {% end %}
  end

  # Returns the last element of this tuple, or `nil` if this
  # is the empty tuple.
  #
  # ```
  # tuple = {1, 2.5}
  # tuple.last? # => 2.5
  #
  # empty = Tuple.new
  # empty.last? # => nil
  # ```
  def last?
    {% if T.size == 0 %}
      nil
    {% else %}
      self[{{T.size - 1}}]
    {% end %}
  end

  # Returns a value with the same type as the element at the given *index* of
  # an instance of `self`. *index* must be an integer or range literal known at
  # compile-time.
  #
  # The most common usage of this macro is to extract the appropriate element
  # type in `Tuple`'s class methods. This macro works even if the corresponding
  # element type is private.
  #
  # NOTE: there should never be a need to call this method outside the standard library.
  private macro element_type(index)
    x = uninitialized self
    x[{{ index }}]
  end
end
