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

(local {: get-online-of : get-cards-on : is-online : get-cell}
       (require :match.info))

(local {: move} (require :move))
(local {: view} (require :fennel))

(fn online-at [world player x y]
  (. (icollect [_ card (ipairs (get-cards-on world x y))]
       (when (and (= card.card.owner player) (is-online card.card))
         card)) 1))

(fn give-terminal [world player kind]
  (var slot nil)
  (each [_ entity (ipairs world)]
    (match entity
      {:slot {: kind :owner player}} (set slot entity)))
  (set slot.slot.used true)
  (table.insert world {:in-slot slot :card {: kind :owner player :open true}}))

(fn remove-children [world card]
  (each [num entity (ipairs world)]
    (match entity {:parent card} (world.remove num))))

(fn capture [world player card]
  (let [slot (. (icollect [_ entity (ipairs world)]
                  (match entity
                    {:slot {:owner player :kind card.card.kind :used nil}} entity))
                1)]
    (set card.in-slot slot)
    (set slot.slot.used true))
  (move card)
  (set card.circuit-x nil)
  (set card.circuit-y nil)
  (set card.card.open true)
  (set card.captured true)
  (when card.boosted
    (set card.boosted false)
    (give-terminal world card.card.owner :line-boost)
    (remove-children world card)))

(fn perform-action [world action]
  (case action
    {:kind :setup : cards : player}
    (let [online (get-online-of world player)]
      (each [_ {: x : y : kind} (ipairs cards)]
        (var found nil)
        (each [i {:card {:kind kind*} &as card} (ipairs online)]
          (when (and (not found) (= kind kind*))
            (set found card)
            (table.remove online i)))
        (move found)
        (set found.circuit-x x)
        (set found.circuit-y y))
      (var not-set-up false)
      (each [_ entity (ipairs world)]
        (when (and entity.card (is-online entity.card) (not entity.circuit-x))
          (set not-set-up true)))
      (if (not not-set-up)
          (set world.phase.kind :turn)))
    {: from-x : from-y : to-x : to-y : player}
    (let [card (online-at world player from-x from-y)]
      (assert card)
      (each [_ other (ipairs (get-cards-on world to-x to-y))]
        (when (and (is-online other.card) (not= other.card.owner card.owner))
          (capture world player other)))
      (move card)
      (set card.circuit-x to-x)
      (set card.circuit-y to-y))
    {:kind :fire-wall : x : y : player}
    (do
      (var found-id nil)
      (var card nil)
      (each [id entity (ipairs world)]
        (match entity {:card {:owner player :kind :fire-wall}} (do
                                                     (set found-id id)
                                                     (set card entity))))
      (world.remove found-id)
      (set card.in-slot.slot.used nil)
      (let [cell (get-cell world x y)]
        (set cell.cell.fire-walled player)))
    {:kind :virus-check : x : y : player}
    (do
      (var to-reveal nil)
      (each [_ card (ipairs (get-cards-on world x y))]
        (when (is-online card.card)
          (set to-reveal card)))
      (set to-reveal.card.open true)
      (each [id card (ipairs world)]
        (match card {:card {:kind :virus-check :owner player}} (world.remove id))))
    {:kind :line-boost : x : y : player}
    (do
      (var to-boost nil)
      (each [_ card (ipairs (get-cards-on world x y))]
        (when (is-online card.card)
          (set to-boost card)))
      (set to-boost.boosted true)
      (table.insert world {:particles (fcollect [i 1 (* math.pi 3) (/ math.pi 5)]
                                        {:x (* (math.sin i) 8)
                                         :y (* (math.cos i) 8)
                                         :dir [0 0]
                                         :angle (+ i (/ math.pi 2))
                                         :torque 0.07})
                           :parent to-boost})
      (each [id card (ipairs world)]
        (match card {:card {:kind :line-boost :owner player}} (world.remove id))))
    {:kind :remove-line-boost : player}
    (do
      (each [_ entity (ipairs world)]
        (match entity {:boosted true :card {:owner player}} (do
                                                  (set entity.boosted false)
                                                  (remove-children world entity))))
      (give-terminal world player :line-boost))
    {:kind :remove-fire-wall : player : x : y}
    (do
      (each [_ entity (ipairs world)]
        (match entity
          {:cell {:fire-walled player} :circuit-x x :circuit-y y}
          (set entity.cell.fire-walled nil)))
      (give-terminal world player :fire-wall))
    {:kind :infiltrate : x : y : player}
    (do
      (capture world player (. (icollect [_ card (ipairs (get-cards-on world x
                                                                       y))]
                                 (when (and (is-online card.card)
                                            (= card.card.owner player))
                                   card)) 1)))
    {:kind :404 : xa : ya : xb : yb : player}
    (do
      (let [a (online-at world player xa ya)
            b (online-at world player xb yb)
            a-kind a.card.kind]
        (var card nil)
        (each [id entity (ipairs world)]
          (match entity {:card {:owner player :kind :404}} (set card id)))
        (world.remove card)
        (set a.move {:from-x b.x :from-y b.y :start (_G.time)})
        (set b.move {:from-x a.x :from-y a.y :start (_G.time)})
        (set a.card.kind b.card.kind)
        (set b.card.kind a-kind)))
    other
    (error (.. "Invalid action: " (view other))))
  (set world.last-action action)
  (set world.phase
       {:kind world.phase.kind
        :player (+ (% world.phase.player (length world.players)) 1)}))

{: perform-action}