common/scheme-functions.ss
#lang scheme

(require "point.ss")
(require "vector.ss")
(require "../utils.ss")
;(require (prefix-in point: "point.ss"))
;(require (prefix-in vector: "vector.ss"))
;
;(define xyz point:xyz)
;(define xy  point:xy )
;(define cx  point:cx )
;(define cy  point:cy )
;(define cz  point:cz )
;
;(define vxyz vector:vxyz)
;(define vxy  vector:vxy )
;(define vx   vector:vx  )
;(define vy   vector:vy  )
;(define vz   vector:vz  )


(require (only-in scheme
                  + - * /))
                      ;; Será que faz sentido?
;                      sin cos tan)))
;(require (prefix-in
;          s: (only-in "coordinates-new.ss"
;                      ;; Será que faz sentido?
;                      sec csc cot
;                      sinh cosh tanh asinh acosh atanh)))

(provide (rename-out [+** +]
                     [-** -]
                     [*** *]
                     [/** /]))

;         ;; Será que faz sentido?
;         sin cos tan sec csc cot
;         sinh cosh tanh asinh acosh atanh)



;;;; Prepare functions to map through a point/vector's coordinates
(define-syntax define-curried-version
  (syntax-rules ()
    [(_ name f)
     (define name
       (case-lambda
         ;; With zero arguments, perform the default behaviour.
         [()
          (f)]
         ;; With one argument, curry the function.
         [(arg)
          (lambda args
            (apply f arg args))]
         ;; With more than one arguments, perform the default behaviour.
         [args
          (apply f args)]))]))


;;;; Defines a point/vector version of function f
;; f must be a commutative function of two arguments
;;   If one of those arguments is a point or a vector,
;;   the function is mapped across its coordinates.

;; 2 (or more*) argument version
(define-syntax define-point/vector-version-2*
  (lambda (stx)
    (syntax-case stx ()
      [(_ name f)
       #`(define name
           (let ([map-point (lambda (p f)
                              (if (point-3d? p)
                                  (xyz (f (cx p))
                                       (f (cy p))
                                       (f (cz p)))
                                  (xy  (f (cx p))
                                       (f (cy p)))))]
                 [map-vector (lambda (v f)
                               (if (vector-3d? v)
                                   (vxyz (f (vx v))
                                         (f (vy v))
                                         (f (vz v)))
                                   (vxy  (f (vx v))
                                        (f (vy v)))))])
             (case-lambda
               ;; With zero arguments, perform the default behaviour.
               [()
                (f)]
               ;; With one argument, perform the default behaviour.
               [(arg)
                (f arg)]
              ;; With two arguments, check for point/vector.
               [(a1 a2)
                (let ([error-out (lambda ()
                                   (error #,(syntax->datum #'(quote f))
                                          "Function ~s is not applicable to objects: '~s' and '~s'."
                                          #,(syntax->datum #'(quote f)) a1 a2))])
                  (cond [(number? a1)
                         (cond [(number? a2)
                                (f a1 a2)]
                               [(point? a2)
                                (map-point a2 (lambda (x) (f x a2)))]
                               [(gvector? a2)
                               (map-vector a2 (lambda (x) (f x a2)))]
                               [else (error-out)])]
                        [(point? a1)
                         (cond [(number? a2)
                                (map-point a1 (lambda (x) (f x a2)))]
                               [(point? a2)
                                (error "Can't apply function " name " to two points.")]
                               [(gvector? a2)
                                (when (not (same-dimension? a1 a2))
                                  (error a1 " and " a2 " are not of the same dimension."))
                                (if (point-3d? a1)
                                    (xyz (f (cx a1) (vx a2))
                                        (f (cy a1) (vy a2))
                                        (f (cz a1) (vz a2)))
                                    (xy  (f (cx a1) (vx a2))
                                         (f (cy a1) (vy a2))))]
                               [else (error-out)])]
                        [(gvector? a1)
                         (cond [(number? a2)
                               (map-vector a1 (lambda (x) (f x a2)))]
                               [(point? a2)
                                (when (not (same-dimension? a1 a2))
                                  (error a1 " and " a2 " are not of the same dimension."))
                                (if (vector-3d? a1)
                                    (vxyz (f (vx a1) (cx a2))
                                          (f (vy a1) (cy a2))
                                          (f (vz a1) (cz a2)))
                                    (vxy  (f (vx a1) (cx a2))
                                         (f (vy a1) (cy a2))))]
                               [(gvector? a2)
                                (when (not (same-dimension? a1 a2))
                                  (error a1 " and " a2 " are not of the same dimension."))
                                (if (vector-3d? a1)
                                    (vxyz (f (vx a1) (vx a2))
                                          (f (vy a1) (vy a2))
                                         (f (vz a1) (vz a2)))
                                    (vxy  (f (vx a1) (vx a2))
                                          (f (vy a1) (vy a2))))]
                               [else (error-out)])]
                        [else (f a1 a2)]))] ;; Default.
               ;; With more than two arguments, perform the default behaviour.
               [args
               (apply f args)])))])))

;; 1 argument version
(define-syntax-rule (define-point/vector-version-1 name f)
  (define name
    (let ([map-point (lambda (p f)
                       (xyz (f (cx p))
                            (f (cy p))
                            (f (cz p))))]
          [map-vector (lambda (v f)
                        (vxyz (f (vx v))
                              (f (vy v))
                              (f (vz v))))])
      (case-lambda
        ;; With zero arguments, perform the default behaviour.
        [()
         (f)]
        ;; With one argument, perform the default behaviour.
        [(arg)
         (cond [(point? arg)
                (map-point arg f)]
               [(gvector? arg)
                (map-vector arg f)]
               [else (f arg)])]))))

(define-syntax define-point/vector-versions-1
  (syntax-rules ()
    [(_ new old)
     (define-point/vector-version-1 new old)]
    [(_ new old more ...)
     (begin
       (define-point/vector-version-1 new old)
       (define-point/vector-versions-1 more ...))]))


;;;; And now... The definitions
(define-point/vector-version-2* +* +)
(define-point/vector-version-2* -* -)
(define-point/vector-version-2* ** *)
(define-point/vector-version-2* /* /)

(define-curried-version +** +*)
(define-curried-version -** -*)
(define-curried-version *** **)
(define-curried-version /** /*)


;(define-point/vector-versions-1
;  sin s:sin
;  cos s:cos
;  tan s:tan
;  sec s:sec
;  csc s:csc
;  cot s:cot
;
;  sinh  s:sinh
;  cosh  s:cosh
;  tanh  s:tanh
;  asinh s:asinh
;  acosh s:acosh
;  atanh s:atanh
;  )