;; -*-theme-d-*-

;; Copyright (C) 2014  Tommi Höynälänmaa
;; Distributed under GNU Lesser General Public License version 3,
;; see file doc/LGPL-3.


(define-body (standard-library complex)


  (import (standard-library bitwise-arithmetic)
	  (standard-library basic-math)
	  (standard-library string-utilities))


  (define-simple-method complex
    (((re1 <real-number>) (im1 <real-number>)) <complex> (pure))
    (raise-simple 'complex:dispatch-error))

  (define-simple-method complex
    (((re1 <real>) (im1 <real>)) <complex> (pure))
    (make <complex> re1 im1))

  (define-simple-method complex
    (((re1 <real>) (im1 <integer>)) <complex> (pure))
    (make <complex> re1 (integer->real im1)))

  (define-simple-method complex
    (((re1 <real>) (im1 <rational>)) <complex> (pure))
    (make <complex> re1 (rational->real im1)))

  (define-simple-method complex
    (((re1 <integer>) (im1 <real>)) <complex> (pure))
    (make <complex> (integer->real re1) im1))

  (define-simple-method complex
    (((re1 <integer>) (im1 <integer>)) <complex> (pure))
    (make <complex> (integer->real re1) (integer->real im1)))

  (define-simple-method complex
    (((re1 <integer>) (im1 <rational>)) <complex> (pure))
    (make <complex> (integer->real re1) (rational->real im1)))

  (define-simple-method complex
    (((re1 <rational>) (im1 <real>)) <complex> (pure))
    (make <complex> (rational->real re1) im1))

  (define-simple-method complex
    (((re1 <rational>) (im1 <integer>)) <complex> (pure))
    (make <complex> (rational->real re1) (integer->real im1)))

  (define-simple-method complex
    (((re1 <rational>) (im1 <rational>)) <complex> (pure))
    (make <complex> (rational->real re1) (rational->real im1)))


  (define-simple-proc real->complex (((r <real>)) <complex> (pure))
    (make <complex> r 0.0))

  (define-simple-proc integer->complex (((n <integer>)) <complex> (pure))
    (make <complex> (integer->real n) 0.0))

  (define-simple-proc rational->complex (((rat <rational>)) <complex> (pure))
    (make <complex> (rational->real rat) 0.0))


  (define-simple-proc complex=? (((cx1 <complex>) (cx2 <complex>)) <boolean>
				 pure)
    (and (real=? (field-ref cx1 're) (field-ref cx2 're))
	 (real=? (field-ref cx1 'im) (field-ref cx2 'im))))

  (define-simple-proc complex= (((cx1 <complex>) (cx2 <complex>)) <boolean>
				pure)
    (and (real= (field-ref cx1 're) (field-ref cx2 're))
	 (real= (field-ref cx1 'im) (field-ref cx2 'im))))

  (define-simple-proc complex-integer=
      (((cx <complex>) (i <integer>)) <boolean> pure)
    (and (real-integer= (field-ref cx 're) i)
	 (real= (field-ref cx 'im) 0.0)))

  (define-simple-proc integer-complex=
      (((i <integer>) (cx <complex>)) <boolean> pure)
    (and (real-integer= (field-ref cx 're) i)
	 (real= (field-ref cx 'im) 0.0)))

  (define-simple-proc complex-real=
      (((cx <complex>) (r <real>)) <boolean> pure)
    (and (real= (field-ref cx 're) r)
	 (real= (field-ref cx 'im) 0.0)))

  (define-simple-proc real-complex=
      (((r <real>) (cx <complex>)) <boolean> pure)
    (and (real= (field-ref cx 're) r)
	 (real= (field-ref cx 'im) 0.0)))

  (define-simple-proc complex-rational=
      (((cx <complex>) (rat <rational>)) <boolean> pure)
    (and (real-rational= (field-ref cx 're) rat)
	 (real= (field-ref cx 'im) 0.0)))

  (define-simple-proc rational-complex=
      (((rat <rational>) (cx <complex>)) <boolean> pure)
    (and (real-rational= (field-ref cx 're) rat)
	 (real= (field-ref cx 'im) 0.0)))

  (add-method equal? complex=?)
  (add-method = complex=)
  (add-method = complex-integer=)
  (add-method = integer-complex=)
  (add-method = complex-real=)
  (add-method = real-complex=)
  (add-method = complex-rational=)
  (add-method = rational-complex=)


  (define-simple-proc real-part (((cx <complex>)) <real> (pure))
    (field-ref cx 're))


  (define-simple-proc imag-part (((cx <complex>)) <real> (pure))
    (field-ref cx 'im))


  (define-simple-proc make-polar (((mgn <real>) (phase <real>))
				  <complex>
				  (pure))
    (make <complex> (* mgn (r-cos phase)) (* mgn (r-sin phase))))


  (define-simple-proc c-abs (((cx <complex>)) <real> (pure))
    (let ((re1 (field-ref cx 're))
	  (im1 (field-ref cx 'im)))
      (r-sqrt (real+ (real* re1 re1) (real* im1 im1)))))


  (define-simple-proc c-angle (((cx <complex>)) <real> (pure))
    (r-atan2 (field-ref cx 'im) (field-ref cx 're)))


  (define-simple-proc c-neg (((cx <complex>)) <complex> (pure))
    (make <complex> (r-neg (field-ref cx 're)) (r-neg (field-ref cx 'im))))


  (define-simple-proc c-square (((cx <complex>)) <complex> (pure))
    (let ((re1 (field-ref cx 're))
	  (im1 (field-ref cx 'im)))
      (make <complex> (real- (r-square re1) (r-square im1))
	    (integer-real* 2 (real* re1 im1)))))


  (add-method abs c-abs)
  (add-method - c-neg)
  (add-method square c-square)

  (define-simple-proc complex+
      (((cx1 <complex>) (cx2 <complex>)) <complex> (pure))
    (make <complex>
	  (real+ (field-ref cx1 're) (field-ref cx2 're))
	  (real+ (field-ref cx1 'im) (field-ref cx2 'im))))

  (add-method + complex+)


  (define-simple-proc complex-
      (((cx1 <complex>) (cx2 <complex>)) <complex> (pure))
    (make <complex>
	  (real- (field-ref cx1 're) (field-ref cx2 're))
	  (real- (field-ref cx1 'im) (field-ref cx2 'im))))

  (add-method - complex-)


  (define-simple-proc complex*
      (((cx1 <complex>) (cx2 <complex>)) <complex> (pure))
    (let ((re1 (field-ref cx1 're))
	  (im1 (field-ref cx1 'im))
	  (re2 (field-ref cx2 're))
	  (im2 (field-ref cx2 'im)))
      (make <complex>
	    (real- (real* re1 re2) (real* im1 im2))
	    (real+ (real* re1 im2) (real* im1 re2)))))

  (add-method * complex*)



  (define-simple-proc complex/
      (((cx1 <complex>) (cx2 <complex>)) <complex> (pure))
    (let* ((re1 (field-ref cx1 're))
	   (im1 (field-ref cx1 'im))
	   (re2 (field-ref cx2 're))
	   (im2 (field-ref cx2 'im))
	   (denom (real+ (r-square re2) (r-square im2)))
	   (x (real+ (real* re1 re2) (real* im1 im2)))
	   (y (real- (real* im1 re2) (real* re1 im2))))
      (make <complex> (real/ x denom) (real/ y denom))))


  (add-method / complex/)


  (define-simple-proc real-complex+
      (((r <real>) (cx <complex>)) <complex> (pure))
    (make <complex> (real+ r (field-ref cx 're)) (field-ref cx 'im)))

  (define-simple-proc complex-real+
      (((cx <complex>) (r <real>)) <complex> (pure))
    (make <complex> (real+ r (field-ref cx 're)) (field-ref cx 'im)))

  (define-simple-proc integer-complex+
      (((n <integer>) (cx <complex>)) <complex> (pure))
    (make <complex> (integer-real+ n (field-ref cx 're)) (field-ref cx 'im)))

  (define-simple-proc complex-integer+
      (((cx <complex>) (n <integer>)) <complex> (pure))
    (make <complex> (integer-real+ n (field-ref cx 're)) (field-ref cx 'im)))

  (define-simple-proc rational-complex+
      (((rat <rational>) (cx <complex>)) <complex> (pure))
    (make <complex>
	  (rational-real+ rat (field-ref cx 're))
	  (field-ref cx 'im)))

  (define-simple-proc complex-rational+
      (((cx <complex>) (rat <rational>)) <complex> (pure))
    (make <complex>
	  (rational-real+ rat (field-ref cx 're))
	  (field-ref cx 'im)))


  (define-simple-proc real-complex-
      (((r <real>) (cx <complex>)) <complex> (pure))
    (make <complex> (real- r (field-ref cx 're)) (r-neg (field-ref cx 'im))))

  (define-simple-proc complex-real-
      (((cx <complex>) (r <real>)) <complex> (pure))
    (make <complex> (real- (field-ref cx 're) r) (field-ref cx 'im)))

  (define-simple-proc integer-complex-
      (((n <integer>) (cx <complex>)) <complex> (pure))
    (make <complex> (integer-real- n (field-ref cx 're))
	  (r-neg (field-ref cx 'im))))

  (define-simple-proc complex-integer-
      (((cx <complex>) (n <integer>)) <complex> (pure))
    (make <complex> (real-integer- (field-ref cx 're) n) (field-ref cx 'im)))

  (define-simple-proc rational-complex-
      (((rat <rational>) (cx <complex>)) <complex> (pure))
    (make <complex>
	  (rational-real- rat (field-ref cx 're))
	  (r-neg (field-ref cx 'im))))

  (define-simple-proc complex-rational-
      (((cx <complex>) (rat <rational>)) <complex> (pure))
    (make <complex> (real-rational- (field-ref cx 're) rat) (field-ref cx 'im)))


  (define-simple-proc real-complex*
      (((r <real>) (cx <complex>)) <complex> (pure))
    (make <complex> (real* r (field-ref cx 're)) (real* r (field-ref cx 'im))))

  (define-simple-proc complex-real*
      (((cx <complex>) (r <real>)) <complex> (pure))
    (make <complex> (real* r (field-ref cx 're)) (real* r (field-ref cx 'im))))

  (define-simple-proc integer-complex*
      (((n <integer>) (cx <complex>)) <complex> (pure))
    (make <complex>
	  (integer-real* n (field-ref cx 're))
	  (integer-real* n (field-ref cx 'im))))

  (define-simple-proc complex-integer*
      (((cx <complex>) (n <integer>)) <complex> (pure))
    (make <complex>
	  (integer-real* n (field-ref cx 're))
	  (integer-real* n (field-ref cx 'im))))

  (define-simple-proc rational-complex*
      (((rat <rational>) (cx <complex>)) <complex> (pure))
    (make <complex>
	  (rational-real* rat (field-ref cx 're))
	  (rational-real* rat (field-ref cx 'im))))

  (define-simple-proc complex-rational*
      (((cx <complex>) (rat <rational>)) <complex> (pure))
    (make <complex>
	  (rational-real* rat (field-ref cx 're))
	  (rational-real* rat (field-ref cx 'im))))


  (define-simple-proc real-complex/
      (((r <real>) (cx <complex>)) <complex> (pure))
    (let* ((re2 (field-ref cx 're))
	   (im2 (field-ref cx 'im))
	   (denom (real+ (r-square re2) (r-square im2)))
	   (x (real* r re2))
	   (y (r-neg (real* r im2))))
      (make <complex> (real/ x denom) (real/ y denom))))

  (define-simple-proc complex-real/
      (((cx <complex>) (r <real>)) <complex> (pure))
    (make <complex> (real/ (field-ref cx 're) r) (real/ (field-ref cx 'im) r)))

  (define-simple-proc integer-complex/
      (((n <integer>) (cx <complex>)) <complex> (pure))
    (let* ((re2 (field-ref cx 're))
	   (im2 (field-ref cx 'im))
	   (denom (real+ (r-square re2) (r-square im2)))
	   (x (integer-real* n re2))
	   (y (r-neg (integer-real* n im2))))
      (make <complex> (real/ x denom) (real/ y denom))))

  (define-simple-proc complex-integer/
      (((cx <complex>) (n <integer>)) <complex> (pure))
    (make <complex>
	  (real-integer/ (field-ref cx 're) n)
	  (real-integer/ (field-ref cx 'im) n)))

  (define-simple-proc rational-complex/
      (((rat <rational>) (cx <complex>)) <complex> (pure))
    (let* ((re2 (field-ref cx 're))
	   (im2 (field-ref cx 'im))
	   (denom (real+ (r-square re2) (r-square im2)))
	   (x (rational-real* rat re2))
	   (y (r-neg (rational-real* rat im2))))
      (make <complex> (real/ x denom) (real/ y denom))))

  (define-simple-proc complex-rational/
      (((cx <complex>) (rat <rational>)) <complex> (pure))
    (make <complex>
	  (real-rational/ (field-ref cx 're) rat)
	  (real-rational/ (field-ref cx 'im) rat)))

  (add-method + real-complex+)
  (add-method + complex-real+)
  (add-method + integer-complex+)
  (add-method + complex-integer+)
  (add-method + rational-complex+)
  (add-method + complex-rational+)

  (add-method - real-complex-)
  (add-method - complex-real-)
  (add-method - integer-complex-)
  (add-method - complex-integer-)
  (add-method - rational-complex-)
  (add-method - complex-rational-)

  (add-method * real-complex*)
  (add-method * complex-real*)
  (add-method * integer-complex*)
  (add-method * complex-integer*)
  (add-method * rational-complex*)
  (add-method * complex-rational*)

  (add-method / real-complex/)
  (add-method / complex-real/)
  (add-method / integer-complex/)
  (add-method / complex-integer/)
  (add-method / rational-complex/)
  (add-method / complex-rational/)


  (define-simple-proc c-sqrt (((cx <complex>)) <complex> (pure))
    (let ((abs-value (c-abs cx))
	  (phase (c-angle cx)))
      (make-polar (r-sqrt abs-value) (/ phase 2))))


  (define-simple-proc c-exp (((cx <complex>)) <complex> (pure))
    (let ((coeff (r-exp (field-ref cx 're)))
	  (im1 (field-ref cx 'im)))
      (make <complex> (* coeff (r-cos im1)) (* coeff (r-sin im1)))))


  (define-simple-proc c-log (((cx <complex>)) <complex> (pure))
    (let ((abs-value (c-abs cx))
	  (phase (c-angle cx)))
      (make <complex> (r-log abs-value) phase)))


  (define-simple-proc c-log10 (((cx <complex>)) <complex> (pure))
    (/ (c-log cx) (r-log 10.0)))


  (define-simple-proc c-expt (((base <complex>) (exponent <complex>))
			      <complex>
			      (pure))
    (c-exp (* exponent (c-log base))))


  (define-simple-proc c-sin (((cx <complex>)) <complex> (pure))
    (let* ((re1 (field-ref cx 're))
	   (im1 (field-ref cx 'im))
	   (exp-term1 (r-exp im1))
	   (exp-term2 (r-exp (- im1)))
	   (cos-term (r-cos re1))
	   (sin-term (r-sin re1))
	   (a (* (- exp-term2 exp-term1) cos-term))
	   (b (* (+ exp-term2 exp-term1) sin-term)))
      (make <complex> (/ b 2) (- (/ a 2)))))


  (define-simple-proc c-cos (((cx <complex>)) <complex> (pure))
    (let* ((re1 (field-ref cx 're))
	   (im1 (field-ref cx 'im))
	   (exp-term1 (r-exp im1))
	   (exp-term2 (r-exp (- im1)))
	   (cos-term (r-cos re1))
	   (sin-term (r-sin re1))
	   (a (* (+ exp-term2 exp-term1) cos-term))
	   (b (* (- exp-term2 exp-term1) sin-term)))
      (make <complex> (/ a 2) (/ b 2))))


  (define-simple-proc c-tan (((cx <complex>)) <complex> (pure))
    (let* ((re1 (field-ref cx 're))
	   (im1 (field-ref cx 'im))
	   (exp-term1 (r-exp im1))
	   (exp-term2 (r-exp (- im1)))
	   (cos-term (r-cos re1))
	   (sin-term (r-sin re1))
	   (a (* (- exp-term2 exp-term1) cos-term))
	   (b (* (+ exp-term2 exp-term1) sin-term))
	   (c (* (+ exp-term2 exp-term1) cos-term))
	   (d (* (- exp-term2 exp-term1) sin-term)))
      (/ (make <complex> b (- a)) (make <complex> c d))))


  (define-simple-proc c-asin (((cx <complex>)) <complex> (pure))
    (let* ((unit (make <complex> 1.0 0.0))
	   (imag-unit (make <complex> 0.0 1.0))
	   (term1 (* imag-unit cx))
	   (sq (c-square cx))
	   (term2 (c-sqrt (- unit sq)))
	   (log-term (c-log (+ term1 term2))))
      (* (make <complex> 0.0 -1.0) log-term)))


  (define-simple-proc c-acos (((cx <complex>)) <complex> (pure))
    (let* ((unit (make <complex> 1.0 0.0))
	   (imag-unit (make <complex> 0.0 1.0))
	   (sq (c-square cx))
	   (term2 (* imag-unit (c-sqrt (- unit sq))))
	   (log-term (c-log (- cx term2))))
      (* imag-unit log-term)))


  (define-simple-proc c-atan (((cx <complex>)) <complex> (pure))
    (let* ((unit (make <complex> 1.0 0.0))
	   (imag-unit (make <complex> 0.0 1.0))
	   (iz (make <complex> (- (field-ref cx 'im)) (field-ref cx 're)))
	   (term1 (c-log (- unit iz)))
	   (term2 (c-log (+ unit iz))))
      (* (make <complex> 0.0 0.5) (- term1 term2))))


  (define-simple-proc c-sinh (((cx <complex>)) <complex> (pure))
    (let ((term1 (c-exp cx))
	  (term2 (c-exp (- cx))))
      (* 0.5 (- term1 term2))))


  (define-simple-proc c-cosh (((cx <complex>)) <complex> (pure))
    (let ((term1 (c-exp cx))
	  (term2 (c-exp (- cx))))
      (* 0.5 (+ term1 term2))))


  (define-simple-proc c-tanh (((cx <complex>)) <complex> (pure))
    (let* ((exp-term1 (c-exp cx))
	   (exp-term2 (c-exp (- cx))))
      (/ (- exp-term1 exp-term2) (+ exp-term1 exp-term2))))


  (define-simple-proc c-asinh (((cx <complex>)) <complex> (pure))
    (let* ((unit (make <complex> 1.0 0.0))
	   (sq (c-square cx))
	   (term2 (c-sqrt (+ sq unit))))
      (c-log (+ cx term2))))


  (define-simple-proc c-acosh (((cx <complex>)) <complex> (pure))
    (let* ((unit (make <complex> 1.0 0.0))
	   (sq (c-square cx))
	   (term2 (c-sqrt (- sq unit))))
      (c-log (+ cx term2))))


  (define-simple-proc c-atanh (((cx <complex>)) <complex> (pure))
    (let* ((unit (make <complex> 1.0 0.0)))
      (* 0.5 (c-log (/ (+ unit cx) (- unit cx))))))
	   

  (define-simple-proc r-complex-log (((r <real>)) <complex> pure)
    (if (>= r 0.0)
	(complex (r-log r) 0.0)
	(complex (r-log (r-abs r)) gl-r-pi)))

 
  (define-simple-proc complex-real-expt (((base <complex>) (exponent <real>))
					 <complex> pure)
    (c-exp (* exponent (c-log base))))


  (define-simple-proc real-complex-expt (((base <real>) (exponent <complex>))
					 <complex> pure)
    (c-exp (* exponent (r-complex-log base))))


  (define-simple-proc r-log-neg (((r <real>)) <complex> pure)
    (complex (r-log (r-abs r)) gl-r-pi))


  (define-simple-proc r-log10-neg (((r <real>)) <complex> pure)
    (complex (r-log10 (r-abs r)) gl-r-pi/ln10))


  (define-simple-proc r-complex-expt (((base <real>) (exponent <real>))
				      <complex> pure)
    (c-exp (* exponent (r-complex-log base))))


  (define-simple-proc c-exp2 (((exponent <complex>)) <complex> pure)
    (c-exp (* exponent gl-r-ln2)))


  ;; Return 1.0+0.0i also for 0^0.
  (define-simple-proc c-nonneg-int-expt
      (((cx-base <complex>) (i-exponent <integer>)) <complex> pure)
    (assert (>= i-exponent 0))
    (let-mutable ((i-cur-exponent <integer> i-exponent)
		  (cx-cur-base <complex> cx-base)
		  (cx-sum <complex> (complex 1.0 0.0)))
      (until ((or (equal? i-cur-exponent 0) (equal? i-cur-exponent 1)))
	(if (not (equal? (bitwise-and i-cur-exponent 1) 0))
	    (set! cx-sum (* cx-sum cx-cur-base)))
	(set! cx-cur-base (square cx-cur-base))
	(set! i-cur-exponent (ash i-cur-exponent -1)))
      (cond
       ((= i-cur-exponent 0) cx-sum)
       ((= i-cur-exponent 1) (* cx-sum cx-cur-base))
       (else (raise-simple 'c-nonneg-int-expt:internal-error)))))


  (define-simple-proc c-int-expt (((cx-base <complex>) (i-expt <integer>))
				  <complex> pure)
    (if (>= i-expt 0)
	(c-nonneg-int-expt cx-base i-expt)
	(/ 1.0 (c-nonneg-int-expt cx-base (i-neg i-expt)))))


  (define-simple-proc complex->string
      (((cx <complex>)) <string> (pure))
    (let* ((re1 (field-ref cx 're))
	   (im1 (field-ref cx 'im))
	   (ch (if (and (>= im1 0) (finite? im1)) "+" "")))
      (string-append (real->string re1)
		     ch
		     (real->string im1)
		     "i")))

  
  (add-method object->string complex->string))
