{-# 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
    -- $intro

    -- * Insertion
    -- $insertion

    -- * Removal
    -- $removal

    -- * Querying
    -- $query

    -- * Transitive Querying
    -- $trans

    -- * Exclusivity
    -- $exclusive

    -- * Hooks
    -- $hooks

    -- * [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' $ 'mkQuery' ('R' \@Likes alice)
-- @
--
-- @
-- x :: ['Rel' Likes]
-- @
--
-- In quasi-notation, this becomes:
--
-- @
-- x \<- 'query' ['q'|Likes -\> alice|]
-- @
--
-- Querying can also be done using the @Any@ wildcard:
--
-- @
-- x \<- 'query' $ 'mkQuery' ('R' \@Likes Any)
-- @
--
-- @
-- x :: [['Rel' Likes]]
-- @
--
-- In quasi-queries, @Any@ is symbolized by @*@:
--
-- @
-- x \<- 'query' ['q'|Likes -\> *|]
-- @
--
-- As you can see, the return type of @'R' \@c Any@ is @[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).
-- In the case of querying for @R c Any@, the query will only match entities that have at least one such relationship. The resulting @[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 \<- ['q'|Maybe Likes -\> alice|]
-- @
--
-- @
-- x :: ['Maybe' ('Result' ('Rel' c))]
-- @
--
-- @'HasR'@ (or just \"Has\"" in quasi-notation) is also supported as the relational equivalent of @'Has'@.
--
-- Filter-wise, relationships allow the same archetype filters as normal components, through the @R@ marker.
-- This is how we get the name of all entities that like alice:
--
-- @
-- x \<- 'query' $ 'mkQuery'' ('C' \@Name) ('With' ('R' \@Likes alice))
-- @
--
-- @
-- x \<- 'query' ['q'|Name / With Likes -\> alice|]
-- @
--
-- This is how we get the name of all entities that like at least one other entity:
--
-- @
-- x \<- 'query' $ 'mkQuery'' ('C' \@Name) ('With' ('R' \@Likes Any))
-- @
--
-- @
-- x \<- 'query' ['q'|Name / With Likes -\> *|]
-- @

-- $exclusive
-- A relationship can be made exclusive by setting the following associated Bool on its component instance:
--
-- @
-- instance 'Component' FooRel where
--   type 'IsExclusiveRel' FooRel = 'True'
-- @
--
-- 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@ and @R \@FooRel (Q (...))@ queries to return a single item rather than a list.

-- $trans
-- Transitive queries are a powerful primitive which allow us to easily query components based on relationships between them.
--
-- For instance, this is how we can get the name of each entity, along with the names of all entities they like:
--
-- @
-- x \<- 'query' $ 'mkQuery' ('C' \@Name, 'R' \@Likes ('Q' ('C' \@Name)))
-- @
--
-- @
-- x :: [(Name, [Name])]
-- @
--
-- They /tend/ to look much better when written as quasi-queries (don't forget the @()@!):
--
-- @
-- x \<- 'query' ['q'|Name, Likes -\> (Name)]
-- @
--
-- Note that transitive queries can be nested indefinitely:
--
-- @
-- x \<- query ['q'|Name, Likes -\> (Name, Likes -\> (Name))|]
-- @
--
-- @
-- x :: [(Name, [(Name, [Name])])]
-- @
--
-- Same as querying for @R \@Likes Any@ (@Likes -> *@), entities that don't have any entity matching the relationship will be ignored by the query.

-- $hooks
-- Relationship support dedicated hooks, named @onAddRel@, @onSetRel@, @onRemoveRel@, following the same rules as the normal hooks.
--
-- This is how we can log a message each time @Likes@ is added between two entities:
--
-- @
-- instance 'Component' Likes where
--   onAddRel = ['hookRel' onLikesAdd]
--
-- onLikesAdd :: 'HookContextRel' -> 'System' ()
-- onLikesAdd HookContextRel {entity, target} = 'info' ['i'|#{entity} now likes #{target}!|]
-- @
--
-- There are a number of predefined hooks that are useful when working with relationships, which can be found in "Mischief.ECS.HooksRel".
-- For instance, @addOther@ 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
--   onAddRel =  [HooksRel.'Mischief.ECS.HooksRel.addOther' ('const' After)]
--
-- instance 'Component' After where
--   'hooks' = [HooksRel.'Mischief.ECS.HooksRel.addOther' ('const' Before)]
-- @
--
-- Now, when we do:
--
-- @
-- 'insert' ('Rel' Before a) b
-- @
--
-- A @Rel After b@ will be inserted automatically on @a@.
--
-- And vice versa.
--
-- There are other interesting hooks, such as ones for automatically cleaning up a relationship when an entity is despawned. Check out "Mischief.ECS.HooksRel" for details!

-- $exclusive
-- 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.