#lang racket/base
(require mzlib/etc 
         racket/list 
         ffi/unsafe
         racket/runtime-path
         racket/class
         (for-syntax racket/base))

(define-runtime-path plplot-path '(so "libplplot"))
(define-runtime-path font-dir "fonts")

(define libplplot (ffi-lib plplot-path))

(define plplotlibdir (get-ffi-obj "plplotLibDir" libplplot _string))

;; set the lib dir to contain the fonts:
(let ([path font-dir])
  ;; free current pointer, if any:
  (let ([p (get-ffi-obj "plplotLibDir" libplplot _pointer)])
    (when p (free p)))
  ;; install new value:
  (set-ffi-obj! "plplotLibDir" libplplot _bytes
		;; malloc the string, since the GC won't see the static variable:
		(let* ([gced-bytes (path->bytes path)]
		       [len (bytes-length gced-bytes)]
		       [p (malloc (add1 len) 'raw)]
		       [malloced-bytes (make-sized-byte-string p len)])
		  (bytes-copy! malloced-bytes 0 gced-bytes)
		  ;; set nul terminator:
		  (ptr-set! p _byte len 0)
		  malloced-bytes)))

(define-cstruct _dc_Dev
  ([user_data _pointer]
   [drawLine _fpointer]
   [drawLines _fpointer]
   [fillPoly _fpointer]
   [setWidth _fpointer]
   [setColor _fpointer]
   [setColorRGB _fpointer]
   [startPage _fpointer]
   [endPage _fpointer]
   [endDoc _fpointer]))

(define _PLINT _int)

(define _plflt _double*)
(define _plint _int)

;; While an array generated from a list is passed to an
;; plot library function, we might perform a GC through
;; the back-end drawing operation. So, arrays must be
;; allocated as non-moving objects
(define-fun-syntax _list/still
  (lambda (stx)
    (syntax-case stx (i)
      [(_ i ty) #'(_list/still i ty 'atomic-interior)]
      [(_ i ty mode)
       #'(type: _pointer
                pre:  (x => (list->cblock/mode x ty mode)))])))

(define-fun-syntax _matrix-of
  (lambda (stx)
    (syntax-case stx (i)
      [(_ ty)
       #'(_list/still i (_list/still i ty) 'interior)])))

(define (list->cblock/mode l type mode)
  (if (null? l)
      #f ; null => NULL
      (let ([cblock (malloc (length l) type mode)])
        (let loop ([l l] [i 0])
          (unless (null? l)
            (ptr-set! cblock type i (car l))
            (loop (cdr l) (add1 i))))
        cblock)))

(define-syntax define*
  (syntax-rules ()
    [(_ (name . args) body ...)
     (begin (provide name) (define (name . args) body ...))]
    [(_ name expr)
     (begin (provide name) (define name expr))]))

(define* pl-setup-page
  (get-ffi-obj "c_plspage" libplplot
               (_fun (xp : _plflt = 0.0)
                     (yp : _plflt = 0.0)
                     (xleng : _plint)
                     (yleng : _plint)
                     (xoff : _plint = 0)
                     (yoff : _plint = 0)
                     -> _void)))

(define* pl-set-device
  (get-ffi-obj "c_plsdev" libplplot (_fun _string -> _void)))

(define* pl-set-output-file
  (get-ffi-obj "c_plsfnam" libplplot (_fun _string -> _void)))

(define* pl-init-plot
  (get-ffi-obj "c_plinit" libplplot (_fun -> _dc_Dev-pointer)))

(define* pl-finish-plot
  (get-ffi-obj "c_plend" libplplot (_fun -> _void)))

(define* pl-set-plot-environment
  (get-ffi-obj "c_plenv" libplplot
    (_fun _plflt _plflt _plflt _plflt _plint _plint -> _void)))

(define* pl-set-labels
  (get-ffi-obj "c_pllab" libplplot
    (_fun _string _string _string -> _void)))

(define* pl-plot-line
  (get-ffi-obj "c_plline" libplplot
    (_fun _plint (x : (_list/still i _plflt)) (y : (_list/still i _plflt)) -> _void)))

(define* pl-plot-segment
  (get-ffi-obj "c_pljoin" libplplot
    (_fun _plflt _plflt _plflt _plflt -> _void)))

(define* pl-set-background-color
  (get-ffi-obj "c_plscolbg" libplplot
    (_fun _plint _plint _plint -> _void)))


(define* pl-select-colormap0-index
  (get-ffi-obj "c_plcol0" libplplot
    (_fun _plint -> _void)))

(define* pl-set-colormap0-index
  (get-ffi-obj "c_plscol0" libplplot
    (_fun _plint _plint _plint _plint -> _void)))

(define* pl-set-line-width
  (get-ffi-obj "c_plwid" libplplot
    (_fun _plint -> _void)))

(define* pl-write-text
  (get-ffi-obj "c_plptex" libplplot
               (_fun _plflt _plflt _plflt _plflt _plflt _string -> _void)))

;;(define* pl-2d-countour-plot ...)
;;(define* pl-2d-shade-plot ...)

(define* pl-x-error-bars
  (get-ffi-obj "c_plerrx" libplplot
    (_fun _plint (_list/still i _plflt)
          (_list/still i _plflt)
          (_list/still i _plflt) -> _void)))

(define* pl-y-error-bars
  (get-ffi-obj "c_plerry" libplplot
    (_fun _plint (_list/still i _plflt)
          (_list/still i _plflt)
          (_list/still i _plflt) -> _void)))

(define* pl-plot-points
  (get-ffi-obj "c_plpoin" libplplot
    (_fun _plint (x : (_list/still i _plflt)) (y : (_list/still i _plflt)) _plint
          -> _void)))

(define* pl-fill
  (get-ffi-obj "c_plfill" libplplot
   (_fun  (n         : _plint = (length x-values))
          (x-values  : (_list/still i _plflt))
          (y-values  : (_list/still i _plflt))
          -> _void)))

(define* pl-world-3d
  (get-ffi-obj "c_plw3d" libplplot
    (_fun
     _plflt _plflt _plflt _plflt _plflt _plflt _plflt _plflt _plflt _plflt _plflt
     ->
     _void)))

;; bit-masks for some of the functions..
(define-values (DRAW_LINEX DRAW_LINEY MAG_COLOR BASE_CONT TOP_CONT SURF_CONT DRAW_SIDES DRAW_FACETED MESH)
  (apply values (build-list 9 (lambda (s) (arithmetic-shift 1 s)))))

(define DRAW_LINEXY (bitwise-ior DRAW_LINEX DRAW_LINEY))


(define* pl-plot3d
  (get-ffi-obj "c_plot3d" libplplot
   (_fun
    (x-values  : (_list/still i _plflt))
    (y-values  : (_list/still i _plflt))
    (z-values  : (_matrix-of _plflt))
    (nx        : _int = (length x-values))
    (ny        : _int = (length y-values))
    (draw-opt1 : _int = DRAW_LINEXY)
    (draw-opt2 : _int = 0)
    -> _void))) ;; these are documented in the plplot ref manual, and will be obseleted.

(define* pl-mesh3d
  (get-ffi-obj "c_plot3d" libplplot
   (_fun
    (x-values  : (_list/still i _plflt))
    (y-values  : (_list/still i _plflt))
    (z-values  : (_matrix-of _plflt))
    (nx        : _int = (length x-values))
    (ny        : _int = (length y-values))
    (draw-opt1 : _int = DRAW_LINEXY)
    -> _void)))

; ;; this function needs to go.
; (define* pl-plot-points
;   (get-ffi-obj "c_plpoin" libplplot
;    (_fun
;     (nx        : _int = (length x-values))
;     (x-values  : (_list/still i _plflt))
;     (y-values  : (_list/still i _plflt))
;     (code      : _int))))

(define* pl-box3
  (get-ffi-obj "c_plbox3" libplplot
   (_fun
    (x-ops     : _string) (x-title   : _string) (x-spacing : _plflt) (x-ticks   : _int)
    (y-ops     : _string) (y-title   : _string) (y-spacing : _plflt) (y-ticks   : _int)
    (z-ops     : _string) (z-title   : _string) (z-spacing : _plflt) (z-ticks   : _int)
    -> _void)))

(define* pl-line3
  (get-ffi-obj "c_plline3" libplplot
   (_fun
    (n-points : _int = (length x-values))
    (x-values  : (_list/still i _plflt))
    (y-values  : (_list/still i _plflt))
    (z-values  : (_list/still i _plflt))
    -> _void)))


(define* pl-poly3
  (get-ffi-obj "c_plpoly3" libplplot
   (_fun
    (n-points : _int = (length x-values))
    (x-values  : (_list/still i _plflt))
    (y-values  : (_list/still i _plflt))
    (z-values  : (_list/still i _plflt))
    (draw-mask : (_list/still i _int))
    (direction : _int)
    -> _void)))

;; need the CStruct PLcGrid ;
;;    PLFLT *xg, *yg, *zg;
;;    PLINT nx, ny, nz;
(define-cstruct _PLcGrid ((xg _pointer)
                          (yg _pointer)
                          (zg _pointer)
                          (nx _int)
                          (ny _int)
                          (nz _int)))

(define pl-2d-contour-plot-int
  (get-ffi-obj "c_plcont" libplplot
   (_fun
    (matrix     : (_matrix-of _plflt))
    (nx         : _int = (PLcGrid-nx grid))
    (ny         : _int = (PLcGrid-ny grid))
    (t1         : _plint = 1)
    (t2         : _int = (PLcGrid-nx grid))
    (t3         : _plint = 1)
    (t4         : _int = (PLcGrid-ny grid))
    (levels     : (_list/still i _plflt))
    (nlevels    : _int = (length levels))
    (pltr       : _fpointer = (get-ffi-obj "pltr1" libplplot _fpointer))
    (grid       : _PLcGrid-pointer)
    -> _void)))

(define* (pl-2d-contour-plot z-vals x-vals y-vals levels)
  (let ((grid-obj (make-PLcGrid (list->cblock x-vals _plflt) (list->cblock y-vals _plflt) #f
                                (length x-vals) (length y-vals) 0)))
    (pl-2d-contour-plot-int z-vals levels grid-obj)))



(define pl-2d-shade-plot-int
  (get-ffi-obj "c_plshades" libplplot
   (_fun
    (matrix     : (_matrix-of _plflt))
    (nx         : _int = (PLcGrid-nx grid))
    (ny         : _int = (PLcGrid-ny grid))
    (null-val   : _pointer = #f)
    (x_min      : _plflt = 0)
    (x_max      : _plflt = 0)
    (y_min      : _plflt = 0)
    (y_max      : _plflt = 0)
    (levels     : (_list/still i _plflt))
    (nlevels    : _int = (length levels))
    (fill_width : _int = 1)
    (cont_col   : _int = 1)
    (cont_width : _int = 0)
    (fill_fun   : _fpointer = (get-ffi-obj "c_plfill" libplplot _fpointer))
    (rectan     : _int = 1)
    (pltr       : _fpointer = (get-ffi-obj "pltr1" libplplot _fpointer))
    (grid       : _PLcGrid-pointer)
    -> _void)))

(define* (pl-2d-shade-plot z-vals x-vals y-vals levels)
  ;; this can prolly be inlined above..
  (let ((grid-obj (make-PLcGrid (list->cblock x-vals _plflt) (list->cblock y-vals _plflt) #f
                                 (length x-vals) (length y-vals) 0)))
    (pl-2d-shade-plot-int z-vals levels grid-obj)))


;; set up color map numbers
(define plscmap1n
  (get-ffi-obj "c_plscmap1n" libplplot
    (_fun _int -> _void)))

;; set up the map
(define plscmap1l
  (get-ffi-obj "c_plscmap1l" libplplot
    (_fun
     (itype     : _plint)
     (npts      : _int = (length intencity))
     (intencity : (_list/still i _plflt))
     (coord1    : (_list/still i _plflt))
     (coord2    : (_list/still i _plflt))
     (coord3    : (_list/still i _plflt))
     (rev       : _pointer = #f)
     -> _void)))

(define pl-mesh3dc-int 
  (get-ffi-obj "c_plmeshc" libplplot
    (_fun
     (x-values  : (_list/still i _plflt))
     (y-values  : (_list/still i _plflt))
     (z-values  : (_matrix-of _plflt))
     (x-len     : _int = (length x-values))
     (y-len     : _int = (length y-values))
     (opts      : _int)
     (levels    : (_list/still i _plflt))
     (n-levels  : _int = (length levels))
     -> _void)))

(define* (pl-mesh3dc x-vals y-vals z-vals contours? lines? colored? sides? levels)
  (let ((opts (foldl
               (lambda (mask use? current) (bitwise-ior current (if use? mask 0)))
               0
               (list DRAW_LINEXY MAG_COLOR BASE_CONT DRAW_SIDES)
               (list contours? lines? colored? sides?))))
    (plscmap1n 256)
    (plscmap1l 0 '(0.0 1.0) '(240 0) '(.6 .6) '(.8 .8))
    (pl-mesh3dc-int x-vals y-vals z-vals opts levels)))

(define (dc-draw-line dest x1 y1 x2 y2)
  (send (ptr-ref dest _racket) draw-line x1 y1 x2 y2))
(define (dc-draw-multi dest xs ys n go)
  (let ([xs (cast xs _pointer (_vector o _short n))]
        [ys (cast ys _pointer (_vector o _short n))])
    (go (ptr-ref dest _racket)
          (for/list ([x (in-vector xs)]
                     [y (in-vector ys)])
            (cons x y)))))
(define (dc-draw-lines dest xs ys n)
  (dc-draw-multi dest xs ys n
                 (lambda (dc l) (send dc draw-lines l))))
(define (dc-fill-poly dest xs ys n)
  (dc-draw-multi dest xs ys n
                 (lambda (dc l) (send dc draw-polygon l))))
(define (dc-set-width dest w)
  (send (ptr-ref dest _racket) set-width w))
(define (dc-set-color dest index)
  (send (ptr-ref dest _racket) set-index-color index))
(define (dc-set-color/rgb dest r g b)
  (send (ptr-ref dest _racket) set-rgb-color r g b))
(define (dc-start-page dest)
  (send (ptr-ref dest _racket) start-page))
(define (dc-end-page dest)
  (send (ptr-ref dest _racket) end-page))
(define (dc-end-doc dest)
  (send (ptr-ref dest _racket) end-doc)
  (free-immobile-cell dest))

(define draw_line (function-ptr dc-draw-line
                                (_fun _pointer _short _short _short _short -> _void)))
(define draw_lines (function-ptr dc-draw-lines
                                 (_fun _pointer _pointer _pointer _PLINT -> _void)))
(define fill_poly (function-ptr dc-fill-poly
                                (_fun _pointer _pointer _pointer _PLINT -> _void)))
(define set_width (function-ptr dc-set-width
                                (_fun _pointer _int -> _void)))
(define set_color (function-ptr dc-set-color
                                (_fun _pointer _short -> _void)))
(define set_color_rgb (function-ptr dc-set-color/rgb
                                    (_fun _pointer _short _short _short -> _void)))
(define start_page (function-ptr dc-start-page
                                 (_fun _pointer -> _void)))
(define end_page (function-ptr dc-end-page
                               (_fun _pointer -> _void)))
(define end_doc (function-ptr dc-end-doc
                              (_fun _pointer -> _void)))

(provide init-dev!)
(define (init-dev! dev obj)
  (set-dc_Dev-user_data! dev (malloc-immobile-cell obj))
  (set-dc_Dev-drawLine! dev draw_line)
  (set-dc_Dev-drawLines! dev draw_lines)
  (set-dc_Dev-fillPoly! dev fill_poly)
  (set-dc_Dev-setWidth! dev set_width)
  (set-dc_Dev-setColor! dev set_color)
  (set-dc_Dev-setColorRGB! dev set_color_rgb)
  (set-dc_Dev-startPage! dev start_page)
  (set-dc_Dev-endPage! dev end_page)
  (set-dc_Dev-endDoc! dev end_doc))
