;; -*- mode: scheme; -*-
;; Planet Fluxus Copyright (C) 2013 Dave Griffiths
;;
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU Affero General Public License for more details.
;;
;; You should have received a copy of the GNU Affero General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;; pdata objects store the data so they can be manipulated, then
;; re-uploaded using the GL VBO handle.
;; todo:
;; - choice of separate or interleaved (faster when modifying one type)
;; - ability to delete CPU copies

(define (make-pdata names size-verts interleaved-data)
  (list names size-verts interleaved-data 0))

(define (pdata-names v) (list-ref v 0))
(define (pdata-modify-names! p v) (set! p (list-replace p 0 v)))
(define (pdata-size-verts v) (list-ref v 1))
(define (pdata-interleaved-data v) (list-ref v 2))
(define (pdata-modify-interleaved-data! p v) (set! p (list-replace p 2 v)))
(define (pdata-vbo v) (list-ref v 3))

(define (pdata-build-vbo gl v)
  ;;(msg (+ "building vbo of size " (length (pdata-interleaved-data v)) " floats"))
  (let ((vb (gl.createBuffer)))
    (gl.bindBuffer gl.ARRAY_BUFFER vb)
    (gl.bufferData gl.ARRAY_BUFFER (new Float32Array (length (pdata-interleaved-data v))) gl.STATIC_DRAW)
    (list-replace v 3 vb)))

(define (pdata-update-vbo! gl v)
  (gl.bindBuffer gl.ARRAY_BUFFER (pdata-vbo v))
  (gl.bufferData gl.ARRAY_BUFFER (new Float32Array (pdata-interleaved-data v)) gl.STATIC_DRAW))

(define (pdata-internal-add! gl v name)
  (let ((oldcount (length (pdata-names v))))
    (pdata-modify-names! v (append (pdata-names v) name))
    (pdata-modify-interleaved-data! 
     v (pdata-insert-util (pdata-interleaved-data v) 
			  oldcount (vector 0 0 0))))
  (pdata-build-vbo gl v)
  (pdata-update-vbo! gl v))

;;;;;;;;;;;;;;;;;;;;;;;;;;;


;; take a list of lists of vertex data, and output a 
;; single interleaved list
(define (pdata-interleave-pdata-util data)
  (js "var ret=[];")
  (js "for (var i=0; i<data[0].length; i+=3) {") 
  (js "  for (var d=0; d<data.length; d++) {")
  (js "    ret.push(data[d][i]);")
  (js "    ret.push(data[d][i+1]);")
  (js "    ret.push(data[d][i+2]);")
  (js "  }")
  (js "}")
  ret)

;; adds a new list to the interleaved data
(define (pdata-insert-util data oldcount default-vector)
  (js "var ret=[];")
  (js "var count=0;")
  (js "for (var i=0; i<data.length; i+=3) {") 
  (js "  ret.push(data[i]);")
  (js "  ret.push(data[i+1]);")
  (js "  ret.push(data[i+2]);")
  (js "  if (count===oldcount-1) {")
  (js "    ret.push(default_vector[0]);")
  (js "    ret.push(default_vector[1]);")
  (js "    ret.push(default_vector[2]);")
  (js "    count=-1;")
  (js "  }")
  (js " count++;")
  (js "}")
  ret)

(define (stride-map fn interleaved stride offset)
  (js "for (var stride_map_i=offset; stride_map_i<interleaved.length; stride_map_i+=stride) {")
  (js "var stride_map_v=fn([interleaved[stride_map_i],interleaved[stride_map_i+1],interleaved[stride_map_i+2]]);")
  (js "interleaved[stride_map_i]=stride_map_v[0];")
  (js "interleaved[stride_map_i+1]=stride_map_v[1];")
  (js "interleaved[stride_map_i+2]=stride_map_v[2];")
  (js "};")
  interleaved)

(define (stride-index-map fn interleaved stride offset)
  (js "var index_map_pos=0;")
  (js "for (var stride_map_i=offset; stride_map_i<interleaved.length; stride_map_i+=stride) {")
  (js "var stride_map_v=fn(index_map_pos,[interleaved[stride_map_i],interleaved[stride_map_i+1],interleaved[stride_map_i+2]]);")
  (js "index_map_pos+=1;")
  (js "interleaved[stride_map_i]=stride_map_v[0];")
  (js "interleaved[stride_map_i+1]=stride_map_v[1];")
  (js "interleaved[stride_map_i+2]=stride_map_v[2];")
  (js "};")
  interleaved)

(define (stride-fold fn v interleaved stride offset)
  (js "for (var stride_fold_i=offset; stride_fold_i<interleaved.length; stride_fold_i+=stride) {")
  (js "v=fn([interleaved[stride_fold_i],interleaved[stride_fold_i+1],interleaved[stride_fold_i+2]],v);")
  (js "};")
  v)

(define (pdata-offset v name)
  (define (_ names r)
    (cond
     ((null? names) #f)
     ((eq? name (car names)) r)
     (else (_ (cdr names) (+ r 3)))))
  (_ (pdata-names v) 0))

;; old fast hardcoded version - perhaps for default types if slow
;  (cond ((eq? name "p") 0)
;	((eq? name "n") 3)
;	((eq? name "t") 6)
;	(else 9)))

(define (pdata-internal-map! gl v fn name)
  (let ((offset (pdata-offset v name)))  
    (let ((stride (* (length (pdata-names v)) 3))) 
      (pdata-modify-interleaved-data!
       v (stride-map fn (pdata-interleaved-data v) stride offset))
      ;; might want to do this separately as it's potentially slow
      (pdata-update-vbo! gl v))))

