; Much of this is derived from https://lodev.org/cgtutor/raycasting.html
(local pi math.pi)
(love.graphics.setColor 1 1 1)
(love.graphics.setNewFont 30)

; Define map (0 is empty space, 1 is wall)
(var map [[1 1 1 1 1 ]
          [2 0 0 0 4 ]
          [2 0 0 0 4 ]
          [2 0 0 0 4 ]
          [1 3 3 3 1 ]])

; This sets the wall texture data
(var walls [])
(var wall-textures (love.filesystem.getDirectoryItems "textures/walls"))

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

; Map size
(var map-height (length map))
(var map-width (length (. map 1)))

; Player position and direction
; pos{x,y}: position vector of the player
; dir{x,y}: direction vector of the player
; pln{x,y}: camera plane of the player
(var player {:posx 3 :posy 3
             :dirx -1 :diry 0
             :plnx 0 :plny 0.66})

; Screen size
(var screen-width 1920)
(var screen-height 1080)
(var texel-width 64)
(var texel-height 64)

; Function to handle player movement
(fn move-player [move-speed]
  (let [new-x (+ player.posx (* (math.cos player.dir) move-speed))
        new-y (+ player.y (* (math.sin player.dir) move-speed))]
    ; Check for collisions with walls
    (when (= (. map (math.floor new-y) (math.floor new-x)) 0)
      (set player.x new-x)
      (set player.y new-y))))

; Function to handle player rotation
(fn rotate-player [rot]
  (local (o-dx o-px) (values player.dirx player.plnx))
  (set player.dirx (- (* player.dirx (math.cos rot)) (* player.diry (math.sin rot))))
  (set player.plnx (- (* player.plnx (math.cos rot)) (* player.plny (math.sin rot))))
  (set player.diry (+ (* o-dx (math.sin rot)) (* player.diry (math.cos rot))))
  (set player.plny (+ (* o-px (math.sin rot)) (* player.plny (math.cos rot))))
)

; Draw function for rendering
{:draw (fn love.draw []
  (love.graphics.clear)
  
  ; For each vertical slice of the screen
  (for [i 0 (- screen-width 1)]
    ; Setup a bunch of variables
    ; Camera Space x-coordinate, kind of the ray position
    (local cam-x (- (/ (* 2 i) screen-width) 1))
    ; Ray direction vector values
    (local (ray-dir-x ray-dir-y) (values (+ player.dirx (* player.plnx cam-x)) (+ player.diry (* player.plny cam-x)))) 
    ; Current player position on the map grid
    (var (map-x map-y) (values (math.floor player.posx) (math.floor player.posy)))
    ; Length of ray from first x/y to the next x/y
    (local (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))))))
    ; Side hit (n/s or e/w)
    (var side nil)
    ; Calculate step and distance from player to first x/y-side (as per Love graphics grid)
    (var (step-x side-dist-x) (if (< ray-dir-x 0) 
                                (values -1 (* (- player.posx map-x) delta-dist-x))
                                (values 1 (* (- player.posx (+ map-x 1)) delta-dist-x))))
    (var (step-y side-dist-y) (if (< ray-dir-y 0) 
                                (values -1 (* (- player.posy map-y) delta-dist-y))
                                (values 1 (* (- player.posy (+ map-y 1)) delta-dist-y))))
    ; Shoot ze ray until it hits something
    (var wall-hit false)
    (while (not wall-hit)
      (if (< side-dist-x side-dist-y)
        (set (side-dist-x map-x side) (values (+ side-dist-x delta-dist-x) (+ map-x step-x) 0))
        (set (side-dist-y map-y side) (values (+ side-dist-y delta-dist-y) (+ map-y step-y) 1)))
      (set wall-hit (if (> (. map map-x map-y) 0) true false)))
    ; Set the perpindicular length from the camera plane to the wall
    (var ray-length (if (= side 0) (- side-dist-x delta-dist-x) (- side-dist-y delta-dist-y)))
    ; Determine pixel-column height
    (var line-height (math.floor (/ screen-height ray-length)))
    (var (draw-start draw-end) (values (+ (/ (* -1 line-height) 2) (/ screen-height 2)) (+ (/ line-height 2) (/ screen-height 2))))
    (if (< draw-start 0) (set draw-start 0))
    (if (>= draw-end screen-height) (set draw-end (- screen-height 1)))
    ; Determine exactly where along the wall the ray hits
    (var wall-col (if (= side 0) (+ player.posy (* ray-length ray-dir-y)) (+ player.posx (* ray-length ray-dir-x))))
    (set wall-col (- wall-col (math.floor wall-col)))
    ; Select the texture data based on the grid number
    (local wall-texture (. walls (. map map-x map-y)))
    ; Calculate the part of the texture to paint, and then do so
    (var texture-x (math.floor (* wall-col (. wall-texture :w))))
    (if (and (= side 0) (> ray-dir-x 0)) (set texture-x (- (- (. wall-texture :w) texture-x) 1)))
    (if (and (= side 1) (< ray-dir-y 0)) (set texture-x (- (- (. wall-texture :w) texture-x) 1)))
    (love.graphics.setColor 1 1 1)
    (love.graphics.draw (. wall-texture :t)
                        (love.graphics.newQuad texture-x 0 1 (. wall-texture :h) (. wall-texture :w) (. wall-texture :h))
                        i draw-start 0 1 (/ line-height (. wall-texture :h)))
    ; Draw simple lines
    ; (love.graphics.line i draw-start i draw-end)
    (love.graphics.setColor 1 0 0)
    (love.graphics.print (.. "player-x: " player.posx ", player-y:" player.posy) 50 300)
    (love.graphics.print (.. "map-x: " map-x ", map-y:" map-y ", val:" (. map map-x map-y)) 50 330)
    (love.graphics.print (.. "side: " side ", ray-length:" ray-length) 50 370)
    (love.graphics.print (.. "step-x: " step-x ", step-y:" step-y) 50 400)
    )
  )

 :update (fn update [dt]
      (when (love.keyboard.isDown "j") (rotate-player 0.1))
      (when (love.keyboard.isDown "l") (rotate-player -0.1)))

 :keypressed (fn keypressed [key set-mode]
      (when (= key "j") (rotate-player 0.05))
      (when (= key "l") (rotate-player -0.05))
      (when (= key "i") (move-player 1))
      (when (= key "k") (move-player -1))
      (when (= key "x") (love.event.quit)))}
