(local state (require :state))
(local mapper (require :mapper))
(local overlay (require :overlay))
(local pi math.pi)
(var count-down true)
; ### Screen Size ###
(var (screen-width screen-height) (love.window.getMode))
(love.mouse.setGrabbed true)
(love.mouse.setRelativeMode true)

; ### Map Information ###
(var (map spawn) (mapper.generate))

; ### Texture Information ###
(var walls [])
(var wall-textures (love.filesystem.getDirectoryItems "assets/textures/walls"))
(var (tex-height tex-width) (values 64 64))

(var skybox [])
(var skybox-textures (love.filesystem.getDirectoryItems "assets/textures/skybox"))

(each [_ v (ipairs wall-textures)]
  (local wall {})
  (tset wall :t (love.graphics.newImage (.. "assets/textures/walls/" v)))
  (tset wall :w (love.graphics.getWidth (. wall :t)))
  (tset wall :h (love.graphics.getHeight (. wall :t)))
  (table.insert walls wall))

(each [_ v (ipairs skybox-textures)]
  (local skyb {})
  (tset skyb :t (love.graphics.newImage (.. "assets/textures/skybox/" v)))
  (tset skyb :w (love.graphics.getWidth (. skyb :t)))
  (tset skyb :h (love.graphics.getHeight (. skyb :t)))
  (table.insert skybox skyb))

; ### "Player" variables ###
(var (posx posy) (values (+ (. spawn :x) 0.5) (+ (. spawn :y) 0.5)))
(var (dirx diry) (values -1.0 0.0))     ; Initial direction vector
(var (planex planey) (values 0 0.66))   ; Camera plane

; ### Offset experimentation ###
(var (offst-x offst-y) (values 0 0))