(define (pdata-internal-index-map! gl v fn name)
  (let ((offset (pdata-offset v name)))  
    (let ((stride (* (length (pdata-names v)) 3))) 
      (pdata-modify-interleaved-data!
       v (stride-index-map fn (pdata-interleaved-data v) stride offset))
      ;; might want to do this separately as it's potentially slow
      (pdata-update-vbo! gl v))))

;; calculate a value from the pdata
(define (pdata-internal-fold v fn start name)
  (let ((offset (pdata-offset v name)))  
    (let ((stride (* (length (pdata-names v)) 3))) 
	  (stride-fold fn start (pdata-interleaved-data v) stride offset))))

(define (pdata-internal-set! gl pdata name pos value)
  (let ((offset (pdata-offset pdata name)))  
    (let ((stride (* (length (pdata-names pdata)) 3)))
      ;; (when (> (+ offset (* pos stride)) 
      ;; 	       (length (pdata-interleaved-data pdata))) 
      ;; 	    (msg (+ offset (* pos stride)))
      ;; 	    (msg (length (pdata-interleaved-data pdata)))
      ;; 	    (msg "WOT")
      ;; 	    (welw)
      ;; 	    )
      (js "pdata_interleaved_data(pdata)[offset+pos*stride]=value[0];")
      (js "pdata_interleaved_data(pdata)[offset+pos*stride+1]=value[1];")
      (js "pdata_interleaved_data(pdata)[offset+pos*stride+2]=value[2];"))))

(define (pdata-internal-ref gl pdata name pos)
  (let ((offset (pdata-offset pdata name)))  
    (let ((stride (* (length (pdata-names pdata)) 3))) 
      (vector
       (js "pdata_interleaved_data(pdata)[offset+pos*stride]")
       (js "pdata_interleaved_data(pdata)[offset+pos*stride+1]")
       (js "pdata_interleaved_data(pdata)[offset+pos*stride+2]")))))

(define (pdata->boundingbox pdata tx)
  (pdata-internal-fold
   pdata
   (lambda (p bb)
	 (boundingbox-expand bb (mtransform tx p)))
   (boundingbox-empty) "p"))

;; stick em out here to avoid allocation all the time
(define model-view (mat4.create))
(define local (mat4.create))
(define normal (mat4.create))

(define last-vbo #f)
(define last-texture #f)
(define last-shader #f)

(define vbo-saved 0)
(define tex-saved 0)

(define (pdata-render pdata type gl view-camera camera-pos state tx)
  (let ((shader (state-shader state))
	(texture (state-texture state))
	(colour (state-colour state))
	(hints (state-hints state)))

    (mat4.multiply tx (state-tx state) local)
    (mat4.multiply view-camera local model-view)
    (mat4.toRotationMat local normal)
	
	(when (not (eq? shader last-shader))
		  (gl.useProgram shader)
		  (set! last-shader shader))
	
    ;; set uniforms stored with this primitive
    (apply-uniform-items! gl shader (state-uniform-items state))
    	
	;; easy optimisation is to check the last vbo
	;; and if it was the same as this one (eg. when
	;; instancing) we don't need to upload it (50->60fps bower)
	;; this is only really helpful when we are rendering instances
	(if (not (eq? (pdata-vbo pdata) last-vbo))
		(begin (index-for-each
				(lambda (index name)
				  (gl.bindBuffer gl.ARRAY_BUFFER (pdata-vbo pdata))
				  (gl.enableVertexAttribArray index)
				  (gl.vertexAttribPointer 
				   index 3 gl.FLOAT false
				   ;; total bytes = 12 bytes per 3 floats
				   (* (length (pdata-names pdata)) 12)
				   (* index 12)))
				(pdata-names pdata))
			   (set! last-vbo (pdata-vbo pdata)))
		(set! vbo-saved (+ vbo-saved 1)))

	(let ((texture-found
		   (cond
			((not (eq? texture last-texture))
			 (if (eq? texture "")
				 (bind-texture gl shader "white.png")
				 (bind-texture gl shader texture)))
			(else
			 (set! last-texture texture)			
			 (set! tex-saved (+ tex-saved 1))
			 #t))))

	  ;; only render if we have a texture for this primitive
	  (when texture-found
			(gl.uniformMatrix4fv shader.ModelViewMatrix false model-view)
			(gl.uniformMatrix4fv shader.NormalMatrix false normal)
			(gl.uniformMatrix4fv shader.LocalMatrix false local)

			(gl.uniform3fv shader.DiffuseColour colour)
			(gl.uniform3fv shader.CameraPos camera-pos)

			(when (bitwise-and hints FLX_HINT_SOLID)
				  (gl.drawArrays
				   (cond
					((eq? type 'lines) gl.LINES)
					((eq? type 'line-strip) gl.LINE_STRIP)
					((eq? type 'line-loop) gl.LINE_LOOP)
					((eq? type 'triangles) gl.TRIANGLES)
					((eq? type 'triangle-strip) gl.TRIANGLE_STRIP)
					((eq? type 'triangle-fan) gl.TRIANGLE_FAN)
					(else gl.TRIANGLES))
				   0 (pdata-size-verts pdata))) 
			(when (bitwise-and hints FLX_HINT_WIRE)
				  (gl.drawArrays gl.LINE_STRIP 0 (pdata-size-verts pdata)))
			))))
