# The top-level number type.
struct Number
  include Comparable(Number)
  include Steppable

  alias Primitive = Int::Primitive | Float::Primitive

  # Returns the value zero in the respective type.
  #
  # ```
  # Int32.zero   # => 0
  # Float64.zero # => 0.0
  # ```
  def self.zero : self
    new(0)
  end

  # Returns the additive identity of this type.
  #
  # For numerical types, it is the value `0` expressed in the respective type.
  #
  # ```
  # Int32.additive_identity   # => 0
  # Float64.additive_identity # => 0.0
  # ```
  def self.additive_identity : self
    zero
  end

  # Returns the multiplicative identity of this type.
  #
  # For numerical types, it is the value `1` expressed in the respective type.
  #
  # ```
  # Int32.multiplicative_identity   # => 1
  # Float64.multiplicative_identity # => 1.0
  # ```
  def self.multiplicative_identity : self
    new(1)
  end

  # See `Object#hash(hasher)`
  def hash(hasher)
    hasher.number(self)
  end

  # Returns `self`.
  def +
    self
  end

  # Divides `self` by *other* using floored division.
  #
  # The result will be of the same type as `self`.
  def //(other)
    self.class.new((self / other).floor)
  end

  # :nodoc:
  macro expand_div(rhs_types, result_type)
    {% for rhs in rhs_types %}
      @[::AlwaysInline]
      def /(other : {{rhs}}) : {{result_type}}
        {{result_type}}.new(self) / {{result_type}}.new(other)
      end
    {% end %}
  end

  # Creates an `Array` of `self` with the given values, which will be casted
  # to this type with the `new` method (defined in each `Number` type).
  #
  # ```
  # floats = Float64[1, 2, 3, 4]
  # floats.class # => Array(Float64)
  #
  # ints = Int64[1, 2, 3]
  # ints.class # => Array(Int64)
  # ```
  #
  # This is similar to an array literal of the same item type:
  #
  # ```
  # Int64[1, 2, 3, 4]     # : Array(Int64)
  # [1, 2, 3, 4] of Int64 # : Array(Int64)
  # ```
  macro [](*nums)
    ::Array({{@type}}).build({{nums.size}}) do |%buffer|
      {% for num, i in nums %}
        %buffer[{{i}}] = {{@type}}.new({{num}})
      {% end %}
      {{nums.size}}
    end
  end

  # Creates a `Slice` of `self` with the given values, which will be casted
  # to this type with the `new` method (defined in each `Number` type).
  #
  # The slice is allocated on the heap.
  #
  # ```
  # floats = Float64.slice(1, 2, 3, 4)
  # floats.class # => Slice(Float64)
  #
  # ints = Int64.slice(1, 2, 3)
  # ints.class # => Slice(Int64)
  # ```
  #
  # This is a convenient alternative to `Slice.[]` for designating a
  # specific item type which also considers autocasting.
  #
  # ```
  # Int64.slice(1, 2, 3, 4)           # : Slice(Int64)
  # Slice[1_i64, 2_i64, 3_i64, 4_i64] # : Slice(Int64)
  # ```
  macro slice(*nums, read_only = false)
    %slice = ::Slice({{@type}}).new({{nums.size}}, read_only: {{read_only}})
    {% for num, i in nums %}
      %slice.to_unsafe[{{i}}] = {{@type}}.new!({{num}})
    {% end %}
    %slice
  end

  # Creates a `StaticArray` of `self` with the given values, which will be casted
  # to this type with the `new` method (defined in each `Number` type).
  #
  # ```
  # floats = Float64.static_array(1, 2, 3, 4)
  # floats.class # => StaticArray(Float64, 4)
  #
  # ints = Int64.static_array(1, 2, 3)
  # ints.class # => StaticArray(Int64, 3)
  # ```
  #
  # This is a convenvenient alternative to `StaticArray.[]` for designating a
  # specific item type which also considers autocasting.
  #
  # ```
  # Int64.static_array(1, 2, 3, 4)          # : StaticArray(Int64)
  # StaticArray[1_i64, 2_i64, 3_i64, 4_i64] # : StaticArray(Int64)
  # ```
  macro static_array(*nums)
    %array = uninitialized ::StaticArray({{@type}}, {{nums.size}})
    {% for num, i in nums %}
      %array.to_unsafe[{{i}}] = {{@type}}.new!({{num}})
    {% end %}
    %array
  end

  # Performs a `#step` in the direction of the _limit_. For instance:
  #
  # ```
  # 10.step(to: 5).to_a # => [10, 9, 8, 7, 6, 5]
  # 5.step(to: 10).to_a # => [5, 6, 7, 8, 9, 10]
  # ```
  def step(*, to limit = nil, exclusive : Bool = false, &) : Nil
    if limit
      direction = limit <=> self
    end
    step = direction.try(&.sign) || 1

    step(to: limit, by: step, exclusive: exclusive) do |x|
      yield x
    end
  end

  # :ditto:
  def step(*, to limit = nil, exclusive : Bool = false)
    if limit
      direction = limit <=> self
    end
    step = direction.try(&.sign) || 1

    step(to: limit, by: step, exclusive: exclusive)
  end

  # Returns the absolute value of this number.
  #
  # ```
  # 123.abs  # => 123
  # -123.abs # => 123
  # ```
  def abs : self
    self < 0 ? -self : self
  end

  # Returns the square of `self` (`self * self`).
  #
  # ```
  # 4.abs2   # => 16
  # 1.5.abs2 # => 2.25
  # ```
  def abs2
    self * self
  end

  # Returns the sign of this number as an `Int32`.
  # * `-1` if this number is negative
  # * `0` if this number is zero
  # * `1` if this number is positive
  #
  # ```
  # 123.sign # => 1
  # 0.sign   # => 0
  # -42.sign # => -1
  # ```
  def sign : Int32
    self < 0 ? -1 : (self == 0 ? 0 : 1)
  end

  # Returns a `Tuple` of two elements containing the quotient
  # and modulus obtained by dividing `self` by *number*.
  #
  # ```
  # 11.divmod(3)  # => {3, 2}
  # 11.divmod(-3) # => {-4, -1}
  # ```
  def divmod(number)
    {(self // number).floor, self % number}
  end

  # The comparison operator.
  #
  # Returns:
  # - `-1` if `self` is less than *other*
  # - `0` if `self` is equal to *other*
  # - `1` if `self` is greater than *other*
  # - `nil` if `self` is `NaN` or *other* is `NaN`, because `NaN` values are not comparable
  def <=>(other) : Int32?
    # NaN can't be compared to other numbers
    return nil if self.is_a?(Float) && self.nan?
    return nil if other.is_a?(Float) && other.nan?

    self > other ? 1 : (self < other ? -1 : 0)
  end

  # Keeps *digits* significant digits of this number in the given *base*.
  #
  # ```
  # 1234.567.significant(1) # => 1000
  # 1234.567.significant(2) # => 1200
  # 1234.567.significant(3) # => 1230
  # 1234.567.significant(4) # => 1235
  # 1234.567.significant(5) # => 1234.6
  # 1234.567.significant(6) # => 1234.57
  # 1234.567.significant(7) # => 1234.567
  # 1234.567.significant(8) # => 1234.567
  #
  # 15.159.significant(1, base = 2) # => 16
  # ```
  def significant(digits, base = 10)
    if digits < 0
      raise ArgumentError.new "digits should be non-negative"
    end
    return self if zero?

    x = self.to_f

    if base == 10
      log = Math.log10(self.abs)
    elsif base == 2
      log = Math.log2(self.abs)
    else
      log = Math.log2(self.abs) / Math.log2(base)
    end

    exponent = (log - digits + 1).floor
    if exponent < 0
      y = base ** -exponent
      value = (x * y).round / y
    else
      y = base ** exponent
      value = (x / y).round * y
    end

    self.class.new(value)
  end

  # Rounds this number to a given precision.
  #
  # Rounds to the specified number of *digits* after the decimal place,
  # (or before if negative), in base *base*.
  #
  # The rounding *mode* controls the direction of the rounding. The default is
  # `RoundingMode::TIES_EVEN` which rounds to the nearest integer, with ties
  # (fractional value of `0.5`) being rounded to the even neighbor (Banker's rounding).
  #
  # ```
  # -1763.116.round(2) # => -1763.12
  # ```
  def round(digits : Number, base = 10, *, mode : RoundingMode = :ties_even)
    if digits < 0
      multiplier = base.to_f ** digits.abs
      shifted = self / multiplier
    else
      multiplier = base.to_f ** digits
      shifted = self * multiplier
    end

    rounded = shifted.round(mode)

    if digits < 0
      result = rounded * multiplier
    else
      result = rounded / multiplier
    end

    self.class.new result
  end

  # Specifies rounding behaviour for numerical operations capable of discarding
  # precision.
  enum RoundingMode
    # Rounds towards the nearest integer. If both neighboring integers are equidistant,
    # rounds towards the even neighbor (Banker's rounding).
    TIES_EVEN

    # Rounds towards the nearest integer. If both neighboring integers are equidistant,
    # rounds away from zero.
    TIES_AWAY

    # Rounds towards zero (truncate).
    TO_ZERO

    # Rounds towards positive infinity (ceil).
    TO_POSITIVE

    # Rounds towards negative infinity (floor).
    TO_NEGATIVE
  end

  # Rounds `self` to an integer value using rounding *mode*.
  #
  # The rounding *mode* controls the direction of the rounding. The default is
  # `RoundingMode::TIES_EVEN` which rounds to the nearest integer, with ties
  # (fractional value of `0.5`) being rounded to the even neighbor (Banker's rounding).
  def round(mode : RoundingMode = :ties_even) : self
    case mode
    in .to_zero?
      trunc
    in .to_positive?
      ceil
    in .to_negative?
      floor
    in .ties_away?
      round_away
    in .ties_even?
      round_even
    end
  end

  # Returns `true` if `self` is an integer.
  #
  # Non-integer types may return `true` as long as `self` denotes a finite value
  # without any fractional parts.
  #
  # ```
  # 1.integer?       # => true
  # 1.0.integer?     # => true
  # 1.2.integer?     # => false
  # (1 / 0).integer? # => false
  # (0 / 0).integer? # => false
  # ```
  def integer? : Bool
    self % 1 == 0
  end

  # Returns `true` if `self` is equal to zero.
  #
  # ```
  # 0.zero? # => true
  # 5.zero? # => false
  # ```
  def zero? : Bool
    self == 0
  end

  # Returns `true` if `self` is greater than zero.
  #
  # ```
  # -1.positive? # => false
  # 0.positive?  # => false
  # 1.positive?  # => true
  # ```
  def positive? : Bool
    self > 0
  end

  # Returns `true` if `self` is less than zero.
  #
  # ```
  # -1.negative? # => true
  # 0.negative?  # => false
  # 1.negative?  # => false
  # ```
  def negative? : Bool
    self < 0
  end
end
