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

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

    -- * The Name Component
    -- $name

    -- * Operations
    -- $ops

    -- * Querying
    -- $query

    -- * Change Detection
    -- $change

    -- * Meta Components
    -- $meta

    -- * Resources
    -- $resources

    -- * From
    -- $from

    -- * Required Components
    -- $required

    -- * Registering Components
    -- $reg

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

import Control.Monad (void)
import Data.Default (Default (def))
import Data.Foldable
import GHC.Generics (Generic)
import GHC.Records (HasField)
import Mischief.ECS

-- $introduction
-- A component is any type which derives the 'Component' typeclass. They can be both carriers of data or marker components used for querying (Tags from Flecs):
--
-- Component that carries data.
--
-- @
-- data Health = Health 'Int' deriving ('Component')
-- @
--
-- Marker components.
--
-- @
-- data Player = Player deriving ('Component')
-- data Enemy = Enemy deriving ('Component')
-- @

-- $name
-- @Name@ is a special component provided by Mischief that is internally added to every spawned entity, based on its @Entity@ index,
-- if none is provided on spawn. It can be, of course, changed at any time.
--
-- @
-- newtype Name = Name 'String' deriving ('Component')
-- @
--
-- Consider this system that prints the name of a given Entity:
--
-- @
-- printName :: 'Entity' -> 'System' ()
-- printName e = 'info' . T.'Data.Text.show' '=<<' 'get' e ['q'|Name|]
-- @
--
-- Notice how the Names behave here:
--
-- @
-- foo <- 'spawn' ()
-- printName foo
--
-- insert ('Name' \"Foo\") foo
-- printName foo
--
-- bar <- 'spawn' ('Name' \"Bar\")
-- printName bar
-- @
--
-- @
-- >> [INFO] Just \"Entity 15v1\"
-- >> [INFO] Just \"Foo\"
-- >> [INFO] Just \"Bar\"
-- @

-- $ops
-- Mischief offers various operations for inserting and manipulating data into the World:
--
-- * You can spawn entities as bundles of components:
--
-- @
-- player <- 'spawn' (Name \"Player\", Player)
-- @
--
-- * You can insert components on existing entities:
--
-- @
-- 'insert' (Name \"New Player Name\", Health 100)
-- @
--
-- * You can remove components:
--
-- @
-- 'remove' ('C' \@Health, 'C' \@Player) player
-- @
--
-- * You can despawn entities:
--
-- @
-- 'despawn' player
-- @
--
-- Additionally, @insert@ has a couple of variants:
--
-- * @'insertNew'@ only inserts components that aren't already on the entity.
-- * @'insertIfNeq'@ only insert components if they aren't on the entity of if their value differs from the current one.
--
-- Note that, unlike other ECS's, Mischief exposes an immutable API to the user. This means that component values cannot be
-- mutated in any other way, other than re-inserting them to change their previous value.

-- $meta
-- Each component has a corresponding entity in the World.
-- The components on that entity store information about the component itself. Such as which archetypes it is part of.
--
-- A component's entity can be accessed by using @meta@.
--
-- Getting the entities of the @Name@ and @Player@ components:
--
-- @
-- x <- 'meta' \@Name
-- y <- 'meta' \@Player
-- @
--
-- Most users should avoid tinkering with Meta Components unless they have a good reason to,
-- and should absolutely never remove or change any components added to them by the @ECS@.

-- $query
-- Components can be queried using the @'C'@, @'M'@, and @'Has'@ markers.
--
-- @'C'@ simply returns the component, and makes the query ignore all entities that don't have it:
--
-- @
-- x \<- 'query' $ 'mkQuery' ('C' \@Name, 'C' \@Health)
-- @
--
-- @
-- x \<- ['q'|Name, Health|]
-- @
--
-- @
-- x :: [(Name, Health)]
-- @
--
-- @'M'@ is short for @Maybe@ and makes the component optional. It may return Nothing, and the query will also include entities that don't have it.
--
-- @
-- x \<- 'query' $ 'mkQuery' ('C' \@Name, 'M' \@Health)
-- @
--
-- @
-- x \<- ['q'|Name, Maybe Health|]
-- @
--
-- @
-- x :: [(Name, 'Maybe' Health)]
-- @
--
-- @'Has'@ is similar to @'M'@ but it will return a bool saying whether the component is present or not.
--
-- @
-- x \<- 'query' $ 'mkQuery' ('C' \@Name, 'Has' \@Health)
-- @
--
-- @
-- x \<- ['q'|Name, Has Health]
-- @
--
-- @
-- x :: [(Name, 'Bool')]
-- @
--
-- Additionally, the @mkQuery'@ function (or @q@ quasi-quoter, with a separating @\/@) accepts an expression of @With@ / @Without@ filters that limit the entities looked at by the query.
--
-- Getting the name of all entities that are @Player@ and either don't have @Enemy@ or have @Health@:
--
-- @
-- x \<- 'query' $ 'mkQuery'' ('C' \@Name) ('With' ('C' \@Player) '`And`' ('Without' ('C' \@Enemy) '`Or`' 'With' ('C' \@Health)))
-- @
--
-- Or
--
-- @
-- x \<- 'query' ['q'|Name / With Player, (Without Enemy || With Health)|]
-- @
--
-- Note that these filters limit the /archetypes/ that the query will look at, rather than filtering the entities themselves, making them significantly faster than other filters.

-- $resources
-- @Resources@ are singleton components that can be easily accessed and modified from any system.
--
-- Any component can be used as a resource.
--
-- @
-- data MyRes = MyRes 'Int' deriving ('Component')
-- @
--
-- You can insert a resource into the World using @insertRes@.
--
-- @
-- 'insertRes' $ MyRes 5
-- @
--
-- And you can query for the value of a resource using @res@:
--
-- @
-- 'Just' myRes <- 'res' \@MyRes
-- @
--
-- @res r@ returns a @'Maybe' r@ because it's possible for the resource to not have been inserted yet.
--
-- Resources are implemented by inserting a component's value on its own meta entity.
--
-- @
-- 'res' \@MyRes
-- @
--
-- Is equivalent to:
--
-- @
-- m <- 'meta' \@MyRes
-- 'get' m [q|MyRes|]
-- @
--
-- Besides using @insertRes@, resources can be inserted as part of a bundle through the @Res@ type.
--
-- The following will spawn an entity, insert a @Name@ on it, and additioanlly insert a resource into the world.
--
-- @
-- a <- 'spawn' ()
-- 'insert' (Name "A", 'Res' $ SomeRes 5) a
-- @
--
-- It's equivalent to:
--
-- @
-- a <- 'spawn' ()
-- 'insert' (Name \"A\") a
-- 'insertRes' (SomeRes 5)
-- @
--
-- @Res@ can also be used in queries to grab a resource:
--
-- @
-- 'mkQuery' ('C' \@Name, 'Res' \@SomeRes)
-- @
--
-- Or
--
-- @
-- ['q'|Name, Res SomeRes|]
-- @
--
-- This will query the name of all entities, and attach @Res SomeRes@ to all of them.

-- $from
-- From is a special type in Mischief:
--
-- @
-- data From c = From {entity :: 'Entity', comp :: c}
-- @
--
-- It symbolizes the idea of a foreign component. A component belonging to an external entity that we store alongside it.
--
-- From can be inserted in any bundle, inserting the component @c@ on the entity stored /inside/ it. For instance, the following code
-- will insert the name \"B2\" on @b@ and the name \"A2\" on @a@:
--
-- @
-- a \<- 'spawn' (Name \"A\")
-- b \<- 'spawn' (Name \"B\")
--
-- 'insert' (Name \"B2\", 'From' a (Name \"A2\")) b
-- @
--
-- Equivalent to:
--
-- @
-- a \<- 'spawn' (Name \"A\")
-- b \<- 'spawn' (Name \"B\")
--
-- 'insert' (Name \"B2\") b
-- 'insert' (Name \"A2\") a
-- @
--
-- From is generally returned from traversal queries such as:
--
-- @
-- x \<- 'query' ['q'|Name, ChildOf -\> (Name)|]
-- @
--
-- @
-- x :: [Name, From Name]
-- @
--
-- Where @Name@ will be the name of each entity and @From Name@ will be the name of their parent.

-- $required
-- Each component can @require@ a bundle of other components.
--
-- @
-- data Player = Player
--
-- instance 'Component' Player where
--   'required' = 'require' \@(Position, Health)
-- @
--
-- This means that each time @Player@ is added to an entity, a /default/ @Position@ and @Health@ will also be inserted, if they aren't already present.
--
-- In order for a component to be required by another, it must instance the @Default@ typeclass, either through a @Generic@ derive or a custom instance.
--
-- @
-- data Position = Position 'Int' 'Int' deriving ('Component', 'Generic', 'Default')
-- @
--
-- @
-- data Health = Health 'Int' deriving ('Component')
--
-- instance 'Default' Health where
--   'def' = Health 100
-- @
--
-- Requirements are transitive (if @A requires B@ and @B requires C@, then @A requires C@) and /can/ contain cycles.
--
-- A requirement is added to the ECS as a @'RequiredBy'@ \/ @'Requires'@ relationship between the components' meta entities.

-- $reg
-- @Registering@ a component involves spawning its meta entity and adding the corresponding data.
--
-- Each component is registered automatically the first time it is inserted on an Entity, so you don't usually
-- have to worry about registration.
--
-- Queries are also smart about components; if you query or filter for a component that hasn't been registered yet, they will just
-- assume that component can't be be on any Entity. Queries can't perform registration themselves, because they're not allowed to mutate
-- the world in any way.
--
-- However, there may be /extremely/ niche situations where you want to register components earlier than normal, which is where manual registration comes in:
--
-- @
-- 'register' \@(Player, Health, Position)
-- @
--
-- One such situation could be wanting to check the requirements between multiple components on runtime.
-- If a component hasn't been registered yet, it won't show up when you query for components that require a specific component.

-- $change
-- Change detection can be done in two ways: @Observers@ and @Filters@.
--
-- === Observers
--
-- Observers can listen to the @OnAdd@, @OnSet@, and @OnRemove@ event:
--
-- * @OnAdd c@ is triggered when the component c is added to an entity that didn't previously have it.
--
-- @
-- onNameAdd :: 'OnAdd' 'Name' -> 'System' ()
-- @
--
-- * @OnSet c@ is triggered each time @c@ is inserted on an entity, whether it's for the first time or it's a
-- re-insertion to change its value.
--
-- @
-- onNameSet :: 'OnInsert' 'Name' -> 'System' ()
-- @
--
-- * @OnRemove@ is triggered when a component is removed from an entity.
--
-- @
-- onNameRemove :: 'OnRemove' 'Name' -> 'System' ()
-- @
--
-- All of these events have a @.entity@ field you can use to obtain the entity which the event was triggered on.
--
-- @
-- onNameRemove :: 'OnRemove' 'Name' -> 'System' ()
-- onNameRemove event = 'info' $ T.'Data.Text.show' event.entity <> \" had their name removed!\"
-- @
--
-- Don't forget to spawn an Observer to listen for each event.
--
-- @
-- 'void' $ 'spawn' ('Observer' onNameAdd)
-- 'void' $ 'spawn' ('Observer' onNameRemove)
-- @
--
-- The order these events are triggered in is also an important detail:
--
-- * /After/ a component has been added, @OnAdd@ is triggered, followed by @OnSet@.
-- * /After/ a component has been re-inserted, @OnSet@ is triggered.
-- * /Before/ a component is removed, @OnRemove@ is triggered.
--
-- You can find more details on Observers and Events in the corresponding [Chapter]("Mischief.ECS.Tutorial.Events").
--
-- === Filters
--
-- Now for the other way of reacting to changes: the @Changed@ and @Added@ filters.
--
-- With the following query we can query the name of all entities that have had the @Player@ component added in the last frame:
--
-- @
-- 'mkQuery' ('C' \@Name)
--   & 'qcheck' ('Added' ('C' \@Player))
--   & 'query'
-- @
--
-- Or
--
-- @
-- ['q'|Name|]
--   & 'qcheck' ['f'|Added Player|]
--   & 'query'
-- @
--
-- @Added c@ will catch entities that just had @c@ added to them, while @Changed c@ will catch any insertion.
-- If you wish to query for entities that have had a component changed but it wasn't just added, you can do:
--
-- @
-- 'mkQuery' ('C' \@Name)
--   & 'qcheck' ('Changed' ('C' \@Player) '`And`' (`Not` ('Added' ('C' \@Player))))
--   & 'query'
-- @
--
-- Or
--
-- @
-- ['q'|Name|]
--   & 'qcheck' ['f'|Changed Player, !Added Player|]
--   & 'query'
-- @
--
-- === Note on listening to changes
--
-- One essential detail to be aware of here is that insertion (@OnSet@ or @Changed@) doesn't necessarily mean a component's value has been changed!
--
-- The following @insert@ /will/ trigger change detection:
--
-- @
-- p <- 'spawn' (Health 100)
-- 'insert' (Health 100) p
-- @
--
-- To avoid this, you can derive 'Eq' on your components and use @'insertIfNeq'@, which will only perform insertion if the value of the component is different
-- from the current one.

-- $hooks
-- @Component hooks@ are events associated directly to a Component instance.
--
-- @
-- instance 'Component' Foo where
--   onAdd = [hook onAddFoo]
--   onSet = [hook onSetFoo]
--   onRemove = [hook onRemoveFoo]
--
-- onAddFoo :: 'HookContext' -> 'System' ()
-- onAddFoo = ...
--
-- onSetFoo :: 'HookContext' -> 'System' ()
-- onSetFoo = ...
--
-- onRemoveFoo :: 'HookContext' -> 'System' ()
-- onRemoveFoo = ...
-- @
--
-- Similar to change events, @HookContext@ has a @.entity@ field.
--
-- The @onAdd@ and @ohSet@ hooks will always run before @OnAdd@ and @OnSet@ events on that component.
-- @onRemove@ hooks will always run after @OnRemove@ events.