; SPDX-FileCopyrightText: 2023 Jummit
;
; SPDX-License-Identifier: GPL-3.0-or-later

(local {: new-world} (require :world))
(local {: draw-cards} (require :cards))
(local {: draw-portraits} (require :portraits))
(local {: update-particles : draw-particles} (require :particles))
(local {: select-with-mouse : perform-select} (require :select))
(local {: make-circuit-mat : make-slots : make-decks} (require :match.make))

(local {: arrange-on-circuit-mat
        : draw-cells
        : draw-highlighted-cells
        : draw-slots
        : draw-game-over
        : arrange-in-slots
        : draw-speech-bubbles} (require :match.draw))

(local {: perform-action} (require :match.action))
(local {: animate-movement} (require :move))
(local {: player-turn : player-setup} (require :match.player))
(local {: get-start-cells-for} (require :match.info))
(local {: make-state} (require :match.state))
(local {: decay} (require :decay))

(fn move-to-parent [world]
  "Sets the x and y coordinates of entities to their parent."
  (each [_ entity (ipairs world)]
    (case entity
      {: parent}
      (do
        (set entity.x (+ (or parent.screen-x parent.x) 2))
        (set entity.y (+ (or parent.screen-y parent.y) 3))))))

(fn say [world message]
  (table.insert world {:speech-bubble {:text message :progress 0}
                       :decay-in (+ (* (length message) 10) 100)
                       :x 145
                       :y 58}))

(fn ai-setup [world]
  "Makes AIs perform the setup action when it's their turn."
  (case world.phase
    {:kind :setup : player}
    (case (. world.players player)
      {: setup : greeting}
      (let [scores (icollect [_ {:circuit-x x :circuit-y y} (ipairs (get-start-cells-for world
                                                                                         player))]
                     {: x : y :score (setup x y)})]
        (table.sort scores (fn [a b] (< a.score b.score)))
        (var links-left 4)
        (say world greeting)
        (perform-action world
                        {:kind :setup
                         : player
                         :cards (icollect [_ {: x : y} (ipairs scores)]
                                  (do
                                    (set links-left (- links-left 1))
                                    {:kind (if (<= 0 links-left) :link :virus)
                                     : x
                                     : y}))})))))

(fn ai-turn [world]
  "Makes AIs perform an action when it's their turn."
  (case world.phase
    {:kind :turn : player}
    (case (. world.players player)
      {: turn : commentate : reply}
      (do
        (set world.phase.wait-time (+ (or world.phase.wait-time 0) 1))
        (when (< 15 world.phase.wait-time)
          (set world.phase.wait-time nil)
          (let [state (make-state world player)
                move (turn state world.last-action)]
            (set move.player player)
            (perform-action world move)
            (-?>> (or (reply state world.last-action) (commentate state move))
                  (say world))))))))

(fn end-game [world]
  "System which ends the game if a player won."
  (when (not= world.phase.kind :game-over)
    (var won nil)
    (each [id player (ipairs world.players)]
      (var links
           (length (icollect [_ entity (ipairs world)]
                     (match entity
                       {:slot {:owner id :kind :link :used true}} true))))
      (var virus
           (length (icollect [_ entity (ipairs world)]
                     (match entity
                       {:slot {:owner id :kind :virus :used true}} true))))
      (when (= 4 links)
        (set won player))
      (when (= 4 virus)
        (set won (. (icollect [_ other (ipairs world.players)]
                      (when (not= other player) other)) 1))))
    (when won
      (_G.music 3)
      (each [_ player (ipairs world.players)]
        (case player
          {: won-message : lost-message}
          (say world (if (= player won) won-message lost-message))))
      (set world.phase {:kind :game-over : won}))))

(fn continue [world]
  "System which goes to the title screen after the game is over."
  (when (= world.phase.kind :game-over)
    (let [(_ _ down) (_G.mouse)]
      (set world.phase.elapsed (+ (or world.phase.elapsed 0) 1))
      (when (and (< 100 world.phase.elapsed) down)
        (local {: new-title-screen} (require :title_screen))
        (set world.next (new-title-screen))))))

(fn debug-setup-player [world opponent]
  (set world.players [(opponent.ai) (opponent.ai)])
  (set world.phase {:kind :setup :player 1})
  (world.run)
  (world.run)
  (set world.players [(opponent.ai) :player])
  (each [_ entity (ipairs world)]
    (case entity
      {:card {: owner}}
      (when (= (. world.players owner) :player)
        (set entity.card.open true)))))

(fn new-match [opponent]
  "Returns a world containing a match against the given opponent."
  (_G.music 2)
  (local entities [{:portrait opponent :x 195 :y 22}])
  (make-slots entities)
  (make-circuit-mat entities)
  (make-decks entities)
  (local world (new-world [(fn [] (_G.cls 0))
                           move-to-parent
                           ai-setup
                           player-setup
                           ai-turn
                           player-turn
                           arrange-on-circuit-mat
                           arrange-in-slots
                           animate-movement
                           update-particles
                           draw-cells
                           draw-slots
                           draw-cards
                           draw-highlighted-cells
                           draw-particles
                           draw-game-over
                           draw-portraits
                           draw-speech-bubbles
                           select-with-mouse
                           perform-select
                           decay
                           end-game
                           continue] entities))
  (set world.players [(opponent.ai) :player])
  (set world.phase {:kind :setup :player 1})
  (debug-setup-player world opponent)
  world)

{: new-match}