;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; GnuPlot based plotting functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defvar plot-stream nil)
(defvar plot-error nil)
(defvar plot-pid nil)

;;; Open a connection to a gnuplot process

(defun open-plot ()
  (multiple-value-bind (input error pid)
      (excl:run-shell-command "gnuplot" 
			      :wait nil
			      :input :stream
			      :error-output :output)
    (setf plot-stream input
	  plot-error error
	  plot-pid pid)))

;;; Close and terminate gnuplot

(defun close-plot ()
  (when plot-stream
    (format plot-stream "quit~%")
    (close plot-stream)
    (setf plot-stream nil)
    (multiple-value-bind (status pid)
        (sys:os-wait)
      (declare (ignore status pid)))))

;;; Send an arbitrary command to gnuplot

(defun plot-command (&optional (command ""))
  (if (not plot-stream)
      (open-plot))
  (format plot-stream command)
  (format plot-stream "~%")
  (finish-output plot-stream))

;;; Reset all 'set' options to the default values

(defun plot-reset ()
  (plot-command "reset"))

;;; Set autoscale for selected axes

(defun plot-set-autoscale ()
  (if (not plot-stream)
      (open-plot))
  (format plot-stream "set autoscale~%")
  (finish-output plot-stream))

;;; Set x range

(defun plot-set-x-range (range)
  (if (not plot-stream)
      (open-plot))
  (when range
    (format plot-stream "set xrange [~f:~f]~%"
	    (first range)(second range))
    (finish-output plot-stream)))

;;; Set y range

(defun plot-set-y-range (range)
  (if (not plot-stream)
      (open-plot))
  (when range
    (format plot-stream "set yrange [~f:~f]~%"
	    (first range)(second range))
    (finish-output plot-stream)))

;;; Set z range

(defun plot-set-z-range (range)
  (if (not plot-stream)
      (open-plot))
  (when range
    (format plot-stream "set zrange [~f:~f]~%"
	    (first range)(second range))
    (finish-output plot-stream)))

;;; Set grid

(defun plot-set-grid ()
  (if (not plot-stream)
      (open-plot))
  (format plot-stream "set grid xtics; set grid ytics; set grid ztics~%")
  (finish-output plot-stream))

;;; Set surface

(defun plot-set-surface ()
  (if (not plot-stream)
      (open-plot))
  (format plot-stream "set surface~%")
  (finish-output plot-stream))

;;; Set parametric mode

(defun plot-set-parametric ()
  (if (not plot-stream)
      (open-plot))
  (format plot-stream "set parametric~%")
  (finish-output plot-stream))

;;; Set ticslevel

(defun plot-set-ticslevel (&optional (level 0))
  (if (not plot-stream)
      (open-plot))
  (format plot-stream "set ticslevel ~s~%" level)
  (finish-output plot-stream))

;;; Set title

(defun plot-set-title (&optional 
		       (title nil))
  (if (not plot-stream)
      (open-plot))
  (when title
    (format plot-stream "set title ~s~%" title)
    (finish-output plot-stream)))

;;; Set the labels for a plot

(defun plot-set-label (&optional 
		       (label nil))
  (if (not plot-stream)
      (open-plot))
  (when label
    (format plot-stream "set label ~s~%" label)
    (finish-output plot-stream)))

;;; Set the margins of a plot

(defun plot-set-margins (&optional 
			 (margin 1))
  (if (not plot-stream)
      (open-plot))
  (when margin
    (format plot-stream "set tmargin ~s~%" margin)
    (format plot-stream "set lmargin ~s~%" margin)
    (format plot-stream "set rmargin ~s~%" margin)
    (format plot-stream "set bmargin ~s~%" margin)
    (finish-output plot-stream)))

;;; Set the borders of a plot

(defun plot-set-border (&optional 
		       (border nil))
  (if (not plot-stream)
      (open-plot))
  (when border
    (format plot-stream "set border ~s~%" border)
    (finish-output plot-stream)))

;;; Start a multiplot

(defun plot-start-multiplot ()
  (if (not plot-stream)
      (open-plot))
  (format plot-stream "set multiplot~%")
  (finish-output plot-stream))

;;; End a multiplot

(defun plot-end-multiplot ()
  (if (not plot-stream)
      (open-plot))
  (format plot-stream "set nomultiplot~%")
  (finish-output plot-stream))

