{-# OPTIONS_GHC -Wno-unused-imports #-}

-- |
-- Module: Relationships Tutorial
-- Description: Tutorial on using @Relationships@
--
-- This module contains a more in-depth tutorial on @Mischief Relationships@.
--
-- [Previous Chapter: Components]("Mischief.ECS.Tutorial.Components")
--
-- [Next Chapter: Queries]("Mischief.ECS.Tutorial.Queries")
--
-- [Main Page]("Mischief.ECS")
module Mischief.ECS.Tutorial.Relationships
  ( -- * Learn You an ECS for Great Mischief! - 5. Relationships

    -- * Introduction
    -- $intro

    -- * Insertion
    -- $insertion

    -- * Removal
    -- $removal

    -- * Querying
    -- $query

    -- * Transitive Querying
    -- $trans

    -- * Hooks
    -- $hooks

    -- * Examples
    -- $examples

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

import Mischief.ECS

-- $intro
-- Mischief implement @Relationships@ in a similar way to @Flecs@.
--
-- When a component is added to an entity, it is actually indexed by a @ComponentId@:
--
-- @
-- data 'ComponentId' = ComponentId {id :: 'Entity', entity :: 'Maybe' 'Entity'}
-- @
--
-- The first field, @id@, is the entity corresponding to the component, while the second field, @entity@, is an optional reference to another entity.
--
-- This means that each @ComponentId@ can either be a simple component, or a pair between a component and an entity (technically even between
-- two components or two entities but that's not directly allowed by the API).
--
-- So a relationship in Mischief is a pair between a component and an entity. It can be inserted on entities using @Rel@:
--
-- @
-- data 'Rel' c = Rel {comp :: c, target :: 'Entity'}
-- @
--
-- For instance, this is how we spawn an entity @b@ that's a child of @a@:
--
-- @
-- a <- 'spawn' ()
-- b <- 'spawn' ('Rel' 'ChildOf' a)
-- @

-- $insertion
-- Let's consider the following component, which will symbolize that an entity likes another, and by how much:
--
-- @
-- data Likes = Likes 'Int' deriving ('Component')
-- @
--
-- And three spawned entities: @alice@, @bob@, @charlie@.
--
-- As mentioned before, we can insert a relationship using the 'Rel' type.
--
-- @
-- 'insert' ('Rel' (Likes 3) alice, 'Rel' (Likes 5) charlie) bob
-- 'insert' ('Rel' (Likes 2) bob) alice
-- @
--
-- If we insert a second relationship with the same component and the same target, its value will overwrite the other. For instance, the following code
-- will make @alice@ like @bob@ by 3 instead of 2:
--
-- @
-- 'insert' ('Rel' (Likes 3) bob) alice
-- @

-- $removal
-- Removing relationships can be done through the @remove@ function, same as normal components. But instead of using the @'C'@ marker, we will use the @'R'@ marker.
--
-- Making @bob@ stop liking @alice@.
--
-- @
-- 'remove' ('R' \@Likes alice) bob
-- @
--
-- The @'R'@ marker takes a type hint of the relationship's type (@\@Likes@), and a target Entity (@alice@). But it can also be given the @Any@ wildcard instead:
--
-- @
-- 'remove' ('R' \@Likes 'Any') bob
-- @
--
-- This will remove all @Likes@ relationships from @bob@, making him not like anyone.

-- $query
-- Relationships can be queried using the @R@ marker.
--
-- @
-- x <- 'query' ('R' \@Likes alice)
-- @
--
-- @
-- x :: ['Result' ('Rel' Likes)]
-- @
--
-- In quasi-queries, this becomes:
--
-- @
-- x \<- ['q'|Likes -\> alice|]
-- @
--
-- @Result (Rel c)@ is the return type of @R \@c e@. It can be used in most Result-based operation discussed in the previous chapter, such as @'set'@ and @'delete'@.
--
-- Querying can also be done using the @Any@ wildcard:
--
-- @
-- x <- 'query' ('R' \@Likes Any)
-- @
--
-- @
-- x :: [['Result' ('Rel' Likes)]]
-- @
--
-- In quasi-queries, @Any@ is symbolized by @*@:
--
-- @
-- x \<- ['q'|Likes -\> *|]
-- @
--
-- As you can see, the return type of @'R' \@c Any@ is @[Result (Rel c)]@. It's returning a list of relationships, rather than a single relationship (unless the
-- relationship is exclusive, but more on that in a bit).
--
-- Remember that the fields of the inner type of a Result are inherited by the Result itself. So we can just use @.comp@ and @.target@ to get the component and target of a @Result (Rel c)@.
--
-- @
-- 'Just' x <- 'get' ('R' \@Likes alice) bob
-- @
--
-- @
-- x.target :: Entity
-- x.comp   :: Likes
-- @
--
-- In the case of querying for @R c Any@, the query will only match entities that have at least one such relationship. The resulting @[Result (Rel c)]@ should never be empty.
--
-- If you wish to also include entities that do not have those relationships, you can use @`MR`@ (short for @Maybe Relationships@), the relational equivalent of @'M'@.
--
-- @
-- x <- 'query' ('MR' \@Likes alice)
-- @
--
-- @
-- x :: ['Maybe' ('Result' ('Rel' c))]
-- @
--
-- Which is this in quasi form:
--
-- @
-- x <- ['q'|Maybe Likes -> alice|]
-- @

-- $exclusive
-- A relationship can be made exclusive by setting the following associated type on its component instance:
--
-- @
-- instance 'Component' FooRel where
--   type 'RelExclusivity' FooRel = 'Exclusive'
-- @
--
-- This will make it so only one instance of a relationship can exist on an entity at once.
--
-- @
-- insert (Rel (FooRel, a)) c
-- insert (Rel (FooRel, b)) c
-- @
--
-- Will result in just @Rel (FooRel, b)@ being on @c@.
--
-- It also changes the result of @R \@FooRel Any@ queries to be of the form:
--
-- @
-- 'Result ('Rel' FooRel)
-- @
--
-- Instead of:
--
-- @
-- ['Result ('Rel' FooRel)]
-- @

-- $trans
-- Transitive queries are a powerful primitive which allow us to easily query components based on relational connections.
--
-- For instance, this is how we can get the name of each entity, along with the names of all entities they like:
--
-- @
-- x <- 'query' ('C' \@Name, 'R' \@Likes ('Q' ('C' \@Name)))
-- @
--
-- @
-- x :: [(Result Name, [Result Name])]
-- @
--
-- They /tend/ to look much better when written as quasi-queries (don't forget the @()@!):
--
-- @
-- x \<- ['q'|Name, Likes -\> (Name)]
-- @
--
-- Note that transitive queries can be nested as much as you want:
--
-- @
-- x \<- ['q'|Name, Likes -\> (Name, Likes -\> (Name))|]
-- @
--
-- @
-- x :: [(Result Name, [(Result Name, [Result Name])])]
-- @

-- $hooks
-- There are a number of predefined hooks that are useful when working with relationships, which can be found in "Mischief.ECS.Hooks".
--
-- For instance, @relComplementary@ can be used to automate adding a complementary relationship on the target of a relationship.
--
-- As a quick example of why this is useful, let's create a @Before@/@After@ relationship between entities:
--
-- @
-- data Before = Before
-- data After = After
--
-- instance 'Component' Before where
--   'hooks' = 'relComplementary' ('const' After)
--
-- instance 'Component' After where
--   'hooks' = 'relComplementary' ('const' Before)
-- @
--
-- Now, when we do:
--
-- @
-- 'insert' ('Rel' Before a) b
-- @
--
-- A @Rel After b@ will be inserted automatically on @a@.
--
-- And when we do:
--
-- @
-- 'remove' ('R' \@Before) b
-- @
--
-- @Rel After b@ will be removed from @a@.
--
-- And vice versa.
--
-- There are also @'relCleanupRemove'@ and @'relCleanupDespawn'@ for automatically removing a relationship (or ,respectively, despawning its entity) when its target has been despawned. And a
-- more generic @'relCleanup'@ which allows adding custom cleanup behavior.

-- The 'WithR' query filter lets us easily query for components of entities that have a certain relationship with a certain entity.
--
-- Getting a list of all entities that like bob.
--
-- @
-- x <- 'query'' 'E' ('WithR' @Likes bob)
-- @
--
-- Getting a list of all entities that like anyone.
--
-- @
-- x <- 'query'' 'E' ('WithR' @Likes Any)
-- @
--
-- We can also modify @Likes@ to have an @Int@ as well, representing how much an entity likes another:
--
-- @
-- data Likes = Likes 'Int' deriving ('Component')
-- @
--
-- @
-- 'insert' ('Rel' (Likes 5, alice), 'Rel' (Likes 8, charlie)) bob
-- @
--
-- The 'R' marker type can be used in a query to get a @['Result' ('Rel' c)]@ for each entity.
--
-- Getting the name and all the Likes relationships of all entities.
--
-- @
-- x <- query (C \@Name, 'R' \@Likes Any)
-- @
--
-- @
-- x :: [('Result' Name, ['Result' ('Rel' Likes)])]
-- @
--
--
-- Getting the name and the Like relationship with bob for all entities.
--
-- @
-- x <- query (C \@Name, 'R' \@Likes bob)
-- @
--
-- @
-- x :: [('Result' Name, 'Result' ('Rel' Likes))]
-- @
--
-- Note that @'R' \@Likes@ will limit the query to only the archetypes that contain any relation with @Likes@.
-- You can also use @'MR'@ (Maybe relationship) to also include the entities that don't contain such relationships.
--
-- A component can be made @exclusive@ by setting the following 'Bool' in the 'Component' instance:
--
-- @
-- instance 'Component' Likes where
--   'isExclusiveRel' = 'True'
-- @
--
-- If a component is exclusive, there can only be one relationship containing it on an entity at once.
--
-- For instance, if we do:
--
-- @
-- 'insert' ('Rel' (Likes, alice) bob
-- 'insert' ('Rel' (Likes, charlie)) bob
-- @
--
-- @(Likes, charlie)@ will overwrite @(Likes, alice)@.
--
-- This is useful for relationships such as 'ChildOf', since an entity can only have one parent at a time.

-- $examples
--
-- === __Example 1__
--
--
-- An example showing different operations on relationships, and the @relCleanupRemove@ hook.
--
-- @
-- import "Mischief.ECS.Prelude"
-- import "Mischief.ECS.Hooks" qualified as [Hooks]("Mischief.ECS.Hooks")
-- import "Mischief.ECS.Systems" qualified as [Systems]("Mischief.ECS.Systems")
--
-- data Likes = Likes 'Int' deriving ('Show')
--
-- instance 'Component' Likes where
--   'hooks' = [Hooks]("Mischief.ECS.Hooks").'Mischief.ECS.Hooks.relCleanupRemove'
--
-- main :: 'IO' ()
-- main = do
--   app <- 'newApp' MainPlugin
--   'runApp' app
--
-- data MainPlugin = MainPlugin deriving ('Eq')
--
-- instance 'Plugin' MainPlugin where
--   init _ = [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' test
--
-- test :: 'System' ()
-- test = do
--   alice <- 'spawn' ('Name' \"Alice\")
--   bob <- 'spawn' ('Name' \"Bob\")
--   charlie <- 'spawn' ('Name' \"Charlie\")
--
--   'insert' ('Rel' (Likes 5) alice, 'Rel' (Likes 10) bob) charlie
--   'insert' ('Rel' (Likes 7) alice) bob
--   'insert' ('Rel' (Likes 9) bob, 'Rel' (Likes 5) charlie) alice
--
--   ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes alice)
--   ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes bob)
--   ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes charlie)
--
--   'remove' ('R' \@Likes 'Any') alice
--   'despawn' bob
--
--   ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes alice)
--   ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes bob)
--   ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes charlie)
-- @
--
-- @
-- > [INFO] [(\"Bob\",Rel {comp = Likes 7, target = 28v1}),(\"Charlie\",Rel {comp = Likes 5, target = 28v1})]
-- > [INFO] [(\"Alice\",Rel {comp = Likes 9, target = 29v1}),(\"Charlie\",Rel {comp = Likes 10, target = 29v1})]
-- > [INFO] [(\"Alice\",Rel {comp = Likes 5, target = 30v1})]
--
-- > [INFO] [(\"Charlie\",Rel {comp = Likes 5, target = 28v1})]
-- > [INFO] []
-- > [INFO] []
-- @
--
-- You can see in the second set of prints that nobody likes Bob anymore, since he "died", and the cleanup hook made it so any "Likes -> Bob" relationships were automatically removed.