-- |
-- Module: Queries Tutorial
-- Description: How To Mischief.
--
-- [Previous Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Dungeon")
--
-- [Next Chapter: Components]("Mischief.ECS.Tutorial.Components")
--
-- [Main Page]("Mischief.ECS")
module Mischief.ECS.Tutorial.Patterns
  ( -- * Learn You an ECS for Great Mischief! - 3. Common Patterns
    -- $intro

    -- * Organizing Your Code
    -- $org

    -- * The Query Bind Pattern
    -- $join

    -- * Extending Queries
    -- $extend

    -- * [Next Chapter: Components]("Mischief.ECS.Tutorial.Components")
  )
where

import Control.Monad (void)
import Data.Foldable (for_)
import Data.Traversable (for)
import Mischief.ECS

-- $intro
-- This module attmepts to capture a few common problems one may encounter when working with Mischief, and various patterns and solutions for solving them.

-- $org
-- It's a common problem when working with a game framework to wonder where and how to organize your logic. Luckily, Mischief comes with a few tips and guidelines on the subject:
--
-- ===Modularity
--
-- Modularily is highly encouraged in everything you do. You should strive to design your codebase so that each
-- part of it can be easily plugged in and out without affecting the functionality of anything outside of it.
--
-- For instance, you may have a @PhysicsPlugin@ that adds physics to your game, a @RenderPlugin@ which renders objects, a @LevelPlugin@ that spawns your levels.
--
-- @
-- main :: 'IO' ()
-- main = do
--   app <- 'newApp'
--   'addPlugin' \@PhysicsPlugin
--   'addPlugin' \@RenderPlugin
--   'addPlugin' \@LevelPlugin
-- @
--
-- Ideally, each of these plugins add their own independent features. So if you were to remove @physicsPlugin@, your entities simply wouldn't
-- move and collide anymore, but the rest of the app would work just fine. Dependencies between plugins at the same level should be avoided.
-- For instance, @PhysicsPlugin@ should not ever depend on @RenderPlugin@.
--
-- Internally, @PhsyicsPlugin@ could also be subdivided into its own plugins with separate roles:
--
-- @
-- data PhysicsPlugin
--
-- instance 'Plugin' PhysicsPlugin where
--   deps = ['dep' \@CollisionPlugin, 'dep' \@MovePlugin]
-- @
--
-- And these should follow the same principle of independence.
--
-- Of course, complete modulariy may just not be possible or desireable at points, but we consider it to be a great standard to look up to.
--
-- ===Files
--
-- File-wise, we generally encourage placing each plugin in a separate module / file. It's also desireable for the module hierarchy to
-- follow the follow the plugin dependency. If the @Player@ plugin in @MyGame.Player@ depends on the @Health@ plugin, the latter
-- is ideally placed in a @MyGame.Player.Health@ module.
--
-- We also encourage defining components locally in the module that uses them most. If multiple modules at the same level use the
-- same component, consider placing it in a separete @Common@ module.
--
-- Even if you don't follow these conventions, it's still important that at you are at least being consistent in these areas.

-- $join
-- To re-use the context from the previous chapter, consider that you have players marked by a @Player@ component, each with
-- a @Coins Int@ component storing how many coins they have collected. You have coins in the world, marked by @Coin Int@, and want to
-- iterate over all players, then over all coins, and, if the coin is in range of the player, despawn the entity and add its value to the
-- total collected by the player.
--
-- This \"nested query\" is a problem you may encounter in a lot of places. You've already seen how it's solved imperatively, by actually nesting the queries,
-- but that solution isn't great. It's highly imperative and mutable, and the indentation gets bad as you need to nest more and more queries.
--
-- @
-- collectCoins :: 'System' ()
-- collectCoins = do
--   players \<- 'query' ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]
--   'for_' players $ \\(player, 'From' _ tile, Coins coins) -> do
--     collected <-
--       ['q'|Coin / With OnTile -> tile|]
--         & 'qtap' (\\e _ -> despawn e)
--         & 'query'
--
--     'insert' (Coins $ 'foldr' (\\(Coin x) -> (+ x)) coins collected) player
-- @
--
-- The annoying part of the code above is that you're pulling /all/ entities out of the query, iterating over them, then performing another query and
-- iterating over even more entities.
--
-- This is where the monadic part of the queries comes in. The goal is to perform all those operations /inside/ the same query, smartly chaining them using /do/.
--
-- As a first step it's often helpful to start with a code like this:
--
-- @
-- 'query_' $ do
--   (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]
--   'pure' ()
-- @
--
-- Just copy the main query that you're iterating over. And /bind/ the data out of it using @<-@.
-- Wrap it in a @do@ folllwing a @query_@ or @query@.
--
-- At this point, you've put your hand on a slice of the data flowing through the query. What you do with this data
-- dictates what will happen with all data.
--
-- Next, try replacing the @pure@ with another query, typically the second query that you're iterating over:
--
-- @
-- 'query_' $ do
--   (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]
--
--   ['q'|Coin / With OnTile -\> tile|]
--     & 'qtap' (\\e _ -> 'despawn' e)
-- @
--
-- This is already a valid query. You're iterating over all the coins that are on each player's tile. You've changed the source, or subject,
-- from being the players to being the coins. But in this case that's not really what you want. You want to somehow collect or aggregate
-- the coins to use them in the larger query.
--
-- Let's try collecting them first:
--
-- @
-- 'query_' $ do
--   (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]
--
--   collectedCoins <-
--     ['q'|Coin / With OnTile -\> tile|]
--       & 'qtap' (\\e _ -> 'despawn' e)
--       & 'qcollect' entity
--
--   pure ()
-- @
--
-- The @qcollect@ function takes an entity, and converts the entire query into just a single element, belonging to that entity.
-- You may see that @collectedCoins@ has type @[From Coin]@.
--
-- Your first instinct here may be to take @collectedCoins@ and start another query instead of the @pure ()@, thinking that you're continuing the original
-- query. But that's wrong. That would just create another level of nesting. That's the big fallacy present in this monad.
-- Instead, what you want is to continue from the @qcollect@. /This/ is the continuation of the original query. With the @qcollect@ we've changed the subject
-- back to the players. We are looking at the coins from above, as symbolized by the @From Coin@ type.
--
-- So this is what you want to be doing instead:
--
-- @
-- 'query_' $ do
--   (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]
--
--   ['q'|Coin / With OnTile -\> tile|]
--     & 'qtap' (\\e _ -> 'despawn' e)
--     & 'qcollect' entity
--     & 'qinsert' (\\collectedCoins -> Coins $ 'foldr' (\\('From' _ (Coin x)) -> (+ x)) coins collectedCoins)
-- @
--
-- That's it! The coins are now folded and added to the total held by the player.
--
-- The only thing to add is that there's actually an alternative to @qcollect@. You can use @qfoldr@ to fold the elements directly:
--
-- @
-- 'query_' $ do
--   (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]
--
--   ['q'|Coin / With OnTile -\> tile|]
--     & 'qtap' (\\e _ -> 'despawn' e)
--     & 'qfoldr' (\\('From' _ (Coin x)) -> (+ x)) coins entity
--     & 'qinsert' Coins
-- @
--
-- You have successfully turned a collection of nested mutations into one pure computation!
--
-- ===Appendix: Pure-ing out
--
-- Alright, so I've lied to you. There is an escape hatch via @pure@, it just requires more explanation and attention.
--
-- Let's consider this:
--
-- @
-- 'query_' $ do
--   (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]
--
--   value <- ['q'|Coin / With OnTile -\> tile|]
--     & 'qtap' (\\e _ -> 'despawn' e)
--     & 'qfoldr' (\\('From' _ (Coin x)) -> (+ x)) 0 entity
--
--   -- some other operations
--
--   -- TODO: add @value@ to @coins@ on each player.
-- @
--
-- You've taken the value out of the second query. You now want to add it to the total number of coins. But you can't chain it directly to
-- @qfoldr@, because, for instance, you may have other operations you need to perform first (see the next section for such a situation).
--
-- What you want is to somehow connect this back to the player:
--
-- @
-- 'qinsert' (const $ Coins $ coins + value)
-- @
--
-- A natural thing to try would be:
--
-- @
-- 'pure' ()
--   & 'qinsert' ('const' $ Coins $ coins + value)
-- @
--
-- And this will compile, but it won't have the effect that you want. What @pure@ does is that creates a query with a single element in it, focusing on
-- something called the \"null entity\". That insert will run for each player, but it will always insert the component on that entity instead.
--
-- The correct way out is to use @qrefocus@, which can force every element in the query to refer to a given entity:
--
-- @
-- 'pure' ()
--   & 'qrefocus' entity
--   & 'qinsert' ('const' $ Coins $ coins + value)
-- @
--
-- This /will/ now work as expected. In an imperative way, what you're doing is essentially:
--
-- @
-- for (entity, Coins coins) in players:
--   value = 0
--   for x in coins:
--     value += x
--
--   for entity in [entity]:
--     insert (Coins (coins + value)) entity
-- @
--
-- The @pure@ is just that one-element query at the end that you're forcing to look at @entity@.
--
-- The same can also be achieved by using @qpure@:
--
-- @
-- 'qpure' entity
--   & 'qinsert' ('const' $ Coins $ coins + value)
-- @

