;;;; examples/09-ghoulspree.lisp

(defpackage #:ww.examples/9
  (:use #:cl)
  (:export #:start)
  (:import-from #:defclass-std #:defclass/std))

(in-package #:ww.examples/9)

;;; CLASSES

(defclass/std  ghoulspree (ww::application)
  ((ghouls-per-click :std 20)
   (collision-on-p :std t)
   (gravity-on-p :std nil)))

(defclass/std ghoul (ww:image)
  ((dx dy dr :std)))

;;; UTILITY FUNCTIONS

(defun make-ghoul (x y)
  (make-instance 'ghoul :texture (ww:get-asset "Fezghoul.png")
                        :x x :y y
                        :dr (random-velocity 0.2)
                        :dx (random-velocity 4)
                        :dy (random-velocity 4)))

(defun out-of-bounds-p (ghoul)
  (not
   (and
    (< -50 (ww:x ghoul) 850)
    (< -50 (ww:y ghoul) 650))))

(defun random-velocity (&optional (size 1.0))
  (* size (if (zerop (random 2))
              (random 1.0)
              (* -1 (random 1.0)))))

(defun advance-pos (thing)
  (with-accessors ((dr dr) (dx dx) (dy dy) (x ww::x) (y ww::y) (r ww::rotation)) thing
    (incf x dx)
    (incf y dy)
    (incf r dr))) 

(defun clamp (lo val hi)
  "Returns VAL if (< LO VAL HI), otherwise returns LO or HI depending
on which boundary VAL is outside of."
  (max lo (min val hi)))

(defun sign (x)
  (if (zerop x) 0
      (/ x (abs x))))

(defmacro with-pairs ((a b) ls &rest body)
  "run body with a and b bound to unique 2-sets of LS"
  (let ((more-a (gensym)))
    `(loop for (,a . ,more-a) on ,ls do
      (loop for ,b in ,more-a do (progn ,@body)) )))

(defun handle-collision (g1 g2 &optional (friction 0.99))
  (with-slots ((dx1 dx) (dy1 dy) (dr1 dr)) g1
    (with-slots ((dx2 dx) (dy2 dy) (dr2 dr)) g2
      (let ((tdx (* friction dx1))
            (tdy (* friction dy1))
            (tdr (* friction dr1)))
        (setf dx1 (* friction dx2)
              dy1 (* friction dy2)
              dr1 (* friction dr2)
              dx2 tdx
              dy2 tdy
              dr2 tdr)))))

(defun apply-gravity-to (thing acc)
  (with-slots (dx dy) thing
    (decf dy acc)))

(ww:defhandler moveghouls
    (ww:on-perframe (app)
      ;; first handle collisions
      (when (collision-on-p app)
        (with-pairs
            (g1 g2) (ww:container-units app)
            (when (ww:units-intersect-p g1 g2)
              (handle-collision g1 g2 1.0)
              ;; need a "bounce" 
              (advance-pos g1)
              (advance-pos g1)
              (advance-pos g2)
              (advance-pos g2))))
      ;; then update positions and remove the out of bounds
      (let ((gravity
              (gravity-on-p app))
            (accelleration
              (/ 9.8 (ww:fps app)))) 
        (loop for ghoul in (ww:container-units app)
              do (advance-pos ghoul)
              when gravity
                do (apply-gravity-to ghoul accelleration)
              when (out-of-bounds-p ghoul)
                do (ww:drop-unit ghoul)))))

(defun random-sign ()
  (if (zerop (random 2)) -1 1))

(defun random-between (lo hi)
  (+ lo (random (- hi lo))))

(ww:defhandler add-ghouls
    (ww:on-mousedown (app x y)
      (loop repeat (ghouls-per-click app)
            for rx = (random 800); (+ x (* (random-sign) (random-between 30 60)))
            for ry = (random 600); (+ y (* (random-sign) (random-between 30 60)))
            do (ww:add-unit app (make-ghoul rx ry)))
      (format t "~a ghouls on screen~%"
              (length (ww:container-units app )))))


(ww:defhandler toggle-collision
    (ww:on-keydown (app scancode)
      (case scancode
        (:scancode-c 
         (format t "collision: ~a~%"
                 (setf (collision-on-p app)
                       (not (collision-on-p app)))))
        (:scancode-g
         (format t "gravity: ~a~%"
                 (setf (gravity-on-p app)
                       (not (gravity-on-p app))))))))


(defmethod ww::boot ((app ghoulspree))
  "Adds the intro text and sets up the start button handler."
  (format t "Click to add ~a ghouls to the screen.~%" (ghouls-per-click app))
  (format t "Press c to toggle collision handling.~%")
  (format t "Press g to toggle gravity.~%")

  (ww:add-handler app #'add-ghouls)
  (ww:add-handler app #'moveghouls)
  (ww:add-handler app #'toggle-collision))

(defun start (&optional (scale 1.0))
  (ww::start
   (make-instance
    'ghoulspree
    :fps 60
    :width (round (* 800 scale))
    :height (round (* 600 scale))
    :scale scale
    :refocus-on-mousedown-p nil
    :title "Click to add ghouls"
    :asset-root
    (merge-pathnames
     "examples/"
     (asdf:system-source-directory :wheelwork)))))