(use-modules (chickadee)
	     (chickadee graphics sprite)
	     (chickadee graphics viewport)
	     (chickadee graphics texture)
	     (chickadee graphics color)
	     (chickadee graphics text)
	     (chickadee math vector)
	     (chickadee math rect)
	     (srfi srfi-9)
	     (chickadee audio)
	     (ice-9 pretty-print)
	     (ice-9 format)
	     (srfi srfi-1))


;;;; Vehicles module
(define vehicle-height 44)
(define vehicle-length 100)
(define vehicle-steering-speed 4.0)

(define-record-type <vehicle>
  (make-vehicle id sprite position speed active?)
  vechicle?
  (id vehicle-id)
  (sprite vehicle-sprite)
  (position vehicle-position set-vehicle-position!)
  (speed vehicle-speed set-vehicle-speed!)
  (active? vehicle-active? set-vehicle-active!))

(define (make-inactive-vehicle id tile initial-position)
  (make-vehicle id tile initial-position 4 #f))

(define (vehicle-hitbox vehicle)
  (rect (vec2-x (vehicle-position vehicle))
	(vec2-y (vehicle-position vehicle))
	vehicle-length
	vehicle-height))

(define (vehicle-safety-distance? vehicle1 vehicle2)
  (let ((vehicle1-x (vec2-x (vehicle-position vehicle1)))
	(vehicle2-x (vec2-x (vehicle-position vehicle2))))
    (> (abs (- vehicle2-x vehicle1-x))
       (+ (* 3 vehicle-length)))))

(define (vehicle-speed- vehicle1 vehicle2)
  (- (vehicle-speed vehicle1) (vehicle-speed vehicle2)))

(define (vehicle-move! vehicle translation)
  (set-vehicle-position!
   vehicle
   (vec2+ (vehicle-position vehicle) translation)))

(define (vehicle-inactive? vehicle)
  (not (vehicle-active? vehicle)))

(define (vehicle= vehicle1 vehicle2)
  (= (vehicle-id vehicle1) (vehicle-id vehicle2)))
;;;; End vehicles module

;;;; Constants and variables

(define assets-root (if (string=? (getenv "APPDIR") "")
			""
			(format #f "~a/usr/share/turbo/" (getenv "APPDIR"))))
(define (asset-path asset)
  (format #f "~a~a" assets-root asset))

(define the-font #f)
(define font-7-segments #f)

(define lane-height 65)
(define lane-length 192)
(define road-start-y 80)
(define road-section-position 0)
(define road #f)
(define road-num-lanes 5)
(define road-end-y (+ road-start-y (* road-num-lanes lane-height)))
(define road-top #f)
(define road-bottom #f)
(define road-lane #f)
(define grass #f)
(define dashboard-set #f)
(define dash-background #f)
(define dash-7seg-background #f)

(define grass-height 96)

(define vehicles-sprites #f)
(define vehicles '())

(define player-velocity (vec2 0 0))
(define player-min-speed 250)
(define player-max-speed 1072)
(define player-rotation 0)
;; 2px/s/s acceleration
(define player-car-acceleration 100)
;; 4px/s/s deceleration
(define player-car-deceleration -500)
(define player-current-acceleration 0)
(define player-distance-travelled 0)
(define player-time-travelled 0)
(define (distance-in-miles)
  (/ player-distance-travelled 12440))

(define (speed-in-mph)
  (* (vehicle-speed player-car) 0.11187))

(define level-difficulty 1)
(define max-level 20)

(define music-res #f)
(define music #f)
(define engine-res #f)
(define engine-sound #f)

(define (calculate-vehicle-position lane)
  (vec2 (window-width (current-window))
	(+ road-start-y
	   (* 65 (- lane 1))
	   (floor (/ (- lane-height vehicle-height) 2)))))

(define (random-lane-start-pos)
  (calculate-vehicle-position (+ 1 (random road-num-lanes))))

(define (make-vehicle-list vehicle-list tile-ref)
  (if (< tile-ref (texture-atlas-size vehicles-sprites))
      (cons (make-inactive-vehicle (* 2 tile-ref)
				   (texture-atlas-ref vehicles-sprites tile-ref)
				   (random-lane-start-pos))
	    (cons (make-inactive-vehicle (+ 1 (* 2 tile-ref))
					 (texture-atlas-ref vehicles-sprites tile-ref)
					 (random-lane-start-pos))
		  (make-vehicle-list vehicle-list (+ tile-ref 1))))
      vehicle-list))

(define (vehicle-reset-ahead veh)
  (let ((lane-num (random road-num-lanes)))
    (set-vehicle-position! veh (calculate-vehicle-position
				(+ 1 lane-num)))
    (set-vehicle-speed! veh (+ player-min-speed (* 75 lane-num)) )
    (set-vehicle-active! veh #f)))

(define game-started? #f)
(define game-over? #f)
(define show-credits? #f)
(define show-help? #f)
(define menu-items (list 'new-game 'credits 'help 'quit))
(define menu-selection 0)

(define (vehicle-has-spawn-space? vehicle)
  (define (vehicle-in-the-way? v)
    (and (not (vehicle= vehicle v))
	 (vehicle-active? v)
	 (not (vehicle-safety-distance? v vehicle))))
  (not (any vehicle-in-the-way? vehicles)))


;;;; Game functions

(define (draw-lanes x y num-lanes)
  (when (> num-lanes 0)
      (draw-sprite road-lane (vec2 x y))
      (draw-lanes x (+ lane-height y) (- num-lanes 1))))

(define (draw-road-section startx starty num-lanes)
  (draw-sprite road-bottom (vec2 startx starty))
  (draw-lanes startx (+ lane-height starty) (- num-lanes 2))
  (draw-sprite road-top (vec2 startx (+ starty (* lane-height (- num-lanes 1))))))

(define (draw-grass-down x y)
  (draw-sprite grass (vec2 x (- y grass-height))))

(define (draw-grass-up x y)
  (draw-sprite grass (vec2 x y)))

(define (draw-road startx starty num-lanes)
  (when (< startx (window-width (current-window)))
      (draw-grass-down startx starty)
      (draw-road-section startx starty num-lanes)
      (draw-road (+ startx lane-length) starty num-lanes)
      (draw-grass-up startx (+ starty (* lane-height num-lanes)))))

(define (lane-y num-lane)
  (+ road-start-y
     (* lane-height (- num-lane 1))))

(define initial-player-position (vec2 0 0))

(define player-car #f)

(define (level-min-speed)
  (+ player-min-speed (* (- level-difficulty 1) 36)))

(define car-gears 5)

(define (gearing-range)
  (/ (* 1.05 player-max-speed) car-gears))

(define (speed->gear speed)
  (let ((gear-range (gearing-range)))
    (floor (/ speed gear-range))))

(define (speed->engine-power speed)
  (let* ((gear (speed->gear speed))
	 (speed-range (* gear (gearing-range))))
    (/ (- speed speed-range) speed-range)))

(define (set-player-speed! new-speed)
  (let* ((min-speed (level-min-speed))
	 (player-speed (max (min new-speed player-max-speed) min-speed))
	 (engine-power (speed->engine-power player-speed))
	 (pitch-factor (+ 1 engine-power)))
    (set-vehicle-speed! player-car player-speed)
    (set-source-pitch! engine-sound (+ 1 pitch-factor))))

(define (player-on-road?)
  (and (> (vec2-y (vehicle-position player-car)) road-start-y)
       (< (+ (vec2-y (vehicle-position player-car)) vehicle-height) road-end-y)))

(define (crash-player top?)
  (set! player-rotation (if top? 1 -1))
  (set-vehicle-position! player-car (vec2+ (vehicle-position player-car) (vec2 0 (if top? 30 10))))
  (set-vehicle-speed! player-car 0)
  (source-stop engine-sound))

(define (off-road-player-top)
  (crash-player #t))

(define (off-road-player-bottom)
  (crash-player #f))

(define (off-road-player)
  "Moves the player off-road"
  (if (> (vec2-y (vehicle-position player-car)) road-start-y)
      (off-road-player-top)
      (off-road-player-bottom)))

(define (player-collides?)
  (let ((player-hitbox (vehicle-hitbox player-car)))
    (any (lambda (vehicle)
	   (rect-intersects? player-hitbox (vehicle-hitbox vehicle)))
	 vehicles)))


(define (vehicle-off-screen? veh)
  (and vehicle-active?
       (>= (- vehicle-length) (vec2-x (vehicle-position veh)))))

(define (reset-game!)
  (set! player-distance-travelled 0)
  (set! player-time-travelled 0)
  (set! level-difficulty 1)
  (set-vehicle-position! player-car initial-player-position)
  (set-player-speed! (level-min-speed))
  (set! player-rotation 0)
  (source-play engine-sound)
  (for-each vehicle-reset-ahead vehicles)
  ;; Reset all vehicles
  (set! game-over? #f)
  (set! game-started? #t))

(define (game-won?)
  (and game-started? (> level-difficulty max-level)))

;;;; Chickadee hooks

(define (load)
  (set! the-font (load-bitmap-font (asset-path "fonts/thick_8x8.xml")))
  (set! font-7-segments (load-font (asset-path "fonts/DSEG7Classic-Regular.ttf") 24))
  (set! road (load-tileset (asset-path "sprites/road.png") lane-length lane-height))
  (set! road-top (texture-atlas-ref road 2))
  (set! road-bottom (texture-atlas-ref road 0))
  (set! road-lane (texture-atlas-ref road 1))
  (set! grass (load-image (asset-path "sprites/grass.png")))
  (set! dashboard-set (load-tileset (asset-path "sprites/dash-background.png") 64 64))
  (set! dash-background (texture-atlas-ref dashboard-set 0))
  (set! dash-7seg-background (texture-atlas-ref dashboard-set 1))
  (set! vehicles-sprites (load-tileset (asset-path "sprites/vehicles.png") vehicle-length vehicle-height))

  (set! initial-player-position
    (vec2 (/ (- (window-width (current-window)) vehicle-length) 6)
	  (+ (lane-y 3)
	     (/ (- lane-height vehicle-height) 2))))
  (set! player-car (make-vehicle
		    0
		    (texture-atlas-ref vehicles-sprites 0)
		    initial-player-position
		    (+ 1 player-min-speed)
		    #t))

  ;; Audio
  (set! music-res (load-audio (asset-path "sound/music.ogg")))
  (set! music (make-source #:audio music-res #:loop? #t))
  (set! engine-res (load-audio (asset-path "sound/engine-loop-1.ogg")))
  (set! engine-sound (make-source #:audio engine-res #:loop? #t))
  (set-source-volume! music 0.3)

  (set! vehicles (make-vehicle-list (list) 1))
  (for-each vehicle-reset-ahead vehicles)
  (set-vehicle-active! (car vehicles) #t)

  (source-play music)
  (source-play engine-sound))

(define (update interval)
  (unless game-over?
    (unless (player-on-road?)
      (off-road-player)
      (set! game-over? #t))


    (set! player-distance-travelled (+ player-distance-travelled (* interval (vehicle-speed player-car))))
    (set! player-time-travelled (+ player-time-travelled interval))

    ;; Increase level every 2 miles
    (when (and (< level-difficulty max-level) (> (distance-in-miles) (* 2 level-difficulty)))
      (set! level-difficulty (+ 1 level-difficulty)))
    
    (if (game-won?)
	(set! game-over? #t))

    (let ((new-speed (+ (vehicle-speed player-car)
			(* player-current-acceleration interval))))
      (set-player-speed! new-speed))

    (when (and game-started? (player-collides?))
      (off-road-player)
      ;; TODO: Think of different animation for this game over
      (set! game-over? #t))

    (set! road-section-position
	  (if (< road-section-position (- lane-length))
	      (- (+ road-section-position lane-length) (* interval (vehicle-speed player-car)))
	      (- road-section-position (* interval (vehicle-speed player-car)))))

    (set-vehicle-position! player-car (vec2+ (vehicle-position player-car)
					     player-velocity))

    (for-each (lambda (veh)
		(vehicle-move! veh (vec2 (- (* interval (vehicle-speed- player-car veh))) 0)))
	      (filter vehicle-active? vehicles))

    ;; Reset vehicles that have gone off screen
    (for-each vehicle-reset-ahead (filter vehicle-off-screen? vehicles))

    ;; Randomly respawn vehicles based on level difficulty
    (let ((challenge (- (round (/ (* max-level 10) level-difficulty)) 9)))
      (when (= 0 (random challenge))
	(let* ((inactive-vehicles (filter vehicle-inactive? vehicles))
	       (spawnable-vehicles (filter vehicle-has-spawn-space? inactive-vehicles)))
	  (if (not (null? spawnable-vehicles))
	      (set-vehicle-active! (list-ref
				    spawnable-vehicles
				    (random (length spawnable-vehicles)))
				   #t)))))
    ;; End (unless game-over?)
    ))

(define (draw alpha)
  (draw-road road-section-position road-start-y road-num-lanes)

  (for-each (lambda (veh)
	      (draw-sprite (vehicle-sprite veh) (vehicle-position veh)))
	    (filter vehicle-active? vehicles))

  (let loop ((index 0))
    (draw-sprite dash-background (vec2 index 0))
    (when (< (+ index (texture-width dash-background)) (window-width (current-window)))
      (loop (+ index (texture-width dash-background)))))

  (draw-sprite (vehicle-sprite player-car) (vehicle-position player-car) #:rotation player-rotation)
  (unless game-started?
      (let ((window-top (window-height (current-window))))
	(cond
	 (show-credits?
	  (let ((left-margin 60)
		(top-start 125)
		(gap 25))
	    (draw-text "TURBO RACER 3000!"
		       (vec2 left-margin (-  window-top top-start))
		       #:font the-font
		       #:scale (vec2 4 4)
		       #:color red)
	    (draw-text "DEVELOPMENT: Gene Pasquet"
		       (vec2 left-margin (- window-top (+ top-start gap)))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "DESIGN: Owen Pasquet"
		       (vec2 left-margin (- window-top (+ top-start (* 2 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "PLAY TESTING: Owen Pasquet"
		       (vec2 left-margin (- window-top (+ top-start (* 3 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "MUSIC: Instrumental by Aries Beats"
		       (vec2 left-margin (- window-top (+ top-start (* 4 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "FONTS: Keshikan"
		       (vec2 left-margin (- window-top (+ top-start (* 5 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "SOUND: qubodup"
		       (vec2 left-margin (- window-top (+ top-start (* 6 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "BACK"
		       (vec2 250 (- window-top 330))
		       #:font the-font
		       #:scale (vec2 2 2)
		       #:color yellow)))
	 (show-help?
	  (let ((left-margin 60)
		(top-start 125)
		(gap 25))
	    (draw-text "TURBO RACER 3000!"
		       (vec2 left-margin (-  window-top top-start))
		       #:font the-font
		       #:scale (vec2 4 4)
		       #:color red)
	    (draw-text "Complete 10miles fast!"
		       (vec2 left-margin (- window-top (+ top-start gap)))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "Arrow keys or WASD control the car."
		       (vec2 left-margin (- window-top (+ top-start (* 2 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "ESCAPE for the main menu"
		       (vec2 left-margin (- window-top (+ top-start (* 3 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text ""
		       (vec2 left-margin (- window-top (+ top-start (* 4 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text ""
		       (vec2 left-margin (- window-top (+ top-start (* 5 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text ""
		       (vec2 left-margin (- window-top (+ top-start (* 6 gap))))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "BACK"
		       (vec2 250 (- window-top 330))
		       #:font the-font
		       #:scale (vec2 2 2)
		       #:color yellow)))
	 (else
	  ;; Draw the menu
	  (draw-text "TURBO RACER 3000!"
		     (vec2 60 (-  window-top 125))
		     #:font the-font
		     #:scale (vec2 4 4)
		     #:color red)
	  (draw-text "NEW GAME"
		     (vec2 250 (- window-top 250))
		     #:font the-font
		     #:scale (vec2 2 2)
		     #:color (if (= menu-selection 0) yellow white))
	  (draw-text "CREDITS"
		     (vec2 250 (- window-top 270))
		     #:font the-font
		     #:scale (vec2 2 2)
		     #:color (if (= menu-selection 1) yellow white))
	  (draw-text "HELP"
		     (vec2 250 (- window-top 290))
		     #:font the-font
		     #:scale (vec2 2 2)
		     #:color (if (= menu-selection 2) yellow white))
	  (draw-text "QUIT"
		     (vec2 250 (- window-top 350))
		     #:font the-font
		     #:scale (vec2 2 2)
		     #:color (if (= menu-selection 3) yellow white))))))

  
  (draw-sprite dash-7seg-background (vec2 270 0))
  (draw-sprite dash-7seg-background (vec2 324 0))
  (draw-text (format #f "~5,1,,,'0f" (distance-in-miles))
	     (vec2 275 20)
	     #:font font-7-segments
	     #:color red)
  (draw-text "MI"
	     (vec2 395 12)
	     #:font the-font
	     #:scale (vec2 2 2))

  
  (draw-sprite dash-7seg-background (vec2 450 0))
  (draw-sprite dash-7seg-background (vec2 503 0))
  (draw-text (format #f "~5,1,,,'0f" (speed-in-mph))
	     (vec2 455.0 20.0)
	     #:font font-7-segments
	     #:color red)
  (draw-text "MPH"
	     (vec2 570 12)
	     #:font the-font
	     #:scale (vec2 2 2))

  (draw-sprite dash-7seg-background (vec2 20 0))
  (draw-sprite dash-7seg-background (vec2 72 0))
  (draw-text (format #f "~5,1,,,'0f" player-time-travelled)
	     (vec2 25 20)
	     #:font font-7-segments
	     #:color red)
  (draw-text "TIME"
	     (vec2 142 12)
	     #:font the-font
	     #:scale (vec2 2 2))

  (if game-over?
      (if (game-won?)
	  (let ((player-score (+ 10000 (* (- 900 player-time-travelled) 1000))))
	    (draw-text "THE END" (vec2 260 (/ (window-width (current-window)) 2))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text (format #f "SCORE: ~,0f" player-score)
		       (vec2 200 (- (/ (window-width (current-window)) 2) 25))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "TRY AGAIN" (vec2 230 (- (/ (window-width (current-window)) 2) 100))
		       #:font the-font
		       #:scale (vec2 2 2)
		       #:color yellow))
	  (begin
	    (draw-text "GAME OVER" (vec2 260 (/ (window-width (current-window)) 2))
		       #:font the-font
		       #:scale (vec2 2 2))
	    (draw-text "TRY AGAIN" (vec2 260 (- (/ (window-width (current-window)) 2) 25))
		       #:font the-font
		       #:scale (vec2 2 2)
		       #:color yellow)))))


(define (key-press key modifiers repeat?)
  (cond
   ((or (eq? key 'w) (eq? key 'up))
    (if game-started?
	(set! player-velocity (vec2 0 vehicle-steering-speed))
	(set! menu-selection (max 0 (- menu-selection 1)))))
   ((or (eq? key 's) (eq? key 'down))
    (if game-started?
	(set! player-velocity (vec2 0 (- vehicle-steering-speed)))
	(set! menu-selection (min (- (length menu-items) 1) (+ menu-selection 1)))))
   ((or (eq? key 'd) (eq? key 'right))
    (set! player-current-acceleration player-car-acceleration))
   ((or (eq? key 'a) (eq? key 'left))
    (set! player-current-acceleration player-car-deceleration))
   ((eq? key 'escape)
    (set! game-over? #f)
    (set! game-started? #f))
   ((and (eq? key 'return) game-over?)
    (reset-game!))
   ((and (eq? key 'return) (not game-started?))
    (cond
     (show-credits?
      (set! show-credits? #f))
     (show-help?
	 (set! show-help? #f))
     (else
      (cond
       ((= menu-selection 0)
	(reset-game!))
       ((= menu-selection 1)
	(set! show-credits? #t))
       ((= menu-selection 2)
	(set! show-help? #t))
       ((= menu-selection 3)
	(abort-game))))))))

(define (key-release key modifiers)
  (cond
   ((or (eq? key 'a)
	(eq? key 'd)
	(eq? key 'left)
	(eq? key 'right))
    (set! player-current-acceleration 0))
   ((or (eq? key 'w)
	(eq? key 's)
	(eq? key 'up)
	(eq? key 'down))
    (set! player-velocity (vec2 0 0)))))

(run-game #:window-title "Turbo Racer 3000"
	  #:load load
	  #:update update
	  #:draw draw
	  #:key-press key-press
	  #:key-release key-release)