require "./libm"

module Math
  extend self

  # Archimedes' constant (π).
  PI = 3.14159265358979323846
  # The full circle constant (τ), equal to 2π.
  TAU = 6.283185307179586476925
  # Euler's number (e).
  E     = LibM.exp_f64(1.0)
  LOG2  = LibM.log_f64(2.0)
  LOG10 = LibM.log_f64(10.0)

  # Calculates the sine of *value*, measured in radians.
  def sin(value : Float32) : Float32
    LibM.sin_f32(value)
  end

  # :ditto:
  def sin(value : Float64) : Float64
    LibM.sin_f64(value)
  end

  # :ditto:
  def sin(value)
    sin(value.to_f)
  end

  # Calculates the cosine of *value*, measured in radians.
  def cos(value : Float32) : Float32
    LibM.cos_f32(value)
  end

  # :ditto:
  def cos(value : Float64) : Float64
    LibM.cos_f64(value)
  end

  # :ditto:
  def cos(value)
    cos(value.to_f)
  end

  # Calculates the tangent of *value*, measured in radians.
  def tan(value : Float32) : Float32
    LibM.tan_f32(value)
  end

  # :ditto:
  def tan(value : Float64) : Float64
    LibM.tan_f64(value)
  end

  # :ditto:
  def tan(value)
    tan(value.to_f)
  end

  # Calculates the arc sine of *value*.
  def asin(value : Float32) : Float32
    LibM.asin_f32(value)
  end

  # :ditto:
  def asin(value : Float64) : Float64
    LibM.asin_f64(value)
  end

  # :ditto:
  def asin(value)
    asin(value.to_f)
  end

  # Calculates the arc cosine of *value*.
  def acos(value : Float32) : Float32
    LibM.acos_f32(value)
  end

  # :ditto:
  def acos(value : Float64) : Float64
    LibM.acos_f64(value)
  end

  # :ditto:
  def acos(value)
    acos(value.to_f)
  end

  # Calculates the arc tangent of *value*.
  def atan(value : Float32) : Float32
    LibM.atan_f32(value)
  end

  # :ditto:
  def atan(value : Float64) : Float64
    LibM.atan_f64(value)
  end

  # :ditto:
  def atan(value)
    atan(value.to_f)
  end

  # Calculates the two-argument arc tangent of the ray from (0, 0) to (*x*, *y*).
  def atan2(y : Float32, x : Float32) : Float32
    LibM.atan2_f32(y, x)
  end

  # :ditto:
  def atan2(y : Float64, x : Float64) : Float64
    LibM.atan2_f64(y, x)
  end

  # :ditto:
  def atan2(y, x) : Float64
    atan2(y.to_f, x.to_f)
  end

  # Calculates the hyperbolic sine of *value*.
  def sinh(value : Float32) : Float32
    LibM.sinh_f32(value)
  end

  # :ditto:
  def sinh(value : Float64) : Float64
    LibM.sinh_f64(value)
  end

  # :ditto:
  def sinh(value)
    sinh(value.to_f)
  end

  # Calculates the hyperbolic cosine of *value*.
  def cosh(value : Float32) : Float32
    LibM.cosh_f32(value)
  end

  # :ditto:
  def cosh(value : Float64) : Float64
    LibM.cosh_f64(value)
  end

  # :ditto:
  def cosh(value)
    cosh(value.to_f)
  end

  # Calculates the hyperbolic tangent of *value*.
  def tanh(value : Float32) : Float32
    LibM.tanh_f32(value)
  end

  # :ditto:
  def tanh(value : Float64) : Float64
    LibM.tanh_f64(value)
  end

  # :ditto:
  def tanh(value)
    tanh(value.to_f)
  end

  # Calculates the inverse hyperbolic sine of *value*.
  def asinh(value : Float32) : Float32
    LibM.asinh_f32(value)
  end

  # :ditto:
  def asinh(value : Float64) : Float64
    LibM.asinh_f64(value)
  end

  # :ditto:
  def asinh(value)
    asinh(value.to_f)
  end

  # Calculates the inverse hyperbolic cosine of *value*.
  def acosh(value : Float32) : Float32
    LibM.acosh_f32(value)
  end

  # :ditto:
  def acosh(value : Float64) : Float64
    LibM.acosh_f64(value)
  end

  # :ditto:
  def acosh(value)
    acosh(value.to_f)
  end

  # Calculates the inverse hyperbolic tangent of *value*.
  def atanh(value : Float32) : Float32
    LibM.atanh_f32(value)
  end

  # :ditto:
  def atanh(value : Float64) : Float64
    LibM.atanh_f64(value)
  end

  # :ditto:
  def atanh(value)
    atanh(value.to_f)
  end

  # Calculates the exponential of *value*.
  def exp(value : Float32) : Float32
    LibM.exp_f32(value)
  end

  # :ditto:
  def exp(value : Float64) : Float64
    LibM.exp_f64(value)
  end

  # :ditto:
  def exp(value)
    exp(value.to_f)
  end

  # Calculates the exponential of *value*, minus 1.
  def expm1(value : Float32) : Float32
    LibM.expm1_f32(value)
  end

  # :ditto:
  def expm1(value : Float64) : Float64
    LibM.expm1_f64(value)
  end

  # :ditto:
  def expm1(value)
    expm1(value.to_f)
  end

  # Calculates 2 raised to the power *value*.
  def exp2(value : Float32) : Float32
    LibM.exp2_f32(value)
  end

  # :ditto:
  def exp2(value : Float64) : Float64
    LibM.exp2_f64(value)
  end

  # :ditto:
  def exp2(value)
    exp2(value.to_f)
  end

  # Calculates the natural logarithm of *value*.
  def log(value : Float32) : Float32
    LibM.log_f32(value)
  end

  # :ditto:
  def log(value : Float64) : Float64
    LibM.log_f64(value)
  end

  # :ditto:
  def log(value) : Float64
    log(value.to_f)
  end

  # Calculates the natural logarithm of 1 plus *value*.
  def log1p(value : Float32) : Float32
    LibM.log1p_f32(value)
  end

  # :ditto:
  def log1p(value : Float64) : Float64
    LibM.log1p_f64(value)
  end

  # :ditto:
  def log1p(value)
    log1p(value.to_f)
  end

  # Calculates the logarithm of *value* to base 2.
  def log2(value : Float32) : Float32
    LibM.log2_f32(value)
  end

  # :ditto:
  def log2(value : Float64) : Float64
    LibM.log2_f64(value)
  end

  # :ditto:
  def log2(value) : Float64
    log2(value.to_f)
  end

  # Calculates the logarithm of *value* to base 10.
  def log10(value : Float32) : Float32
    LibM.log10_f32(value)
  end

  # :ditto:
  def log10(value : Float64) : Float64
    LibM.log10_f64(value)
  end

  # :ditto:
  def log10(value)
    log10(value.to_f)
  end

  # Calculates the logarithm of *value* to the given *base*.
  def log(value, base)
    log(value) / log(base)
  end

  # Calculates the square root of *value*.
  def sqrt(value : Float32) : Float32
    LibM.sqrt_f32(value)
  end

  # :ditto:
  def sqrt(value : Float64) : Float64
    LibM.sqrt_f64(value)
  end

  # :ditto:
  def sqrt(value) : Float64
    sqrt(value.to_f)
  end

  # Calculates the integer square root of *value*.
  def isqrt(value : Int::Primitive)
    raise ArgumentError.new "Input must be non-negative integer" if value < 0
    return value if value < 2
    res = value.class.zero
    bit = res.succ << (res.leading_zeros_count - 2)
    bit >>= value.leading_zeros_count & ~0x3
    while (bit != 0)
      if value >= res + bit
        value -= res + bit
        res = (res >> 1) + bit
      else
        res >>= 1
      end
      bit >>= 2
    end
    res
  end

  # Calculates the cubic root of *value*.
  def cbrt(value : Float32) : Float32
    LibM.cbrt_f32(value)
  end

  # :ditto:
  def cbrt(value : Float64) : Float64
    LibM.cbrt_f64(value)
  end

  # :ditto:
  def cbrt(value)
    cbrt(value.to_f)
  end

  # Calculates the error function of *value*.
  def erf(value : Float32) : Float32
    LibM.erf_f32(value)
  end

  # :ditto:
  def erf(value : Float64) : Float64
    LibM.erf_f64(value)
  end

  # :ditto:
  def erf(value)
    erf(value.to_f)
  end

  # Calculates 1 minus the error function of *value*.
  def erfc(value : Float32) : Float32
    LibM.erfc_f32(value)
  end

  # :ditto:
  def erfc(value : Float64) : Float64
    LibM.erfc_f64(value)
  end

  # :ditto:
  def erfc(value)
    erfc(value.to_f)
  end

  # Calculates the gamma function of *value*.
  #
  # Note that `gamma(n)` is same as `fact(n - 1)` for integer `n > 0`.
  # However `gamma(n)` returns float and can be an approximation.
  def gamma(value : Float32) : Float32
    LibM.tgamma_f32(value)
  end

  # :ditto:
  def gamma(value : Float64) : Float64
    LibM.tgamma_f64(value)
  end

  # :ditto:
  def gamma(value) : Float64
    gamma(value.to_f)
  end

  # Calculates the logarithmic gamma of *value*.
  #
  # ```
  # Math.lgamma(2.96)
  # ```
  # is equivalent to
  # ```
  # Math.log(Math.gamma(2.96).abs)
  # ```
  def lgamma(value : Float32)
    {% if flag?(:darwin) %}
      LibM.gamma_f64(value).to_f32
    {% else %}
      LibM.gamma_f32(value)
    {% end %}
  end

  # :ditto:
  def lgamma(value : Float64) : Float64
    LibM.gamma_f64(value)
  end

  # :ditto:
  def lgamma(value) : Float64
    lgamma(value.to_f)
  end

  # Calculates the cylindrical Bessel function of the first kind of *value* for the given *order*.
  def besselj(order : Int32, value : Float32)
    {% if flag?(:darwin) || flag?(:win32) %}
      LibM.besselj_f64(order, value).to_f32
    {% else %}
      LibM.besselj_f32(order, value)
    {% end %}
  end

  # :ditto:
  def besselj(order : Int32, value : Float64) : Float64
    LibM.besselj_f64(order, value)
  end

  # :ditto:
  def besselj(order, value)
    besselj(order.to_i32, value.to_f)
  end

  # Calculates the cylindrical Bessel function of the first kind of *value* for order 0.
  def besselj0(value : Float32)
    {% if flag?(:darwin) || flag?(:win32) %}
      LibM.besselj0_f64(value).to_f32
    {% else %}
      LibM.besselj0_f32(value)
    {% end %}
  end

  # :ditto:
  def besselj0(value : Float64) : Float64
    LibM.besselj0_f64(value)
  end

  # :ditto:
  def besselj0(value)
    besselj0(value.to_f)
  end

  # Calculates the cylindrical Bessel function of the first kind of *value* for order 1.
  def besselj1(value : Float32)
    {% if flag?(:darwin) || flag?(:win32) %}
      LibM.besselj1_f64(value).to_f32
    {% else %}
      LibM.besselj1_f32(value)
    {% end %}
  end

  # :ditto:
  def besselj1(value : Float64) : Float64
    LibM.besselj1_f64(value)
  end

  # :ditto:
  def besselj1(value)
    besselj1(value.to_f)
  end

  # Calculates the cylindrical Bessel function of the second kind of *value* for the given *order*.
  def bessely(order : Int32, value : Float32)
    {% if flag?(:darwin) || flag?(:win32) %}
      LibM.bessely_f64(order, value).to_f32
    {% else %}
      LibM.bessely_f32(order, value)
    {% end %}
  end

  # :ditto:
  def bessely(order : Int32, value : Float64) : Float64
    LibM.bessely_f64(order, value)
  end

  # :ditto:
  def bessely(order, value)
    bessely(order.to_i32, value.to_f)
  end

  # Calculates the cylindrical Bessel function of the second kind of *value* for order 0.
  def bessely0(value : Float32)
    {% if flag?(:darwin) || flag?(:win32) %}
      LibM.bessely0_f64(value).to_f32
    {% else %}
      LibM.bessely0_f32(value)
    {% end %}
  end

  # :ditto:
  def bessely0(value : Float64) : Float64
    LibM.bessely0_f64(value)
  end

  # :ditto:
  def bessely0(value)
    bessely0(value.to_f)
  end

  # Calculates the cylindrical Bessel function of the second kind of *value* for order 1.
  def bessely1(value : Float32)
    {% if flag?(:darwin) || flag?(:win32) %}
      LibM.bessely1_f64(value).to_f32
    {% else %}
      LibM.bessely1_f32(value)
    {% end %}
  end

  # :ditto:
  def bessely1(value : Float64) : Float64
    LibM.bessely1_f64(value)
  end

  # :ditto:
  def bessely1(value)
    bessely1(value.to_f)
  end

  # Calculates the length of the hypotenuse from (0, 0) to (*value1*, *value2*).
  #
  # Equivalent to
  # ```
  # Math.sqrt(value1 ** 2 + value2 ** 2)
  # ```
  def hypot(value1 : Float32, value2 : Float32) : Float32
    LibM.hypot_f32(value1, value2)
  end

  # :ditto:
  def hypot(value1 : Float64, value2 : Float64) : Float64
    LibM.hypot_f64(value1, value2)
  end

  # :ditto:
  def hypot(value1, value2)
    hypot(value1.to_f, value2.to_f)
  end

  # Fused multiply-add; returns `value1 * value2 + value3`, performing a single
  # rounding instead of two.
  #
  # ```
  # Math.fma(0.1, 10.0, -1.0) # => 5.551115123125783e-17
  # 1.0 * 10.0 - 1.0          # => 0.0
  # ```
  def fma(value1 : Float32, value2 : Float32, value3 : Float32) : Float32
    LibM.fma_f32(value1, value2, value3)
  end

  # :ditto:
  def fma(value1 : Float64, value2 : Float64, value3 : Float64) : Float64
    LibM.fma_f64(value1, value2, value3)
  end

  # :ditto:
  def fma(value1, value2, value3)
    fma(value1.to_f, value2.to_f, value3.to_f)
  end

  # Returns the unbiased base 2 exponent of the given floating-point *value*.
  def ilogb(value : Float32) : Int32
    LibM.ilogb_f32(value)
  end

  # :ditto:
  def ilogb(value : Float64) : Int32
    LibM.ilogb_f64(value)
  end

  # :ditto:
  def ilogb(value)
    ilogb(value.to_f)
  end

  # Returns the unbiased radix-independent exponent of the given floating-point *value*.
  #
  # For `Float32` and `Float64` this is equivalent to `ilogb`.
  def logb(value : Float32) : Float32
    LibM.logb_f32(value)
  end

  # :ditto:
  def logb(value : Float64) : Float64
    LibM.logb_f64(value)
  end

  # :ditto:
  def logb(value)
    logb(value.to_f)
  end

  # Multiplies the given floating-point *value* by 2 raised to the power *exp*.
  def ldexp(value : Float32, exp : Int32) : Float32
    {% if flag?(:win32) %}
      # ucrt does not export `ldexpf` and instead defines it like this
      LibM.ldexp_f64(value, exp).to_f32!
    {% else %}
      LibM.ldexp_f32(value, exp)
    {% end %}
  end

  # :ditto:
  def ldexp(value : Float64, exp : Int32) : Float64
    LibM.ldexp_f64(value, exp)
  end

  # :ditto:
  def ldexp(value, exp)
    ldexp(value.to_f, exp.to_i32)
  end

  # Returns the floating-point *value* with its exponent raised by *exp*.
  #
  # For `Float32` and `Float64` this is equivalent to `ldexp`.
  def scalbn(value : Float32, exp : Int32) : Float32
    LibM.scalbn_f32(value, exp)
  end

  # :ditto:
  def scalbn(value : Float64, exp : Int32) : Float64
    LibM.scalbn_f64(value, exp)
  end

  # :ditto:
  def scalbn(value, exp)
    scalbn(value.to_f, exp.to_i32)
  end

  # :ditto:
  def scalbln(value : Float32, exp : Int64)
    LibM.scalbln_f32(value, exp)
  end

  # :ditto:
  def scalbln(value : Float64, exp : Int64) : Float64
    LibM.scalbln_f64(value, exp)
  end

  # :ditto:
  def scalbln(value, exp) : Float64
    scalbln(value.to_f, exp.to_i64)
  end

  # Decomposes the given floating-point *value* into a normalized fraction and an integral power of two.
  def frexp(value : Float32) : {Float32, Int32}
    {% if flag?(:win32) %}
      # ucrt does not export `frexpf` and instead defines it like this
      frac = LibM.frexp_f64(value, out exp)
      {frac.to_f32, exp}
    {% else %}
      frac = LibM.frexp_f32(value, out exp)
      {frac, exp}
    {% end %}
  end

  # :ditto:
  def frexp(value : Float64) : {Float64, Int32}
    frac = LibM.frexp_f64(value, out exp)
    {frac, exp}
  end

  # :ditto:
  def frexp(value)
    frexp(value.to_f)
  end

  # Returns the floating-point value with the magnitude of *value1* and the sign of *value2*.
  def copysign(value1 : Float32, value2 : Float32)
    LibM.copysign_f32(value1, value2)
  end

  # :ditto:
  def copysign(value1 : Float64, value2 : Float64) : Float64
    LibM.copysign_f64(value1, value2)
  end

  # :ditto:
  def copysign(value1, value2)
    copysign(value1.to_f, value2.to_f)
  end

  # Returns the greater of *value1* and *value2*.
  def max(value1 : Float32, value2 : Float32)
    LibM.max_f32(value1, value2)
  end

  # :ditto:
  def max(value1 : Float64, value2 : Float64) : Float64
    LibM.max_f64(value1, value2)
  end

  # :ditto:
  def max(value1, value2)
    value1 >= value2 ? value1 : value2
  end

  # Returns the smaller of *value1* and *value2*.
  def min(value1 : Float32, value2 : Float32)
    LibM.min_f32(value1, value2)
  end

  # :ditto:
  def min(value1 : Float64, value2 : Float64) : Float64
    LibM.min_f64(value1, value2)
  end

  # :ditto:
  def min(value1, value2)
    value1 <= value2 ? value1 : value2
  end

  # Computes the smallest nonnegative power of 2 that is greater than or equal
  # to *v*.
  #
  # The returned value has the same type as the argument. Raises `OverflowError`
  # if the result does not fit into the argument's type.
  #
  # ```
  # Math.pw2ceil(33) # => 64
  # Math.pw2ceil(64) # => 64
  # Math.pw2ceil(-5) # => 1
  # ```
  def pw2ceil(v : Int::Primitive)
    v.next_power_of_two
  end
end
