module Mischief.ECS.World.Modify where

import Data.Maybe
import Mischief.ECS.Components
import Mischief.ECS.Components.Bundle
import Mischief.ECS.Components.Common
import Mischief.ECS.Log
import Mischief.ECS.Prelude
import Mischief.ECS.Tables
import Mischief.ECS.World
import Mischief.ECS.World.Insert
import Mischief.ECS.World.Query
import Mischief.ECS.World.Query.Queryable

-- | Modifies the value of the component with the given function.
--
-- The function will be applied over the @live value@ of the component, adding some overhead.
--
-- If you are confident the value in the 'Result' is the live one, or otherwise do not care of updating the live value,
-- you are encouraged to use 'modify'' instead.
--
-- Note that this will trigger change detection even if the provided function is 'id'.
modify :: forall c i. (Updateable (Result c), Settable (Result c) i, DeepValue (Result c) i) => Result c -> (i -> i) -> System ()
modify :: forall c i.
(Updateable (Result c), Settable (Result c) i,
 DeepValue (Result c) i) =>
Result c -> (i -> i) -> System ()
modify !Result c
result !i -> i
f = do
  res <- Result c -> System (Maybe (Result c))
forall c.
Updateable (Result c) =>
Result c -> System (Maybe (Result c))
update Result c
result
  case res of
    Maybe (Result c)
Nothing -> Text -> System ()
forall w (m :: * -> *).
(HasCallStack, MonadSystem w m) =>
Text -> m ()
warn (Text -> System ()) -> Text -> System ()
forall a b. (a -> b) -> a -> b
$ Text
"Modify failed: Entity " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity -> Text
forall a. Show a => a -> Text
text (Result c -> Entity
forall c. Result c -> Entity
entityOf Result c
result) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" is not alive."
    Just Result c
res -> do
      let v :: i
v = Result c -> i
forall c i. DeepValue c i => c -> i
deepValue Result c
res
      Result c -> i -> System ()
forall c i. Settable c i => c -> i -> System ()
set Result c
result (i -> i
f i
v)

-- | Modifies the value of the component with the given function.
--
-- The function will be applied over the value contained within the 'Result', which is not guaranteed
-- to be the live value of the component.
--
-- If you wish to apply the function over the live value, use 'modify' instead.
--
-- Note that this will trigger change detection even if the provided function is 'id'.
modify' :: forall c i. (Settable (Result c) i, DeepValue (Result c) i) => Result c -> (i -> i) -> System ()
modify' :: forall c i.
(Settable (Result c) i, DeepValue (Result c) i) =>
Result c -> (i -> i) -> System ()
modify' !Result c
result !i -> i
f = do
  let v :: i
v = Result c -> i
forall c i. DeepValue c i => c -> i
deepValue Result c
result
  Result c -> i -> System ()
forall c i. Settable c i => c -> i -> System ()
set Result c
result (i -> i
f i
v)

-- | The most generic function for modifying a component on a given entity.
--
-- This can do removals, insertions, and modify the value.
--
-- Example:
--
-- @
-- data Counter = Counter 'Int' deriving ('Component', 'Queryable')
--
-- incrementCounter :: 'Entity' -> 'System' ()
-- incrementCounter = 'alter' (\case 'Nothing' -> 'Just' $ Counter 0; 'Just' (Counter x) -> 'Just' $ Counter (x + 1))
-- @
alter :: forall c. (Queryable (C c) (Result c), Bundle c, Component c) => (Maybe c -> Maybe c) -> Entity -> System ()
alter :: forall c.
(Queryable (C c) (Result c), Bundle c, Component c) =>
(Maybe c -> Maybe c) -> Entity -> System ()
alter !Maybe c -> Maybe c
f !Entity
entity = do
  val <- C c -> Entity -> System (Maybe (Result c))
forall qd (m :: * -> *) w out.
(Queryable qd out, MonadSystem w m) =>
qd -> Entity -> m (Maybe out)
get (forall a. C a
forall {k} (a :: k). C a
C @c) Entity
entity
  let r = Maybe c -> Maybe c
f ((Result c -> c) -> Maybe (Result c) -> Maybe c
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Result c -> c
forall c. Result c -> c
value Maybe (Result c)
val)

  if isNothing r && isJust val
    then
      remove (C @c) entity
    else case r of
      Just c
r ->
        c -> Entity -> System ()
forall b. (HasCallStack, Bundle b) => b -> Entity -> System ()
forall b. Bundle b => b -> Entity -> System ()
insert c
r Entity
entity
      Maybe c
Nothing -> () -> System ()
forall a. a -> System a
forall (m :: * -> *) a. Monad m => a -> m a
return ()