;;; Set origin and size of plot area

(defun plot-size (xorigin yorigin xsize ysize)
  (if (not plot-stream)
      (open-plot))
  (format plot-stream "set origin ~s,~s~%" 
	  (coerce xorigin 'float)
	  (coerce yorigin 'float))
  (format plot-stream "set size ~s,~s~%" 
	  (coerce xsize 'float)
	  (coerce ysize 'float))
  (finish-output plot-stream))

;;; Simple data plot

(defun plot-data (data 
		  &key
		  (style "linespoints")
		  (label ""))
  (if (not plot-stream)
      (open-plot))
  (plot-set-grid)
  (format plot-stream "plot '-'")
  (if label
      (format plot-stream " title ~s" label))
  (if style
      (format plot-stream " with ~a" style))
  (format plot-stream "~%")
  (loop 
    for x from 0 
    for y in data
    do (format plot-stream "~f ~f~%" x y))
  (format plot-stream "e~%")
  (finish-output plot-stream))

;;; Plot a supplied curve

(defun plot-2d-curve (curve 
		      &key
		      (style "linespoints")
		      (label ""))
  (if (not plot-stream)
      (open-plot))
  (plot-set-grid)
  (format plot-stream "plot '-'")
  (if label
      (format plot-stream " title ~s" label))
  (if style
      (format plot-stream " with ~a" style))
  (format plot-stream "~%")
  (loop 
    for x in curve by #'cddr
    for y in (cdr curve) by #'cddr
    do (format plot-stream "~f ~f~%" x y))
  (format plot-stream "e~%")
  (finish-output plot-stream))

;; Plot a list of supplied curves

(defun plot-2d-curves (curves 
		       &key
		       (styles "linespoints")
		       (labels ""))
  (if (not plot-stream)
      (open-plot))
  (plot-set-grid)
  (if (not (listp styles))
      (setf styles (loop repeat (length curves)
		     collect styles)))
  (if (not (listp labels))
      (setf labels (loop repeat (length curves)
		     collect labels)))
  (format plot-stream "plot")
  (loop 
    for index from 0
    for style in styles
    for label in labels do
    (format plot-stream " '-' ")
    (if label
	(format plot-stream " title ~s" label))
    (if style
	(format plot-stream " with ~a" style))
    (if (/= index (- (length curves) 1))
	(format plot-stream ", ")))
  (format plot-stream "~%")
  (loop for curve in curves do		    
    (loop 
      for x in curve by #'cddr
      for y in (cdr curve) by #'cddr
      do (format plot-stream "~f ~f~%" x y))
    (format plot-stream "e~%"))
  (finish-output plot-stream))

;;; Plot a 3d curve

(defun plot-3d-curve (3d-curve 
		      &key
		      (style "linespoints")
		      (label "")
		      (zstyle "impulses")
		      (xrot)
		      (zrot)
		      (scale)
		      (zscale))
  (if (not plot-stream)
      (open-plot))
  (plot-set-border (+ 127 256 512))
  (plot-set-grid)
  (plot-set-surface)
  (plot-set-parametric)
  (plot-set-ticslevel 0)
  (if (or xrot zrot scale zscale)
      (format plot-stream "set view ~a,~a,~a,~a~%"
	      (if xrot xrot "")
	      (if zrot zrot "")
	      (if scale scale "")
	      (if zscale zscale "")))
  (format plot-stream "splot '-'")
  (if label
      (format plot-stream " title ~s" label))
  (if style
      (format plot-stream " with ~a 1" style))
  (if zstyle
      (format plot-stream ", '-' notitle with ~a 1" zstyle))
  (format plot-stream "~%")
  (loop 
    for x in 3d-curve by #'cdddr
    for y in (cdr 3d-curve) by #'cdddr
    for z in (cddr 3d-curve) by #'cdddr do 
    (format plot-stream "~f ~f ~f~%" x y z))
  (format plot-stream "e~%")
  (if zstyle (loop 
	       for x in 3d-curve by #'cdddr
	       for y in (cdr 3d-curve) by #'cdddr
	       for z in (cddr 3d-curve) by #'cdddr do 
	       (format plot-stream "~f ~f ~f~%" x y z)
	       finally (format plot-stream "e~%")))
  (finish-output plot-stream))