{-# LANGUAGE AllowAmbiguousTypes #-}

module Mischief.ECS.HooksRel
  ( addOther,
    removeOther,
    addDespawnCleaner,
    addRemoveCleaner,
    addCleaner,
    removeCleaner,
  )
where

import Data.Kind
import Mischief.ECS.Components
import Mischief.ECS.Components.HooksDef
import Mischief.ECS.EntityDef
import Mischief.ECS.Hooks
import Mischief.ECS.World
import Mischief.ECS.World.Insert
import Mischief.ECS.World.Query
import Mischief.ECS.World.Query.Markers
import Mischief.ECS.World.Remove
import Mischief.ECS.World.Spawn

-- | Relationship Hook for @Component a@.
--
-- Takes a @a -> b@. If @a@ is a relationship from @x@ to @y@, this hook will add the corresponding @b@ from @y@ to @x@.
--
-- Example usage:
--
-- @
-- data After = After
-- data Before = Before
--
-- instance 'Component' Before where
--   onAddRel = [addOther ('const' After)]
--
-- instance 'Component' After where
--   onAddRel = [addOther ('const' Before)]
-- @
addOther :: forall (a :: Type) b. (Component a, Component b) => (a -> b) -> HookRel a
addOther :: forall a b. (Component a, Component b) => (a -> b) -> HookRel a
addOther a -> b
f = (HookContextRel -> System ()) -> HookRel a
forall {k} (a :: k). (HookContextRel -> System ()) -> HookRel a
hookRel ((HookContextRel -> System ()) -> HookRel a)
-> (HookContextRel -> System ()) -> HookRel a
forall a b. (a -> b) -> a -> b
$ (a -> b) -> HookContextRel -> System ()
forall a b.
(Component b, Component a) =>
(a -> b) -> HookContextRel -> System ()
insertComplementary a -> b
f

-- | Relationship Hook for @Component a@.
--
-- Removes @b@ from the target entity of a relationship.
--
-- Example usage:
--
-- @
-- data Likes = Likes
-- data LikedBy = LikedBy deriving (Component)
--
-- instance 'Component' Likes where
--   onAddRel = [addOther ('const' Likes)]
--   onRemoveRel = [removeOther \@Likes]
-- @`
removeOther :: forall a b. (Component b) => HookRel a
removeOther :: forall {k} (a :: k) b. Component b => HookRel a
removeOther = (HookContextRel -> System ()) -> HookRel a
forall {k} (a :: k). (HookContextRel -> System ()) -> HookRel a
hookRel ((HookContextRel -> System ()) -> HookRel a)
-> (HookContextRel -> System ()) -> HookRel a
forall a b. (a -> b) -> a -> b
$ forall b. Component b => HookContextRel -> System ()
removeComplementary @b

insertComplementary :: forall (a :: Type) b. (Component b, Component a) => (a -> b) -> HookContextRel -> System ()
insertComplementary :: forall a b.
(Component b, Component a) =>
(a -> b) -> HookContextRel -> System ()
insertComplementary a -> b
f HookContextRel
event = do
  Just val <- Query System (Rel a) -> System (Maybe (Rel a))
forall w (m :: * -> *) out.
MonadSystem w m =>
Query m out -> m (Maybe out)
single (Query System (Rel a) -> System (Maybe (Rel a)))
-> Query System (Rel a) -> System (Maybe (Rel a))
forall a b. (a -> b) -> a -> b
$ Entity -> R a Entity -> Query System (Rel a)
forall qd out (m :: * -> *).
Queryable qd out =>
Entity -> qd -> Query m out
mkGet HookContextRel
event.entity (forall {k} (a :: k) b. b -> R a b
forall a b. b -> R a b
R @a HookContextRel
event.target)
  insert (Rel (f val.comp) event.entity) event.target

removeComplementary :: forall b. (Component b) => HookContextRel -> System ()
removeComplementary :: forall b. Component b => HookContextRel -> System ()
removeComplementary HookContextRel
event = R b Entity -> Entity -> System ()
forall c. Collectable c ToRemove => c -> Entity -> System ()
remove (forall {k} (a :: k) b. b -> R a b
forall a b. b -> R a b
R @b HookContextRel
event.entity) HookContextRel
event.target

addCleaner :: forall (a :: Type). (Component a) => (CleanupRequest -> System ()) -> HookRel a
addCleaner :: forall a. Component a => (CleanupRequest -> System ()) -> HookRel a
addCleaner CleanupRequest -> System ()
f = (HookContextRel -> System ()) -> HookRel a
forall {k} (a :: k). (HookContextRel -> System ()) -> HookRel a
hookRel ((HookContextRel -> System ()) -> HookRel a)
-> (HookContextRel -> System ()) -> HookRel a
forall a b. (a -> b) -> a -> b
$ forall c.
Component c =>
(CleanupRequest -> System ()) -> HookContextRel -> System ()
insertCleanupWatcher @a CleanupRequest -> System ()
f

-- | Relationship Hook for @Component a@.
--
-- Adds a cleaner that will remove this relationship if the target is despawned.
--
-- Note that one component can have only one cleaner at a time.
addRemoveCleaner :: forall (a :: Type). (Component a) => HookRel a
addRemoveCleaner :: forall a. Component a => HookRel a
addRemoveCleaner = forall a. Component a => (CleanupRequest -> System ()) -> HookRel a
addCleaner @a (\CleanupRequest
r -> forall c. Component c => Entity -> Entity -> System ()
removeRel @a CleanupRequest
r.target CleanupRequest
r.entity)

-- | Relationship Hook for @Component a@.
--
-- Adds a cleaner that will despawn this relationship's entity if the target of the relationship is despawned.
--
-- Note that one component can have only one cleaner at a time.
addDespawnCleaner :: forall (a :: Type). (Component a) => HookRel a
addDespawnCleaner :: forall a. Component a => HookRel a
addDespawnCleaner = forall a. Component a => (CleanupRequest -> System ()) -> HookRel a
addCleaner @a (\CleanupRequest
r -> Entity -> System ()
despawn CleanupRequest
r.entity)

insertCleanupWatcher :: forall c. (Component c) => (CleanupRequest -> System ()) -> HookContextRel -> System ()
insertCleanupWatcher :: forall c.
Component c =>
(CleanupRequest -> System ()) -> HookContextRel -> System ()
insertCleanupWatcher CleanupRequest -> System ()
f HookContextRel
e = forall b. (HasCallStack, Bundle b) => b -> Entity -> System ()
Rel (CleanupWatcher c) -> Entity -> System ()
forall b. Bundle b => b -> Entity -> System ()
insert (CleanupWatcher c -> Entity -> Rel (CleanupWatcher c)
forall c. c -> Entity -> Rel c
Rel (forall c. (CleanupRequest -> System ()) -> CleanupWatcher c
forall {k} (c :: k).
(CleanupRequest -> System ()) -> CleanupWatcher c
CleanupWatcher @c CleanupRequest -> System ()
f) HookContextRel
e.entity) HookContextRel
e.target

-- | Relationship Hook for @Component a@.
--
-- Removes the cleaner associated to this component, if any.
removeCleaner :: forall (a :: Type). (Component a) => HookRel a
removeCleaner :: forall a. Component a => HookRel a
removeCleaner = (HookContextRel -> System ()) -> HookRel a
forall {k} (a :: k). (HookContextRel -> System ()) -> HookRel a
hookRel (forall b. Component b => HookContextRel -> System ()
removeCleanupWatcher @a)

removeCleanupWatcher :: forall c. (Component c) => HookContextRel -> System ()
removeCleanupWatcher :: forall b. Component b => HookContextRel -> System ()
removeCleanupWatcher HookContextRel
e = do
  forall b. (HasCallStack, Bundle b) => b -> Entity -> System ()
Rel (CleanupWatcher c) -> Entity -> System ()
forall b. Bundle b => b -> Entity -> System ()
insert (CleanupWatcher c -> Entity -> Rel (CleanupWatcher c)
forall c. c -> Entity -> Rel c
Rel (forall c. (CleanupRequest -> System ()) -> CleanupWatcher c
forall {k} (c :: k).
(CleanupRequest -> System ()) -> CleanupWatcher c
CleanupWatcher @c (() -> System ()
forall a. a -> System a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (() -> System ())
-> (CleanupRequest -> ()) -> CleanupRequest -> System ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. () -> CleanupRequest -> ()
forall a. a -> CleanupRequest -> a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ())) HookContextRel
e.entity) HookContextRel
e.target
  forall c. Component c => Entity -> Entity -> System ()
removeRel @(CleanupWatcher c) HookContextRel
e.entity HookContextRel
e.target

newtype CleanupWatcher c = CleanupWatcher {forall {k} (c :: k).
CleanupWatcher c -> CleanupRequest -> System ()
function :: CleanupRequest -> System ()}

data CleanupRequest = CleanupRequest
  { CleanupRequest -> Entity
entity :: Entity,
    CleanupRequest -> Entity
target :: Entity
  }

instance (Component c) => Component (CleanupWatcher c) where
  onRemoveRel :: [HookRel (CleanupWatcher c)]
onRemoveRel = [(HookContextRel -> System ()) -> HookRel (CleanupWatcher c)
forall {k} (a :: k). (HookContextRel -> System ()) -> HookRel a
hookRel (forall b. Component b => HookContextRel -> System ()
triggerCleanup @c)]

triggerCleanup :: forall c. (Component c) => HookContextRel -> System ()
triggerCleanup :: forall b. Component b => HookContextRel -> System ()
triggerCleanup HookContextRel
e = do
  Just watcher <- Query System (Rel (CleanupWatcher c))
-> System (Maybe (Rel (CleanupWatcher c)))
forall w (m :: * -> *) out.
MonadSystem w m =>
Query m out -> m (Maybe out)
single (Query System (Rel (CleanupWatcher c))
 -> System (Maybe (Rel (CleanupWatcher c))))
-> Query System (Rel (CleanupWatcher c))
-> System (Maybe (Rel (CleanupWatcher c)))
forall a b. (a -> b) -> a -> b
$ Entity
-> R (CleanupWatcher c) Entity
-> Query System (Rel (CleanupWatcher c))
forall qd out (m :: * -> *).
Queryable qd out =>
Entity -> qd -> Query m out
mkGet HookContextRel
e.entity (forall {k} (a :: k) b. b -> R a b
forall a b. b -> R a b
R @(CleanupWatcher c) HookContextRel
e.target)
  watcher.comp.function CleanupRequest {entity = e.target, target = e.entity}