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

-- |
-- Module: Components Tutorial
-- Description: Introductory Tutorial
--
-- This module walks the user through setting up Mischief and creating a simple app.
--
-- [Next Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Dungeon")
--
-- [Main Page]("Mischief.ECS")
module Mischief.ECS.Tutorial.Startup
  ( -- * Learn You an ECS for Great Mischief! - 1. Startup Guide
    -- $intro

    -- * What do I need to know?
    -- $know

    -- * Setup
    -- $setup

    -- * Text
    -- $text

    -- * The ECS
    -- $ecs

    -- * The App
    -- $app

    -- * Your First System
    -- $firstSystem

    -- * Your First Component
    -- $firstComp

    -- * Your First Query
    -- $firstQuery

    -- * Your First Mutation
    -- $firstMut

    -- * Your First Resource
    -- $res

    -- * Your First Relationship
    -- $rel

    -- * Your First Transitive Query
    -- $trans

    -- * Explanation: From
    -- $from

    -- * What's Next?
    -- $next

    -- * [Next Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Dungeon")
  )
where

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

-- $intro
-- This chapter will guide you through setting up a working Mischief app and performing some simple operations on data.

-- $know
-- This book doesn't assume any knowledge of other game engines or programming paradigms, but it does expect some Haskell knowledge.
--
-- While it is possible to read this book and get a pretty good idea of what Mischief is and how it works, you'll have a much better time
-- if you have at least a very basic understanding of Haskell syntax.

-- $setup
-- In order to use Mischief, you'll first need to install @GHC@ and @cabal@. You can follow [this](https://www.haskell.org/cabal/) quick-start guide in order to do that.
--
-- After you have a new project set up, just add @mischief-ecs@ under @build-depends@ in you @.cabal@ file.
--
-- We recommend using @GHC2024@ as the language standard (set in your @.cabal@ file).
--
-- == Language Extensions
-- We generally recommend using the following language extensions in a Mischief project:
--
-- @
-- DeriveAnyClass
-- DuplicateRecordFields
-- NoFieldSelectors
-- DerivingStrategies
-- OverloadedRecordDot
-- MultiWayIf
-- OverloadedStrings
-- QuasiQuotes
-- RequiredTypeArguments
-- TypeFamilyDependencies
-- @
--
-- @QuasiQuotes@ and @OverloadedStrings@ are especially important because some Mischief features are not available without them (namely quasi-queries and logging).
-- The rest of the extensions are highly optional.
--
-- You can paste these extensions in the @default-extensions@ field of your @.cabal@ file.

-- $text
-- Mischief uses @Text@ instead of @String@ where possible, including in its logging system.
-- The 'i' macro from @string-interpolate@ is re-exported by Mischief and will be often used for logging in this tutorial.
--
-- Sometimes functions from @Data.Text@ are used as well (such as @T.show@), so it's recommended to add @text@ as a dependency to your project and to qualify it when you want to use it:
--
-- @
-- import "Data.Text" qualified as T
-- @

-- $ecs
-- Mischief's ECS logic is designed to be very approachable and simple to write.
--
-- @Components@ are just types deriving the @Component@ typeclass.
--
-- @
-- data Position = Position {x :: 'Float', y :: 'Float'} deriving ('Component')
-- @
--
-- @Systems@ are functions in the @System@ monad.
--
-- @
-- updatePositions :: 'System' ()
-- updatePosition =
--   ['q'|Position, Velocity|]
--     & 'qinsert' (\(Position p, Velocity v) -> (Position p + v))
--     & 'query_'
-- @
--
-- @Entities@ are opaque ids used to represent and manipulate data.
--
-- @
-- data Entity = Entity 'Int'
-- @

-- $app
-- A Mischief program usually starts with creating an App and adding a plugin to it. So let's do that!
--
-- @
-- import "Mischief.ECS.Prelude"
--
-- main :: 'IO' ()
-- main = do
--   app <- 'newApp'
--   'addPlugin' \@MyPlugin app
--   'runApp' app
--
-- data MyPlugin
--
-- instance 'Plugin' MyPlugin
-- @

-- $firstSystem
-- Copy the following function into your file:
--
-- @
-- helloWorld :: 'System' ()
-- helloWorld = 'info' "Hello World!"
-- @
--
-- This will be our first system. It just logs a message saying /"Hello World!"/. The only remaining step is to schedule it to run!
--
-- @
-- instance 'Plugin' MyPlugin where
--   'Mischief.ECS.App.Plugins.init' :: 'System' ()
--   'Mischief.ECS.App.Plugins.init' = do
--      'systems' helloWorld
--        & 'schedule' \@Update
-- @
--
-- The @systems@ function will grab the system for us, and @schedule \@Update@ will add it to the Update schedule,
-- making it run once per frame. If you run your app again, you will see \"Hello World!\" printed to your terminal many, many times.

-- $firstComp
-- Let's do a little more than greeting the whole world, let's greet some individual people!
--
-- In ECS, you would generally model people as entities with a set of components that define them. Let's start with a simple @Person@ component:
--
-- @
-- data Person = Person deriving ('Component')
-- @
--
-- So how can we give people names? In a more traditional design you could just add a @name :: String@ field to @Person@. But the ECS makes you think of it differently!
-- A @Name@ is just a piece of data that can be attached to anything. A dog could also have a name. So why not just make a @Name@ component?
--
-- @
-- data Name = Name 'String' deriving ('Component')
-- @
--
-- No need to write this one though, since this exact @Name@ is already defined internally by Mischief and exported by the Prelude.
--
-- Now that we can represent people with names, let's make a system that spawns some:
--
-- @
-- addPeople :: 'System' ()
-- addPeople = do
--   kim <- 'spawn' (Person, Name \"Kimberly\")
--   nick <- 'spawn' (Person, Name \"Nicholas\")
--   flo <- 'spawn' (Person, Name \"Florian\")
--   'pure' ()
-- @
--
-- You can register it to run on the app's Startup schedule, making it run only once, at the start:
--
-- @
-- instance 'Plugin' MyPlugin where
--   init = do
--     'systems' helloWorld
--       & 'schedule' \@Update
--
--     'systems' addPeople
--       & 'schedule' \@Startup
-- @

-- $firstQuery
-- If you run your app, the people will be spawned but we aren't doing anything with them yet! Let's make a system that greets them:
--
-- @
-- greetPeople :: 'System' ()
-- greetPeople = do
--   people <- 'query' [q|Name, Person|]
--   'for_' people $ \\(name, _) -> do
--     'info' ['i'|Hello #{name}!|]
-- @
--
-- The above @query@ function will grab the @Name@ and @Person@ of every entity. We then iterate over them in order to greet them.
--
-- The @Person@ component however, is only queried to ensure we are querying the right entities. We don't care about its value at all! So we can instead write it
-- as a filter to limit the types of entities selected by the query and save us the trouble of carrying an extra variable around.
--
-- @
-- greetPeople :: 'System' ()
-- greetPeople = do
--   people <- 'query' [q|Name / With Person|]
--   'for_' people $ \\name ->
--     'info' ['i'|Hello #{name}!|]
-- @
--
-- @With Person@ is a filter, telling our query builder to only select entities with the @Person@ component. We use @/@ to separate
-- the data that we're querying from the filters.
--
-- Additionally, Mischief lets you write and process queries by piping dedicated functions into each other. For instance, our earlier function is equivalent to:
--
-- @
-- greetPeople :: 'System' ()
-- greetPeople = do
--   ['q'|Name / With Person|]
--     & 'qinfo' (\\name -> ['i'|Hello #{name}!|])
--     & 'query_'
-- @
--
-- The quasi-query (@[q|..|]@) produces our query, we then use @qinfo@ to display a message to the terminal for each element in the query,
-- and finally we use @query_@ to run all the commands and discard the results (the normal @query@ returns the results).
--
-- Now we can schedule this system to also run:
--
-- @
-- instance 'Plugin' MyPlugin where
--   init = do
--     'systems' (helloWorld, greePeople)
--       & 'schedule' \@Update
--
--     'systems' addPeople
--       & 'schedule' \@Startup
-- @
--
-- Running our app will result in the following output:
--
-- @
-- [INFO] Hello World!
-- [INFO] Hello Kimberly!
-- [INFO] Hello Nicholas!
-- [INFO] Hello Florian!
-- @
--
-- Note that \"Hello World\" might show above or beneath the others. That's because systems in the same schedule can run in any order unless they are explicitly ordered.

-- $firstMut
-- If we want to change the name of some people, we can apply a mutation to a value obtained from the query:
--
-- @
-- updateFlo :: 'System' ()
-- updateFlo = do
--   people <- 'query' ['q'|Entity, Name / With Person|]
--   'for_' people $ \\(entity, name) -> do
--     'when' (name == Name \"Florian\") $
--       'insert' (Name \"Florianne\") entity
-- @
--
-- We are querying the name of each entity (@Name@), along with its actual id (@Entity@). We then iterate over all the names, and once we see \"Florian\", we
-- re-insert the component, changing its value to \"Florianne\". Re-insertion is the main way to mutate data in Mischief.
--
-- Although.. that feels awfully imperative doesn't it? Let's rewrite the same system, this time using piping:
--
-- @
-- updateFlo :: 'System' ()
-- updateFlo = do
--   ['q'|Name / With Person|]
--     & 'qfilter' (== Name \"Florian\")
--     & 'qinsert' (\\_ -> Name \"Florianne\")
--     & 'query_'
-- @
--
-- This time we apply a filter over our Query, leaving only those entities with their name set to \"Florian\". We then use @qinsert@ to
-- map the old name to the new one and insert it on the entity.
--
-- Let's add the new system to a schedule:
--
-- @
-- 'Mischief.ECS.App.Plugins.init' = do
--    'systems' (helloWorld, greetPeople)
--      & 'schedule' \@Update
--
--    'systems' addPeople
--      & 'schedule' \@Startup
--
--    'systems' updateFlo
--      & 'before' greetPeople
--      & 'schedule' \@Update
-- @
--
-- Note that we have explicitly ordered @updateFlo@ to happen /before/ @greetPeople@. We want to only greet Flo after their name has changed! Running the app
-- should now show \"Hello Florianne!\" instead of \"Florian\".

-- $res
-- Resources are a great way to store global information that can be easily written to and read in any system.
--
-- Let's say we want to have a custom greeting that we can change at runtime. We could store that in a resource:
--
-- @
-- data Greeting = Greeting 'String' deriving ('Component')
-- @
--
-- Yes, resources are just normal components!
--
-- We'll also give it a @Show@ instance to make printing it easier:
--
-- @
-- instance 'Show' Greeting where
--   'show' (Greeting a) = a
-- @
--
-- Let's insert a greeting from our init system:
--
-- @
-- 'Mischief.ECS.App.Plugins.init' = do
--    'systems' (helloWorld, greetPeople)
--      & 'schedule' \@Update
--
--    'systems' addPeople
--      & 'schedule' \@Startup
--
--    'systems' updateFlo
--      & 'before' greetPeople
--      & 'schedule' \@Update
--
--   'insertRes' (Greeting \"Hey\")
-- @
--
-- @insertRes@ inserts the corresponding resource into the World.
--
-- And let's modify @greetPeople@ so that it uses the current greeting from the resource:
--
-- @
-- greetPeople :: 'System' ()
-- greetPeople = do
--   'Just' greeting <- 'res' \@Greeting
--
--   ['q'|Name / With Person|]
--     & 'qinfo' (\\name -> ['i'|#{greeting} #{name}!|])
--     & 'query_'
-- @
--
-- You should now see this when running the app:
--
-- @
-- [INFO] Hello World!
-- [INFO] Hey Kimberly!
-- [INFO] Hey Nicholas!
-- [INFO] Hey Florianne!
-- @

-- $rel
-- Relationships in Mischief are pairs made up of a Component and an Entity. Let's implement a simple relationship between our entities that says which like which.
--
-- We'll start by defining a component:
--
-- @
-- data Likes = Likes deriving ('Component')
-- @
--
-- Let's now modify our spawning system to also insert relationships between our three entities. We can insert a relationship using the @Rel@ keyword.
--
-- @
-- addPeople :: 'System' ()
-- addPeople = do
--   kim <- 'spawn' (Person, Name \"Kimberly\")
--   nick <- 'spawn' (Person, Name \"Nicholas\")
--   flo <- 'spawn' (Person, Name \"Florian\")
--
--   'insert' ('Rel' Likes kim) flo
--   'insert' ('Rel' Likes nick, 'Rel' Likes flo) kim
-- @
--
-- We've now made @flo@ like @kim@, and we've made @kim@ like both @nick@ and @flo@!.

-- $trans
-- We now have relationships but we aren't doing much with them. What about having a system that displays the name of each entity, along with the name of all entities they like?
--
-- We can make use of a mechanism called a @transitive query@, which look like this:
--
-- @
-- showLikes :: 'System' ()
-- showLikes = do
--   ['q'|Name, Likes -\> (Name)|]
--     & 'qinfo' (\\(name, likes) -\> [i|#{name} likes #{likes}|])
--     & 'query_'
-- @
--
-- Pretty cool, huh?
--
-- Now let's schedule our new system to run:
--
-- @
-- 'Mischief.ECS.App.Plugins.init' = do
--    'systems' (helloWorld, greetPeople, showLikes)
--      & 'schedule' \@Update
--
--    'systems' addPeople
--      & 'schedule' \@Startup
--
--    'systems' updateFlo
--      & 'before' (greetPeople, showLikes)
--      & 'schedule' \@Update
--
--   'insertRes' (Greeting \"Hey\")
-- @
--
-- We should now see these additional lines printed to the terminal:
--
-- @
-- [INFO] Florianne likes [From (42v1, Kimberly)]
-- [INFO] Kimberly likes [From (44v1, Nicholas),From (45v1, Florianne)]
-- @

-- $from
-- You may have noticed earlier, when we query for @-> (Name)@ and then print the names, we don't get the actual names, but instead
-- something that looks like @From (42v1, Kimberly)@.
--
-- That's because, when doing transitive queries, the components come wrapped in this:
--
-- @
-- data From c = From {comp :: c, entity :: Entity}
-- @
--
-- They have a different origin entity than the other components in the query, and this is our main way of keeping track of that.
--
-- To get rid of the wrapper we can just change the query to this:
--
-- @
-- showLikes :: 'System' ()
-- showLikes = do
--   ['q'|Name, Likes -\> (Name)|]
--     & 'qinfo' (\\(name, likes) -\> [i|#{name} likes #{'map' (.comp) likes}|])
--     & 'query_'
-- @
--
-- You can find more information on this in the [Components Chapter]("Mischief.ECS.Tutorial.Components").

-- $next
-- What you learn next is up to you.
--
-- The [next chapter]("Mischief.ECS.Tutorial.Dungeon") will have you working on a little dungeon game in the terminal and introduce you to more notions. If you prefer to learn by example I would recommended
-- checking that out.
--
-- Then there's a [chapter]("Mischief.ECS.Tutorial.HowTo") which goes over different situations and problems you may encounter and describes various solutions to them.
--
-- After that, there are chapters giving you a technical overview for various parts of the ECS (Components, Queries, Systems, and so on).