(cl:defpackage :ocm.nvg
  (:use :cl :cffi-c-ref :alexandria)
  (:export run))
(cl:in-package :ocm.nvg)

(defun aget (alist key)
  (cdr (assoc key alist :test #'equal)))

(defun agets (alist &rest keys)
  (reduce #'aget keys :initial-value alist))

(defun agetter (&rest keys)
  (lambda (alist)
    (apply 'agets alist keys)))

(defun tags (obj)
  (let ((tags (ocm.osm::obj-tags obj)))
    (when (functionp tags)
      (setf tags (funcall tags)
            (ocm.osm::obj-tags obj) tags))
    tags))

(defun merge-tags (obj parent)
  (append obj parent))


(defvar *context* nil "NanoVG Context")
(defvar *window-width* 640.0)
(defvar *window-height* 480.0)
(defvar *frame-width* 640.0)
(defvar *frame-height* 480.0)
(defvar *pixel-ratio* 1.0)
(defvar *scale* 3f0)
(defvar *pos-x* 0f0)
(defvar *pos-y* 0f0)

(defparameter +granularity+ (expt 10 8))
(defun node-x (node)
  (float (/ (ocm.osm::node-lat node) +granularity+)))
(defun node-y (node)
  (float (/ (ocm.osm::node-lon node) +granularity+)))

(defun render-path (nodes)
  (%nvg:begin-path *context*)
  (let ((start (car nodes)))
    (%nvg:move-to *context* (node-x start) (node-y start))
    (loop for node in (cdr nodes)
       do (%nvg:line-to *context* (node-x node) (node-y node)))))

(defparameter +highway-color+ '(250 0 0 255))
(defparameter +strokes+
  `(
    (((:way "highway")) :color ,+highway-color+ :width 10)))

(defun apply-stroke (tags)
  (when-let (options (find-match +strokes+ tags :way))
    (destructuring-bind (&key color width) options
      (when color
        (c-with ((n-color %nvg:color))
          (%nvg:stroke-color *context* (apply '%nvg:rgba (n-color &) color))))
      (when width (%nvg:stroke-width *context* width)))))


(defun render-line (nodes tags)
  (render-path nodes)
  (apply-stroke tags)
  (%nvg:stroke *context*))

(defparameter +water-color+ '(0 0 250 255))
(defparameter +wood-color+ '(0 250 0 255))
(defparameter +fills+
  `(
    (((nil "natural" "water")) :color ,+water-color+)
    (((nil "natural" "scrub")
      (nil "natural" "wood")) :color ,+wood-color+)
    ))

(defun find-match (query-value tags type)
  (loop for (query . value) in query-value
     when (ocm.osm::query-match2 query type tags)
     do (return value)))

(defun apply-fill (tags)
  (when-let (fill (find-match +fills+ tags :way))
    (destructuring-bind (&key color paint) fill
      (when color
        (c-with ((n-color %nvg:color))
          (%nvg:fill-color *context* (apply '%nvg:rgba (n-color &) color))))
      (when paint))))


(defun render-poly (nodes tags &optional hole)
  (render-path nodes)
  (%nvg:close-path *context*)
  (when hole
    (%nvg:path-winding *context* (cffi:foreign-bitfield-value '%nvg:solidity :hole)))
  (apply-fill tags)
  (%nvg:fill *context*))

(defparameter +closed-lines+
  '((:way "highway")))

(defun is-area (nodes tags)
  (let ((first (first nodes))
        (last (car (last nodes))))
    (and first last
         (= (if (ocm.osm::obj-p first) (ocm.osm::obj-id first) first)
            (if (ocm.osm::obj-p last) (ocm.osm::obj-id last) last))
         (not (ocm.osm::query-match2 +closed-lines+ :way tags)))))

(defun render-way (nodes tags)
  (if (is-area nodes tags)
      (render-poly nodes tags)
      (render-path nodes)))

(defun render-multipoly (rel)
  (loop for mem in (ocm.osm::rel-memids rel)
     for mem-role in (ocm.osm::rel-roles rel)
     for mem-type in (ocm.osm::rel-types rel)
     when (ocm.osm::way-p mem) ;; skip non-ways
     do (render-poly (ocm.osm::way-refs mem)
                     (merge-tags (tags mem) (tags rel))
                     (equal "inner" mem-role))))

(defun render-objects (objects)
  (loop for obj in objects
     do (typecase obj
          (ocm.osm::way
           (render-way (ocm.osm::way-refs obj) (tags obj)))
          (ocm.osm::rel
           (when (equal (aget (tags obj) "type")
                        "multipolygon")
             (render-multipoly obj))))))

(defun render ()
  (%nvg:begin-frame *context* *window-width* *window-height* *pixel-ratio*)

  (%nvg:transform *context* *scale* 0f0 0f0 *scale* *pos-x* *pos-y*)

  (render-objects ocm.osm::+water+)

  (%nvg:begin-path *context*)
  (%nvg:rect *context* 0f0 0f0 120f0 30f0)
  (c-with ((color %nvg:color))
    (%nvg:fill-color *context* (%nvg:rgba (color &)
                                          (round (* 127.5 (+ 1 (sin (* 2 (%glfw:get-time))))))
                                          192 192 255)))
  (%nvg:fill *context*)

  (%nvg:end-frame *context*)
  )

(defparameter +translate-step+ 10f0)
(defparameter +scale-step+ 0.1)
(glfw:define-key-callback on-keys (window key scancode action mod-keys)
  (declare (ignorable window key scancode action mod-keys))
  (format t "KEY: ~{~A ~}~%" (list key scancode action mod-keys))
  (when (eql action %glfw:+press+)
    (cond
      ((or
        (eql key %glfw:+key-escape+)
        (eql key %glfw:+key-q+))
       (%glfw:set-window-should-close window %glfw:+true+))
      ((eql key %glfw:+key-left+) (decf *pos-x* +translate-step+))
      ((eql key %glfw:+key-right+) (incf *pos-x* +translate-step+))
      ((eql key %glfw:+key-up+) (decf *pos-y* +translate-step+))
      ((eql key %glfw:+key-down+) (incf *pos-y* +translate-step+))
      ((eql key %glfw:+key-minus+) (decf *scale* +scale-step+))
      ((eql key %glfw:+key-equal+) (incf *scale* +scale-step+))))
  (print (list *scale* *pos-x* *pos-y*)))

(glfw:define-framebuffer-size-callback on-fb-size (window width height)
  (declare (ignorable window))
  (setf *frame-width* (float width)
        *frame-height* (float height)
        *pixel-ratio* (/ *frame-width* *window-width*)))

(glfw:define-window-size-callback on-win-size (window width height)
  (declare (ignorable window))
  (setf *window-width* (float width)
        *window-height* (float height)
        *pixel-ratio* (/ *frame-width* *window-width*)))

(Defun create-window ()
  (if (uiop:featurep :bodge-gl2)
      (glfw:with-window-hints ((%glfw:+context-version-major+ 2)
                               (%glfw:+context-version-minor+ 1)
                               (%glfw:+depth-bits+ 24)
                               (%glfw:+stencil-bits+ 8))
        (%glfw:create-window 640 480 "Hello NanoVG 2" nil nil))
      (glfw:with-window-hints ((%glfw:+context-version-major+ 3)
                               (%glfw:+context-version-minor+ 3)
                               (%glfw:+opengl-profile+ %glfw:+opengl-core-profile+)
                               (%glfw:+opengl-forward-compat+ %glfw:+true+)
                               (%glfw:+depth-bits+ 24)
                               (%glfw:+stencil-bits+ 8))
        (%glfw:create-window 640 480 "Hello NanoVG 3" nil nil))))

(defun main ()
  ;; Initializing window and OpenGL context
  (glfw:with-init ()
    (c-let ((window %glfw:window :from (create-window))
            (width :int :alloc t)
            (height :int :alloc t))
      (when (cffi:null-pointer-p (window &))
        (%glfw:terminate)
        (error "Failed to create GLFW window"))
      (%glfw:make-context-current (window &))
      ;; Mangling GL function pointers
      (glad:init)
      ;; Set event callbacks
      (%glfw:set-key-callback (window &) (cffi:callback on-keys))
      (%glfw:set-framebuffer-size-callback (window &) (cffi:callback on-fb-size))
      (%glfw:set-window-size-callback (window &) (cffi:callback on-win-size))
      ;; Get initial values
      (%glfw:get-framebuffer-size (window &) (width &) (height &))
      (setf *frame-width* (float width)
            *frame-height* (float height))
      (%glfw:get-window-size (window &) (width &) (height &))
      (setf *window-width* (float width)
            *window-height* (float height)
            *pixel-ratio* (/ *frame-width* *window-width*))

      ;; reset
      (setf *scale* 1f0
            *pos-x* 0f0
            *pos-y* 0f0)

      ;; Creating NanoVG context
      (let ((*context* (nvg:make-context)))
        (unwind-protect
             (loop while (= (%glfw:window-should-close (window &)) 0) do
                  (gl:clear-color 1f0 1f0 1f0 1f0)
                  (gl:clear :color-buffer-bit :depth-buffer-bit :stencil-buffer-bit)

                ;; Actually drawing into nanovg context and onto display
                  (render)

                  (%glfw:swap-buffers (window &))
                  (%glfw:poll-events))
          ;; Cleaning up NanoVG context
          (nvg:destroy-context *context*))))))

(defun run ()
  (main)
  ;; (trivial-main-thread:with-body-in-main-thread (:blocking t)
  ;;   (float-features:with-float-traps-masked t
  ;;     (main)))
  )