-- $extend
-- One annoying problem when writing complex systems is having to query and unwrap new data.
--
-- @
-- players \<- 'query' ['q'|Name / With Player|]
-- 'for_' players $ \\name -\> do
--   someRes \<- 'res' \@SomeRes
--   'for_' someRes $ \\someRes -> do
--     someOhterRes \<- 'res' \@SomeOtherRes
--     'for_' someOhterRes $ \\someOhterRes -> do
--       position \<- 'single' ['q'|player. Position|]
--       'for_' position $ \\position -> do
--         -- do something
-- @
--
-- When writing imperative code it's easy to fall into a pattern where you keep needing more data and having to iterate over Maybes in order to only run
-- the code if it's all there.
--
-- There are 3 main patterns to help solve this problem:
--
-- ===1. Internalizing
--
-- When possible, you can try internalizing all data you need into the same query. Transitive and local components, as well as resources, can be queried at once:
--
-- @
-- players \<- 'query' ['q'|Entity, Position, Name, Res SomeRes, Res SomeOtherRes / With Player|]
-- 'for_' players $ \\(player, position, name, someRes, someOtherRes) -> do
--   -- do something
-- @
--
-- This is often a nice fix. Although, if you need to grab a lot of external data, the query might start to feel uncomfortably large. Plus, this will not always
-- work for more complex queries.
--
-- ===2. Piping
--
-- Sometimes piping can work wonders.
--
-- @
-- players \<-
--   ['q'|Entity, Name / With Player|]
--     & 'qextend' \['qd'|Res SomeRes, Res SomeOtherRes, Position|] (,)
--     & 'query'
--
-- 'for_' players $ \\((player, name), (someRes, someOhterRes, position)) -> do
--   -- do something
-- @
--
-- You're now using @qextend@ to query new data and map it into the other data. This may allow for more complex
-- queries (through methods such as @qjoin@, and @qrelateMany@), but you still suffer from having to
-- list all the data when iterating.
--
-- ===3. Binding
--
-- Once again, the @Query@ monad saves the day. You can use @qres@ and @qget@ to seamlessly chain data into your flow.
--
-- @
-- 'query_' $ do
--   (player, name) \<- ['q'|Entity, Name / With Player|]
--   someRes \<- 'qres' \@SomeRes
--   someOtherRes \<- 'qres' \@SomeOtherRes
--   position \<- 'qget' player ['qd'|Position|]
--
--   'qpure' player
--     & -- do something
-- @
--
-- You now have a fully functional and pure chain of operations. You don't need to ever re-list your variable, since they're all already bound in the current scope.
-- If a resource doesn't exist, @qres@ will just naturally null your whole query. Same with @qget@. This method also lets you write any complex queries.