#lang racket/base

;; Extra math functions and constants.

(require racket/math racket/flonum racket/match racket/list racket/contract
         "contract.rkt" "contract-doc.rkt")

(provide (all-defined-out))

(define-struct ivl (min max) #:transparent
  #:guard (λ (a b _)
            (cond [(and (regular? a) (regular? b))  (values (min* a b) (max* a b))]
                  [else  (values a b)])))

(defproc (bounds->intervals [xs (listof real?)]) (listof ivl?)
  (cond [((length xs) . < . 2)  (raise-type-error 'bounds->intervals "list with length >= 2" xs)]
        [else
         (for/list ([x1  (in-list xs)]
                    [x2  (in-list (rest xs))])
           (ivl x1 x2))]))

(define -pi (- pi))
(define 2pi (* 2 pi))
(define -1/2pi (* -1/2 pi))
(define 1/2pi (* 1/2 pi))

(define 180/pi (fl/ 180.0 pi))
(define pi/180 (fl/ pi 180.0))

(defproc (degrees->radians [d real?]) real?
  (fl* (exact->inexact d) pi/180))

(defproc (radians->degrees [r real?]) real?
  (fl* (exact->inexact r) 180/pi))

(define (atan2 y x)
  (if (and (zero? y) (zero? x)) 0 (atan y x)))

(define (real-modulo x y) (- x (* y (floor (/ x y)))))

(define (sum f xs) (apply + (map f xs)))

(define (flmodulo x y) (fl- x (fl* y (flfloor (fl/ x y)))))
(define (fldist2 x y) (flsqrt (fl+ (fl* x x) (fl* y y))))
(define (fldist3 x y z) (flsqrt (fl+ (fl* x x) (fl+ (fl* y y) (fl* z z)))))

(define (flsum ps)
  (for/fold ([sum 0.0]) ([p  (in-list ps)])
    (fl+ sum p)))

(define (factorial n)
  (if (zero? n) 1 (* n (factorial (sub1 n)))))

(define (alpha-blend x1 x2 a)
  (+ (* a x1) (* (- 1 a) x2)))

(define (build-linear-seq start step num)
  (for/list ([n  (in-range num)])
    (+ start (* n step))))

(defproc (linear-seq [start real?] [end real?] [num exact-nonnegative-integer?]
                     [#:start? start? boolean? #t]
                     [#:end? end? boolean? #t]) (listof real?)
  (cond
    [(zero? num)  empty]
    ; ambiguous request: arbitrarily return start
    [(and start? end? (= 1 num))  (list start)]
    [else
     (define size (- end start))
     (define step (/ size (cond [(and start? end?)  (- num 1)]
                                [(or start? end?)   (- num 1/2)]
                                [else               num])))
     (define real-start
       (cond [start?  start]
             [else    (+ start (* 1/2 step))]))
     
     (build-linear-seq real-start step num)]))

(defproc (linear-seq* [points (listof real?)] [num exact-nonnegative-integer?]
                      [#:start? start? boolean? #t]
                      [#:end? end? boolean? #t]) (listof real?)
  (let/ec return
    (when (empty? points) (raise-type-error 'linear-seq* "nonempty (listof real?)" points))
    
    (define pts (list->vector points))
    (define len (vector-length pts))
    
    (define indexes (linear-seq 0 (sub1 len) num #:start? start? #:end? end?))
    (define int-parts (map floor indexes))
    (define frac-parts (map - indexes int-parts))
    (map (λ (i f)
           (if (= i (sub1 len))
               (vector-ref pts i)
               (alpha-blend (vector-ref pts (add1 i)) (vector-ref pts i) f)))
         int-parts frac-parts)))

(define (nan? x) (eqv? x +nan.0))
(define (infinite? x) (or (eqv? x +inf.0) (eqv? x -inf.0)))
(define (regular? x) (and (real? x) (not (nan? x)) (not (infinite? x))))

(define (min2 x y)
  (cond [(x . < . y)  x]
        [(y . < . x)  y]
        [(exact? x)  x]
        [else  y]))

(define (max2 x y)
  (cond [(x . > . y)  x]
        [(y . > . x)  y]
        [(exact? x)  x]
        [else  y]))

(define (clamp x mn mx) (min (max x mn) mx))
(define (clamp* x mn mx) (min2 (max2 x mn) mx))

(define (min* x . xs) (foldl min2 x xs))
(define (max* x . xs) (foldl max2 x xs))

(define (maybe-min x . xs)
  (for/fold ([x x]) ([y  (in-list xs)])
    (if x (if y (min* x y) x)
        (if y y #f))))

(define (maybe-max x . xs)
  (for/fold ([x x]) ([y  (in-list xs)])
    (if x (if y (max* x y) x)
        (if y y #f))))

(define (floor-log10 x)
  (inexact->exact (floor (/ (log (abs x)) (log 10)))))

(define (ceiling-log10 x)
  (inexact->exact (ceiling (/ (log (abs x)) (log 10)))))

(define (bin-samples bin-bounds xs)
  (let* ([bin-bounds  (filter (compose not nan?) (remove-duplicates bin-bounds))]
         [bin-bounds  (sort bin-bounds <)]
         [x-min  (first bin-bounds)]
         [x-max  (last bin-bounds)]
         [xs  (filter (λ (x) (<= x-min x x-max)) xs)]
         [xs  (sort xs <)])
    (define-values (res rest-xs)
      (for/fold ([res empty] [xs xs]) ([x1  (in-list bin-bounds)]
                                       [x2  (in-list (rest bin-bounds))])
        (define-values (lst rest-xs)
          (let loop ([lst empty] [xs xs])
            (if (and (not (empty? xs)) (<= x1 (first xs) x2))
                (loop (cons (first xs) lst) (rest xs))
                (values lst xs))))
        (values (cons (reverse lst) res)
                rest-xs)))
    (reverse res)))

(define (polar->cartesian θ r)
  (let ([θ  (exact->inexact θ)]
        [r  (exact->inexact r)])
    (vector (fl* r (flcos θ))
            (fl* r (flsin θ)))))

(define (3d-polar->3d-cartesian θ ρ r)
  (let* ([θ  (exact->inexact θ)]
         [ρ  (exact->inexact ρ)]
         [r  (exact->inexact r)]
         [cos-ρ  (flcos ρ)])
    (vector (fl* r (fl* (flcos θ) cos-ρ))
            (fl* r (fl* (flsin θ) cos-ρ))
            (fl* r (flsin ρ)))))
