#### libremiliacr
#### Copyright(C) 2020-2024 Remilia Scarlet <remilia@posteo.jp>
####
#### This program is free software: you can redistribute it and/or modify
#### it under the terms of the GNU General Public License as published
#### the Free Software Foundation, either version 3 of the License, or
#### (at your option) any later version.
####
#### This program is distributed in the hope that it will be useful,
#### but WITHOUT ANY WARRANTY; without even the implied warranty of
#### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
#### GNU General Public License for more details.
####
#### You should have received a copy of the GNU General Public License
#### along with this program.If not, see<http:####www.gnu.org/licenses/.>
require "math"

module RemiMath
  extend self

  # The value of pi divided by 2.
  HALF_PI = ::Math::PI / 2.0

  # The value of pi multiplied by 2.
  TWO_PI = ::Math::PI * 2.0

  # The value of pi multiplied by 4.
  FOUR_PI = ::Math::PI * 4.0

  # The value of pi divided by 4.
  QUARTER_PI = ::Math::PI / 4.0

  # A version of sine that trades accuracy for speed.  The intended
  # domain is [0, pi/2].
  def fastSin(val)
    sqr = val * val
    ret = -2.39e-08
    ret *= sqr
    ret += 2.7526e-06
    ret *= sqr
    ret -= 1.98409e-04
    ret *= sqr
    ret += 8.3333315e-03
    ret *= sqr
    ret -= 1.666666664e-01
    ret *= sqr
    ret += 1.0
    ret * val
  end

  # A version of sine that trades even more accuracy for speed.  The
  # intended domain is [0, pi/2].
  def fasterSin(val)
    sqr = val * val
    ret = 7.61e-03
    ret *= sqr
    ret -= 1.6605e-01
    ret *= sqr
    ret += 1.0
    ret * val
  end

  # A version of cosine that trades accuracy for speed.  The intended
  # domain is [0, pi/2].
  def fastCos (val)
    sqr = val * val
    ret = -2.605e-07
    ret *= sqr
    ret += 2.47609e-05
    ret *= sqr
    ret -= 1.3888397e-03
    ret *= sqr
    ret += 4.16666418e-02
    ret *= sqr
    ret -= 4.999999963e-01
    ret *= sqr
    ret + 1.0
  end

  # A version of cosine that trades even more accuracy for speed.  The
  # intended domain is [0, pi/2].
  def fasterCos (val)
    sqr = val * val
    ret = 3.705e-02
    ret *= sqr
    ret -= 4.967e-01
    ret *= sqr
    ret + 1.0
  end

  # A version of tangent that trades accuracy for speed.  The intended
  # domain is [0, pi/4].
  def fastTan (val)
    sqr = val * val
    ret = 9.5168091e-03
    ret *= sqr
    ret += 2.900525e-03
    ret *= sqr
    ret += 2.45650893e-02
    ret *= sqr
    ret += 5.33740603e-02
    ret *= sqr
    ret += 1.333923995e-01
    ret *= sqr
    ret += 3.333314036e-01
    ret *= sqr
    ret += 1.0
    ret * val
  end

  # A version of tangent that trades even more accuracy for speed.
  # The intended domain is [0, pi/4].
  def fasterTan (val)
    sqr = val * val
    ret = 2.033e-01
    ret *= sqr
    ret += 3.1755e-01
    ret *= sqr
    ret += 1.0
    ret * val
  end

  # A version of inverse sine that trades accuracy for speed.  The
  # intended domain is [0, 1]
  def fastInvSin (val)
    root = ::Math.sqrt(1.0 - val)
    ret = -0.0187293
    ret *= val
    ret += 0.0742610
    ret *= val
    ret -= 0.2121144
    ret *= val
    ret += 1.5707288
    HALF_PI - root * ret
  end

  # A version of inverse cosine that trades accuracy for speed.  The
  # intended domain is [0, 1]
  def fastInvCos (val)
    root = ::Math.sqrt(1.0 - val)
    ret = -0.0187293
    ret *= val
    ret += 0.0742610
    ret *= val
    ret -= 0.2121144
    ret *= val
    ret += 1.5707288
    ret * root
  end

  # A version of inverse tangent that trades accuracy for speed.  The
  # intended domain is [-1, 1]
  def fastInvTan (val)
    sqr = val * val
    ret = 0.0028662257
    ret *= sqr
    ret -= 0.0161657367
    ret *= sqr
    ret += 0.0429096138
    ret *= sqr
    ret -= 0.0752896400
    ret *= sqr
    ret += 0.1065626393
    ret *= sqr
    ret -= 0.1420889944
    ret *= sqr
    ret += 0.1999355085
    ret *= sqr
    ret -= 0.3333314528
    ret *= sqr
    ret += 1.0
    ret * val
  end

  # A version of inverse tangent that trades even more accuracy for
  # speed.  The intended domain is [-1, 1]
  def fasterInvTan (val)
    sqr = val * val
    ret = 0.0208351
    ret *= sqr
    ret -= 0.085133
    ret *= sqr
    ret += 0.180141
    ret *= sqr
    ret -= 0.3302995
    ret *= sqr
    ret += 0.999866
    ret * val
  end

  # A version of inverse hyperbolic tangent that trades accuracy for speed.  The
  # intended domain is [-3, 3].  The minimum error in this range is about 0.0,
  # and the maximum error is about 0.024.
  #
  # Note: This enforces its intended domain.  If `x` is less than -3, then this
  # always returns -1.0.  Likewise, if `x` is greater than 3, this always
  # returns 1.0.
  @[AlwaysInline]
  def fastTanh(x : Float64) : Float64
    if x < -3
      -1.0
    elsif x > 3
      1.0
    else
      x * ( 27.0 + x * x ) / ( 27.0 + 9.0 * x * x )
    end
  end

  # A version of arc tangent that trades accuracy for speed.  The intended
  # domain is [-1, 1].  The maximum error in this range is about 0.0015089
  # radians.
  @[AlwaysInline]
  def fastAtan(x : Float64) : Float64
    # https://nghiaho.com/?p=997
    QUARTER_PI * x - x * (x.abs - 1) * (0.2447 + 0.0663 * x.abs)
  end
end
