;;; Arl Standard Library - Math Helpers

(module math
  (export % inc dec clamp within? signum
          expt
          quotient remainder modulo
          gcd lcm
          make-rectangular make-polar real-part imag-part magnitude angle)



  ;;' @section Arithmetic Helpers

  ;;' @description Modulo helper using R %%.
  ;;' @param x Dividend
  ;;' @param y Divisor
  ;;' @examples
  ;;' (% 10 3)   ; => 1
  ;;' (% 17 5)   ; => 2
  ;;' @assert
  ;;' (assert-equal 1 (% 10 3))
  ;;' (assert-equal 2 (% 17 5))
  (define %
    (lambda (x y)
      (%% x y)))

  ;;' @description Increment numeric value by n (default 1).
  ;;' @param x Number to increment
  ;;' @param n Amount to add (default 1)
  ;;' @examples
  ;;' (inc 5)       ; => 6
  ;;' (inc 5 3)     ; => 8
  ;;' @assert
  ;;' (assert-equal 6 (inc 5))
  ;;' (assert-equal 8 (inc 5 3))
  ;;' @seealso dec
  (define inc
    (lambda (x (n 1))
      (+ x n)))

  ;;' @description Decrement numeric value by n (default 1).
  ;;' @param x Number to decrement
  ;;' @param n Amount to subtract (default 1)
  ;;' @examples
  ;;' (dec 5)       ; => 4
  ;;' (dec 5 3)     ; => 2
  ;;' @assert
  ;;' (assert-equal 4 (dec 5))
  ;;' (assert-equal 2 (dec 5 3))
  ;;' @seealso inc
  (define dec
    (lambda (x (n 1))
      (- x n)))

  ;;' @description Clamp numeric value x to the inclusive range [lo, hi].
  ;;' @param x Value to clamp
  ;;' @param lo Lower bound (inclusive)
  ;;' @param hi Upper bound (inclusive)
  ;;' @examples
  ;;' (clamp 5 0 10)   ; => 5
  ;;' (clamp -3 0 10)  ; => 0
  ;;' (clamp 15 0 10)  ; => 10
  ;;' @assert
  ;;' (assert-equal 5 (clamp 5 0 10))
  ;;' (assert-equal 0 (clamp -3 0 10))
  ;;' (assert-equal 10 (clamp 15 0 10))
  ;;' @seealso within?
  (define clamp
    (lambda (x lo hi)
      (if (< x lo)
        lo
        (if (> x hi) hi x))))

  ;;' @description Return #t if x is within the inclusive range [lo, hi].
  ;;' @param x Value to test
  ;;' @param lo Lower bound (inclusive)
  ;;' @param hi Upper bound (inclusive)
  ;;' @examples
  ;;' (within? 5 0 10)   ; => #t
  ;;' (within? -3 0 10)  ; => #f
  ;;' (within? 10 0 10)  ; => #t
  ;;' @assert
  ;;' (assert-true (within? 5 0 10))
  ;;' (assert-false (within? -3 0 10))
  ;;' (assert-true (within? 10 0 10))
  ;;' @seealso clamp
  (define within?
    (lambda (x lo hi)
      (if (< x lo)
        #f
        (if (> x hi) #f #t))))

  ;;' @description Return sign of x: -1, 0, or 1.
  ;;' @param x Numeric value
  ;;' @examples
  ;;' (signum 42)    ; => 1
  ;;' (signum -5)    ; => -1
  ;;' (signum 0)     ; => 0
  ;;' @assert
  ;;' (assert-equal 1 (signum 42))
  ;;' (assert-equal -1 (signum -5))
  ;;' (assert-equal 0 (signum 0))
  ;;' @signature (signum x)
  (define signum sign)

  ;;' @section Power and Roots

  ;;' @description Return base raised to power.
  ;;' @param base Base number
  ;;' @param power Exponent
  ;;' @examples
  ;;' (expt 2 10)   ; => 1024
  ;;' (expt 3 3)    ; => 27
  ;;' @assert
  ;;' (assert-equal 1024 (expt 2 10))
  ;;' (assert-equal 27 (expt 3 3))
  (define expt
    (lambda (base power)
      (^ base power)))

  ;;' @section Integer Division

  ;;' @description Return integer quotient of x divided by y.
  ;;' @param x Dividend
  ;;' @param y Divisor
  ;;' @examples
  ;;' (quotient 10 3)   ; => 3
  ;;' (quotient -10 3)  ; => -3
  ;;' @assert
  ;;' (assert-equal 3 (quotient 10 3))
  ;;' (assert-equal -3 (quotient -10 3))
  ;;' @seealso remainder, modulo
  (define quotient
    (lambda (x y)
      (as.integer (trunc (/ x y)))))

  ;;' @description Return remainder of x divided by y (same sign as x).
  ;;' @param x Dividend
  ;;' @param y Divisor
  ;;' @examples
  ;;' (remainder 10 3)   ; => 1
  ;;' (remainder -10 3)  ; => -1
  ;;' @assert
  ;;' (assert-equal 1 (remainder 10 3))
  ;;' (assert-equal -1 (remainder -10 3))
  ;;' @seealso quotient, modulo
  (define remainder
    (lambda (x y)
      (- x (* y (quotient x y)))))

  ;;' @description Return modulo of x and y (same sign as y).
  ;;' @param x Dividend
  ;;' @param y Divisor
  ;;' @examples
  ;;' (modulo 10 3)    ; => 1
  ;;' (modulo -10 3)   ; => 2
  ;;' @assert
  ;;' (assert-equal 1 (modulo 10 3))
  ;;' (assert-equal 2 (modulo -10 3))
  ;;' @seealso quotient, remainder, %
  (define modulo
    (lambda (x y)
      (%% x y)))

  ;;' @section Number Theory

  ;;' @description Return greatest common divisor of arguments.
  ;;' @signature (gcd a b ...)
  ;;' @param args Two or more integers
  ;;' @examples
  ;;' (gcd 12 8)      ; => 4
  ;;' (gcd 12 8 6)    ; => 2
  ;;' @assert
  ;;' (assert-equal 4 (gcd 12 8))
  ;;' (assert-equal 2 (gcd 12 8 6))
  ;;' @seealso lcm
  (define gcd
    (lambda (. args)
      (if (== (length args) 0)
        0
        (if (== (length args) 1)
          (abs ([[ args 1))
          (begin
            ;; GCD of two numbers using Euclidean algorithm.
            (define gcd-two
              (lambda (a b)
                (if (== b 0)
                  (abs a)
                  (gcd-two b (modulo a b)))))
            (Reduce gcd-two args))))))

  ;;' @description Return least common multiple of arguments.
  ;;' @signature (lcm a b ...)
  ;;' @param args Two or more integers
  ;;' @examples
  ;;' (lcm 4 6)       ; => 12
  ;;' (lcm 3 4 5)     ; => 60
  ;;' @assert
  ;;' (assert-equal 12 (lcm 4 6))
  ;;' (assert-equal 60 (lcm 3 4 5))
  ;;' @seealso gcd
  (define lcm
    (lambda (. args)
      (if (== (length args) 0)
        1
        (if (== (length args) 1)
          (abs ([[ args 1))
          (begin
            ;; LCM of two numbers using a*b/gcd(a,b).
            (define lcm-two
              (lambda (a b)
                (if (if (== a 0) #t (== b 0))
                  0
                  (/ (abs (* a b)) (gcd a b)))))
            (Reduce lcm-two args))))))

  ;;' @section Complex Number Utilities

  ;;' @description Construct a complex number from real and imaginary parts.
  ;;' @param real Real component
  ;;' @param imag Imaginary component
  ;;' @examples
  ;;' (make-rectangular 3 4)  ; => 3+4i
  ;;' @assert
  ;;' (assert-equal (complex :real 3 :imaginary 4) (make-rectangular 3 4))
  ;;' @seealso make-polar, real-part, imag-part
  (define make-rectangular
    (lambda (real imag)
      (complex :real real :imaginary imag)))

  ;;' @description Construct a complex number from polar coordinates (magnitude and angle).
  ;;' @param magnitude Distance from origin
  ;;' @param angle Angle in radians
  ;;' @examples
  ;;' (make-polar 5 0)      ; => 5+0i
  ;;' @assert
  ;;' (assert-equal (complex :modulus 5 :argument 0) (make-polar 5 0))
  ;;' @seealso make-rectangular, magnitude, angle
  (define make-polar
    (lambda (magnitude angle)
      (complex :modulus magnitude :argument angle)))

  ;;' @description Extract the real part of a complex number.
  ;;' @param z Complex number
  ;;' @examples
  ;;' (real-part (make-rectangular 3 4))  ; => 3.0
  ;;' @assert
  ;;' (assert-equal 3.0 (real-part (make-rectangular 3 4)))
  ;;' @seealso imag-part, make-rectangular
  (define real-part
    (lambda (z)
      (Re z)))

  ;;' @description Extract the imaginary part of a complex number.
  ;;' @param z Complex number
  ;;' @examples
  ;;' (imag-part (make-rectangular 3 4))  ; => 4.0
  ;;' @assert
  ;;' (assert-equal 4.0 (imag-part (make-rectangular 3 4)))
  ;;' @seealso real-part, make-rectangular
  (define imag-part
    (lambda (z)
      (Im z)))

  ;;' @description Compute the magnitude (modulus) of a complex number.
  ;;' @param z Complex number
  ;;' @examples
  ;;' (magnitude (make-rectangular 3 4))  ; => 5.0
  ;;' @assert
  ;;' (assert-equal 5.0 (magnitude (make-rectangular 3 4)))
  ;;' @seealso angle, make-polar
  (define magnitude
    (lambda (z)
      (Mod z)))

  ;;' @description Compute the angle (argument) of a complex number.
  ;;' @param z Complex number
  ;;' @examples
  ;;' (angle (make-rectangular 1 1))  ; => 0.7853981633974483 (pi/4)
  ;;' @assert
  ;;' (assert-true (< (abs (- (angle (make-rectangular 1 1)) (/ pi 4))) 1e-10))
  ;;' @seealso magnitude, make-polar
  (define angle
    (lambda (z)
      (Arg z)))
)
