-- | Ecstasy is a library architected around the
-- <http://reasonablypolymorphic.com/blog/higher-kinded-data/ HKD pattern>, the
-- gist of which is to define a "template" type that can be reused for several
-- purposes. Users of ecstasy should define a record type of 'Component's
-- parameterized over a variable of kind 'StorageType':
--
-- @
--   data World s = Entity
--     { position :: 'Component' s ''Field' (V2 Double)
--     , graphics :: 'Component' s ''Field' Graphics
--     , isPlayer :: 'Component' s ''Unique' ()
--     }
--     deriving ('Generic')
-- @
--
-- Ensure that this type have an instance of 'Generic'.
--
-- For usability, it might be desirable to also define the following type
-- synonym:
--
-- @
--   type Entity = World ''FieldOf'
-- @
--
-- which is the only form of the @World@ that most users of ecstasy will
-- need to interact with.
--
-- Throughout this document there are references to the @HasWorld@ and
-- @HasWorld'@ classes, which are implementation details and provided
-- automatically by the library.
module Data.Ecstasy
  (
  -- * Defining components
  -- $components
    ComponentType (..)
  , Component

  -- * Storage
  -- $world
  , defStorage
  , StorageType (..)

  -- * The SystemT monad
  -- $systemt
  , SystemT ()
  , runSystemT
  , yieldSystemT
  , System
  , runSystem
  , SystemState

  -- * Working with SystemT
  , createEntity
  , newEntity
  , getEntity
  , setEntity
  , deleteEntity

  -- * SystemT traversals
  -- $traversals
  , emap
  , efor
  , eover
  , unchanged
  , delEntity

  -- * Entity targets
  , EntTarget
  , allEnts
  , someEnts
  , anEnt

  -- * The QueryT monad
  -- $queryt
  , QueryT ()
  , runQueryT

  -- * Queries
  -- $querying
  , query
  , with
  , without
  , queryEnt
  , queryMaybe
  , queryFlag
  , queryDef

  -- * Updates
  , Update (..)
  , maybeToUpdate

  -- * Miscellany
  , Ent ()
  , VTable (..)

  -- * Re-exports
  , Generic ()
  ) where

import Data.Ecstasy.Internal hiding (HasWorld, HasWorld')
import Data.Ecstasy.Types
import GHC.Generics



-- $components
-- Components are pieces of data that may or may not exist on a particular
-- entity. In fact, an 'Ent' is nothing more than an identifier, against which
-- components are linked.
--
-- Components classified by their 'ComponentType', which describes the
-- semantics behind a component.
--
-- [@Field@]  A 'Field' is a "normal" component and corresponds exactly to
-- a 'Maybe' value.
--
-- [@Unique@] A 'Unique' component may only exist on a single entity at a given
-- time. They are often used to annotate "notable" entites, such as whom the
-- camera should be following.
--
-- [@Virtual@] A 'Virtual' component is defined in terms of monadic 'vget' and
-- 'vset' actions, rather than having dedicated storage in the ECS. Virtual
-- components are often used to connect to external systems, eg. to a 3rd party
-- physics engine which wants to own its own data. For more information on
-- using virtual components, see <#world defStorage>.
--
--



-- $world
-- #world# 'defStorage' provides a suitable container for storing entity data, to
-- be used with 'runSystemT' and friends. If you are not using any 'Virtual'
-- components, it can be used directly.
--
-- However, when using 'Virtual' components, the 'VTable' for each must be set
-- on 'defStorage' before being given as a parameter to 'runSystemT'. For
-- example, we can write a virtual 'String' component that writes its updates
-- to stdout:
--
-- @
--   data World s = Entity
--     { stdout :: 'Component' s ''Virtual' String
--     }
--     deriving ('Generic')
--
--  main :: IO ()
--  main = do
--    let storage = 'defStorage'
--          { stdout = 'VTable'
--              { 'vget' = \\_   -> pure Nothing
--              , 'vset' = \\_ m -> for_ m putStrLn
--              }
--          }
--    'runSystemT' storage $ do
--      void $ 'createEntity' 'unchanged
--        { stdout = Just "hello world"
--        }
-- @
--
-- In this example, if you were to use 'defStorage' rather than @storage@ as the
-- argument to 'runSystemT', you would receive the following error:
--
-- @unset VTable for Virtual component \'stdout\'@



-- $systemt
-- The 'SystemT' transformer provides capabilities for creating, modifying,
-- reading and deleting entities, as well as performing <#traversals query
-- traversals> over them. It is the main monad of ecstasy.



-- $queryt
-- The 'QueryT' transformer provides an environment for <#traversals querying>
-- components of an entity. Due to its 'Control.Monad.MonadPlus' instance,
-- failing queries will prevent further computations in the monad from running.



-- $traversals
-- #traversals# 'SystemT' provides functionality for traversing over entities
-- that match a 'EntTarget' and a <#querying query>. The functions 'emap' and
-- 'eover' return a @world 'SetterOf@, corresponding to partial update of the
-- targeted entity.
--
-- A @world 'SetterOf@ is the world record where all of its selectors have the
-- type @'Update' a@. For example, given a world:
--
-- @
--   data World s = Entity
--     { position :: 'Component' s ''Field' (V2 Double)
--     , graphics :: 'Component' s ''Field' Graphics
--     , isPlayer :: 'Component' s ''Unique' ()
--     }
-- @
--
-- then @World 'SetterOf@ is equivalent to the following definition:
--
-- @
--   data World 'SetterOf = Entity
--     { position :: 'Update' (V2 Double)
--     , graphics :: 'Update' Graphics
--     , isPlayer :: 'Update' ()
--     }
-- @
--
-- 'unchanged' provides a @world 'SetterOf@ which will update no components,
-- and can have partial modifications added to it.
--
-- 'delEntity' provides a @world 'SetterOf@ which will delete all components
-- associated with the targeted entity.



-- $querying
-- #querying# The 'QueryT' monad provides functionality for performing
-- computations over an 'Ent''s components. The basic primitive is 'query',
-- which will pull the value of a component, and fail the query if it isn't
-- set.
--
-- For example, given the following world:
--
-- @
--   data World s = Entity
--     { position :: 'Component' s ''Field' (V2 Double)
--     , velocity :: 'Component' s ''Field' (V2 Double)
--     }
--     deriving ('Generic')
-- @
--
-- we could model a discrete time simulation via:
--
-- @
--   stepTime :: 'System' World ()
--   stepTime = do
--     'emap' 'allEnts' $ do
--       pos <- 'query' position
--       vel <- 'query' velocity
--       pure $ 'unchanged'
--         { position = 'Set' $ pos + vel
--         }
-- @
--
-- which will add an entity's velocity to its position, so long as it has both
-- components to begin with.