{
  :draw (fn love.draw []
    (state.setDirX dirx)
    (state.setDirY diry)
    ; Mouse-Look
    (love.graphics.translate offst-x offst-y)

    ; "Skybox"
    ; Draw a big thing before everything else
    ; (love.graphics.draw (. skybox 1 :t) -20 -20)

    ; WALL CASTING
    (for [i 0 screen-width]
      ; Calculate ray position and direction
      ; Originals, giving a fish-eye lens effect:
      ; (var camerax (/ (* 2 i) (- screen-width 1)))
      ; (var (ray-dir-x ray-dir-y) (values (+ dirx (* planex camerax)) (+ diry (* planey camerax))))
      (var camerax (- (/ (* 2 i) (- screen-width 1)) 1.0))
      (var (ray-dir-x ray-dir-y) (values (+ dirx (* planex camerax)) (+ diry (* planey camerax))))

      ; Which map square we're in
      (var (mapx mapy) (values (math.floor posx) (math.floor posy)))

      ; Length of ray from current position to next x or y side
      (var (side-dist-x side-dist-y) (values 0 0))

      ; Length of ray from one x or y side to the next x or y side
      ; (var (delta-dist-x delta-dist-y) (values 
      ;                                      (math.sqrt (+ 1 (/ (* ray-dir-y ray-dir-y) (* ray-dir-x ray-dir-x))))
      ;                                      (math.sqrt (+ 1 (/ (* ray-dir-x ray-dir-x) (* ray-dir-y ray-dir-y))))))
      ; (var (delta-dist-x delta-dist-y) (values 
      ;                                      (math.sqrt (+ 1 (* (/ ray-dir-y ray-dir-x) (/ ray-dir-y ray-dir-x))))
      ;                                      (math.sqrt (+ 1 (* (/ ray-dir-x ray-dir-y) (/ ray-dir-x ray-dir-y))))))
      (var (delta-dist-x delta-dist-y) (values (math.abs (/ 1 ray-dir-x)) (math.abs (/ 1 ray-dir-y))))

      (var perp-wall-dist 0)

      ; Direction to step in, x or y
      (var (stepx stepy) (values 0 0))

      ; Side the ray hits, x (0) or y (1)
      (var side 0)

      ; Calculate step and initial side-dist-*
      (if (< ray-dir-x 0)
        (do
          (set stepx -1)
          (set side-dist-x (* (- posx mapx) delta-dist-x)))
        (do
          (set stepx 1)
          (set side-dist-x (* (- (+ mapx 1.0) posx) delta-dist-x))))
      (if (< ray-dir-y 0)
        (do
          (set stepy -1)
          (set side-dist-y (* (- posy mapy) delta-dist-y)))
        (do
          (set stepy 1)
          (set side-dist-y (* (- (+ mapy 1.0) posy) delta-dist-y))))

      ; Perform DDA (Digital Differential Analysis)
      (var hit false)
      (while (not hit)
        (if (< side-dist-x side-dist-y)
          (do
            (set side-dist-x (+ side-dist-x delta-dist-x))
            (set mapx (+ mapx stepx))
            (set side 0))
          (do
            (set side-dist-y (+ side-dist-y delta-dist-y))
            (set mapy (+ mapy stepy))
            (set side 1)))
        (if (> (. map mapx mapy) 0) (set hit true)))

      ; Calculate distance of perpendicular ray, to avoid fisheye effect
      (if (= side 0)
        (set perp-wall-dist (- side-dist-x delta-dist-x))
        (set perp-wall-dist (- side-dist-y delta-dist-y)))

      ; Calculate height of line to draw on screen
      (var line-height (/ screen-height perp-wall-dist))

      ; Calculate lowest and highest pixel to fill in current stripe
      (var draw-start (+ (/ (- line-height) 2) (/ screen-height 2)))
      (if (< draw-start 0) (set draw-start 0))
      (var draw-end (+ (/ line-height 2) (/ screen-height 2)))
      (if (>= draw-end screen-height) (set draw-end (- screen-height 1)))

      ; Draw textured wall
      ;; Choose texture
      ; (var tex-num (. walls (. map mapx mapy)))
      (var tex-num (. walls (math.floor (/ (. map mapx mapy) 10))))
      ;; Calculate exactly where the wall was hit
      (var wallx 0)
      (if (= side 0)
        (set wallx (+ posy (* perp-wall-dist ray-dir-y)))
        (set wallx (+ posx (* perp-wall-dist ray-dir-x))))
      (set wallx (- wallx (math.floor wallx)))
      ;; Find the x-coordinate on the texture
      (var tex-x (math.floor (* wallx (. tex-num :w))))
      (if (and (= side 0) (> ray-dir-x 0)) (set tex-x (- (. tex-num :w) tex-x 1)))
      (if (and (= side 1) (< ray-dir-y 0)) (set tex-x (- (. tex-num :w) tex-x 1)))
      ;; Draw the texture, accounting for "fog"
      (var fog-dist (- 1 (/ perp-wall-dist 10)))
      (var light-dist (- 0.8 (/ perp-wall-dist 5)))
      (love.graphics.setColor light-dist light-dist light-dist fog-dist)
      ; (love.graphics.draw (. tex-num :t)
      ;                     (love.graphics.newQuad tex-x 0 1 (. tex-num :h) (. tex-num :w) (. tex-num :h))
      ;                     i draw-start 0 1 (/ line-height (. tex-num :h)))
      ; (when (= (. map mapx mapy) 3)
        (for [q 0 (% (. map mapx mapy) 10)]
          (set fog-dist (- fog-dist 0.1))
          (set light-dist (- light-dist 0.1))
          (love.graphics.setColor light-dist light-dist light-dist fog-dist)
          (love.graphics.draw (. tex-num :t)
                              (love.graphics.newQuad tex-x 0 1 (. tex-num :h) (. tex-num :w) (. tex-num :h))
                              i (- draw-start (* line-height q)) 0 1 (/ line-height (. tex-num :h)))
        )
      ; )
    )
    (overlay.overlay offst-x offst-y)
  )

  :update (fn update [dt]
    (var mvspeed (* dt 3.0))
    (var rtspeed (* dt 1.0))

    (when (love.keyboard.isDown "w")
      (when (= 0 (. map (math.floor (+ (* mvspeed dirx) posx)) (math.floor posy)))
        (set posx (+ (* mvspeed dirx) posx))
        (state.modDis mvspeed))
      (when (= 0 (. map (math.floor posx) (math.floor (+ (* mvspeed diry) posy))))
        (set posy (+ (* mvspeed diry) posy))
        (state.modDis mvspeed))
    )

    (when (love.keyboard.isDown "s")
      (when (= 0 (. map (math.floor (- posx (* mvspeed dirx))) (math.floor posy)))
        (set posx (- posx (* mvspeed dirx)))
        (state.modDis mvspeed))
      (when (= 0 (. map (math.floor posx) (math.floor (- posy (* mvspeed diry)))))
        (set posy (- posy (* mvspeed diry)))
        (state.modDis mvspeed))
    )

    (when (love.keyboard.isDown "d")
      (when (= 0 (. map (math.floor (+ (* mvspeed planex) posx)) (math.floor posy)))
        (set posx (+ (* mvspeed planex) posx))
        (state.modDis mvspeed))
      (when (= 0 (. map (math.floor posx) (math.floor (+ (* mvspeed planey) posy))))
        (set posy (+ (* mvspeed planey) posy))
        (state.modDis mvspeed))
    )

    (when (love.keyboard.isDown "a")
      (when (= 0 (. map (math.floor (- posx (* mvspeed planex))) (math.floor posy)))
        (set posx (- posx (* mvspeed planex)))
        (state.modDis mvspeed))
      (when (= 0 (. map (math.floor posx) (math.floor (- posy (* mvspeed planey)))))
        (set posy (- posy (* mvspeed planey)))
        (state.modDis mvspeed))
    )

    (when (love.keyboard.isDown "e")
      (var old-dirx dirx)
      (set dirx (- (* dirx (math.cos (- rtspeed))) (* diry (math.sin (- rtspeed)))))
      (set diry (+ (* old-dirx (math.sin (- rtspeed))) (* diry (math.cos (- rtspeed)))))
      (var old-planex planex)
      (set planex (- (* planex (math.cos (- rtspeed))) (* planey (math.sin (- rtspeed)))))
      (set planey (+ (* old-planex (math.sin (- rtspeed))) (* planey (math.cos (- rtspeed)))))
    )

    (when (love.keyboard.isDown "q")
      (var old-dirx dirx)
      (set dirx (- (* dirx (math.cos rtspeed)) (* diry (math.sin rtspeed))))
      (set diry (+ (* old-dirx (math.sin rtspeed)) (* diry (math.cos rtspeed))))
      (var old-planex planex)
      (set planex (- (* planex (math.cos rtspeed)) (* planey (math.sin rtspeed))))
      (set planey (+ (* old-planex (math.sin rtspeed)) (* planey (math.cos rtspeed))))
    )

    (if (= 0 (% (+ 1 (math.floor (state.getDis))) 10))
      (when count-down
        (set count-down false)
        (state.modO -1))
    (set count-down true))
  )

  :mousemoved (fn mousemoved [x y dx dy]
    (var rtspeed (* dx -0.001))
    (var yawspeed (* dy -0.5))

    (var old-dirx dirx)
    (set dirx (- (* dirx (math.cos rtspeed)) (* diry (math.sin rtspeed))))
    (set diry (+ (* old-dirx (math.sin rtspeed)) (* diry (math.cos rtspeed))))
    (var old-planex planex)
    (set planex (- (* planex (math.cos rtspeed)) (* planey (math.sin rtspeed))))
    (set planey (+ (* old-planex (math.sin rtspeed)) (* planey (math.cos rtspeed))))

    (set offst-y (+ offst-y yawspeed))
  )

  :keypressed (fn keypressed [k]
    (when (= k "x") (love.event.quit))                
  )
}
