{-# LANGUAGE AllowAmbiguousTypes #-}

module Mischief.ECS.App.Systems where

import Control.Monad.IO.Class
import Data.Foldable
import Data.IORef
import Data.Map (Map)
import Data.Map qualified as Map
import GHC.StableName (StableName, eqStableName, hashStableName, makeStableName)
import GHC.Stack.Types
import Mischief.ECS.App.Schedules
import Mischief.ECS.App.SystemDef
import Mischief.ECS.Components
import Mischief.ECS.Components.Required
import Mischief.ECS.Entities
import Mischief.ECS.Resources
import Mischief.ECS.Utils
import Mischief.ECS.World
import Mischief.ECS.World.Query
import Mischief.ECS.World.Query.Markers
import Mischief.ECS.World.Spawn

newtype Systems = Systems
  { Systems
-> IORef
     (Map
        (ScheduleId, Int) (IORef [(StableName (System ()), SystemId)]))
systemMap :: IORef (Map (ScheduleId, Int) (IORef [(StableName (System ()), SystemId)]))
  }
  deriving anyclass (Typeable Systems
[HookRel Systems]
[Hook Systems]
Set DefaultComponentType
IsExclusive (IsExclusiveRel Systems)
(Typeable Systems, IsExclusive (IsExclusiveRel Systems)) =>
Set DefaultComponentType
-> [Hook Systems]
-> [Hook Systems]
-> [Hook Systems]
-> [HookRel Systems]
-> [HookRel Systems]
-> [HookRel Systems]
-> Component Systems
forall c.
(Typeable c, IsExclusive (IsExclusiveRel c)) =>
Set DefaultComponentType
-> [Hook c]
-> [Hook c]
-> [Hook c]
-> [HookRel c]
-> [HookRel c]
-> [HookRel c]
-> Component c
$crequired :: Set DefaultComponentType
required :: Set DefaultComponentType
$conAdd :: [Hook Systems]
onAdd :: [Hook Systems]
$conSet :: [Hook Systems]
onSet :: [Hook Systems]
$conRemove :: [Hook Systems]
onRemove :: [Hook Systems]
$conAddRel :: [HookRel Systems]
onAddRel :: [HookRel Systems]
$conSetRel :: [HookRel Systems]
onSetRel :: [HookRel Systems]
$conRemoveRel :: [HookRel Systems]
onRemoveRel :: [HookRel Systems]
Component)

newtype SystemFunction = SystemFunction {SystemFunction -> System ()
inner :: System ()}

instance Component SystemFunction where
  required :: Set DefaultComponentType
required = forall b. RequiredBundle b => Set DefaultComponentType
forall {k} (b :: k). RequiredBundle b => Set DefaultComponentType
require @(SystemTick, LastSystemTick)

newSystems :: IO Systems
newSystems :: IO Systems
newSystems = do
  systemMap <- Map (ScheduleId, Int) (IORef [(StableName (System ()), SystemId)])
-> IO
     (IORef
        (Map
           (ScheduleId, Int) (IORef [(StableName (System ()), SystemId)])))
forall a. a -> IO (IORef a)
newIORef Map (ScheduleId, Int) (IORef [(StableName (System ()), SystemId)])
forall k a. Map k a
Map.empty
  return Systems {systemMap}

getSystemId' :: (HasCallStack) => ScheduleId -> System () -> StableName (System ()) -> IORef [(StableName (System ()), SystemId)] -> System SystemId
getSystemId' :: HasCallStack =>
ScheduleId
-> System ()
-> StableName (System ())
-> IORef [(StableName (System ()), SystemId)]
-> System SystemId
getSystemId' ScheduleId
schedule System ()
system StableName (System ())
stableName IORef [(StableName (System ()), SystemId)]
list = do
  list' <- IO [(StableName (System ()), SystemId)]
-> System [(StableName (System ()), SystemId)]
forall a. IO a -> System a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [(StableName (System ()), SystemId)]
 -> System [(StableName (System ()), SystemId)])
-> IO [(StableName (System ()), SystemId)]
-> System [(StableName (System ()), SystemId)]
forall a b. (a -> b) -> a -> b
$ IORef [(StableName (System ()), SystemId)]
-> IO [(StableName (System ()), SystemId)]
forall a. IORef a -> IO a
readIORef IORef [(StableName (System ()), SystemId)]
list
  case find (\(StableName (System ()), SystemId)
x -> (StableName (System ()), SystemId) -> StableName (System ())
forall a b. (a, b) -> a
fst (StableName (System ()), SystemId)
x StableName (System ()) -> StableName (System ()) -> Bool
forall a b. StableName a -> StableName b -> Bool
`eqStableName` StableName (System ())
stableName) list' of
    Just (StableName (System ())
_, SystemId
x) -> SystemId -> System SystemId
forall a. a -> System a
forall (m :: * -> *) a. Monad m => a -> m a
return SystemId
x
    Maybe (StableName (System ()), SystemId)
Nothing -> do
      index <- (SystemFunction, Rel ScheduledIn) -> System Entity
forall b. (HasCallStack, Bundle b) => b -> System Entity
spawn (System () -> SystemFunction
SystemFunction System ()
system, ScheduledIn -> Entity -> Rel ScheduledIn
forall c. c -> Entity -> Rel c
Rel ScheduledIn
ScheduledIn ScheduleId
schedule.id)
      liftIO $ modifyIORef' list (++ [(stableName, SystemId index)])
      return $ SystemId index

getSystemId :: (HasCallStack) => ScheduleId -> System () -> System SystemId
getSystemId :: HasCallStack => ScheduleId -> System () -> System SystemId
getSystemId ScheduleId
sch System ()
system = do
  Systems {systemMap} <- Maybe Systems -> Systems
forall a. HasCallStack => Maybe a -> a
unwrap (Maybe Systems -> Systems)
-> System (Maybe Systems) -> System Systems
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (forall c. Component c => System (Maybe c)
res @Systems)

  stableName <- liftIO $ makeStableName system
  systemMap' <- liftIO $ readIORef systemMap

  case Map.lookup (sch, hashStableName stableName) systemMap' of
    Just IORef [(StableName (System ()), SystemId)]
x -> HasCallStack =>
ScheduleId
-> System ()
-> StableName (System ())
-> IORef [(StableName (System ()), SystemId)]
-> System SystemId
ScheduleId
-> System ()
-> StableName (System ())
-> IORef [(StableName (System ()), SystemId)]
-> System SystemId
getSystemId' ScheduleId
sch System ()
system StableName (System ())
stableName IORef [(StableName (System ()), SystemId)]
x
    Maybe (IORef [(StableName (System ()), SystemId)])
Nothing -> do
      l <- IO (IORef [(StableName (System ()), SystemId)])
-> System (IORef [(StableName (System ()), SystemId)])
forall a. IO a -> System a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (IORef [(StableName (System ()), SystemId)])
 -> System (IORef [(StableName (System ()), SystemId)]))
-> IO (IORef [(StableName (System ()), SystemId)])
-> System (IORef [(StableName (System ()), SystemId)])
forall a b. (a -> b) -> a -> b
$ [(StableName (System ()), SystemId)]
-> IO (IORef [(StableName (System ()), SystemId)])
forall a. a -> IO (IORef a)
newIORef []
      liftIO $ modifyIORef' systemMap (Map.insert (sch, hashStableName stableName) l)
      getSystemId' sch system stableName l

removeSystemFromMap' :: StableName (System ()) -> IORef [(StableName (System ()), SystemId)] -> System ()
removeSystemFromMap' :: StableName (System ())
-> IORef [(StableName (System ()), SystemId)] -> System ()
removeSystemFromMap' StableName (System ())
stableName IORef [(StableName (System ()), SystemId)]
list = do
  IO () -> System ()
forall a. IO a -> System a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> System ()) -> IO () -> System ()
forall a b. (a -> b) -> a -> b
$ IORef [(StableName (System ()), SystemId)]
-> ([(StableName (System ()), SystemId)]
    -> [(StableName (System ()), SystemId)])
-> IO ()
forall a. IORef a -> (a -> a) -> IO ()
modifyIORef' IORef [(StableName (System ()), SystemId)]
list (([(StableName (System ()), SystemId)]
  -> [(StableName (System ()), SystemId)])
 -> IO ())
-> ([(StableName (System ()), SystemId)]
    -> [(StableName (System ()), SystemId)])
-> IO ()
forall a b. (a -> b) -> a -> b
$ ((StableName (System ()), SystemId) -> Bool)
-> [(StableName (System ()), SystemId)]
-> [(StableName (System ()), SystemId)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(StableName (System ()), SystemId)
x -> Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (StableName (System ()), SystemId) -> StableName (System ())
forall a b. (a, b) -> a
fst (StableName (System ()), SystemId)
x StableName (System ()) -> StableName (System ()) -> Bool
forall a b. StableName a -> StableName b -> Bool
`eqStableName` StableName (System ())
stableName)

removeSystemFromMap :: ScheduleId -> System () -> System ()
removeSystemFromMap :: ScheduleId -> System () -> System ()
removeSystemFromMap ScheduleId
sch System ()
system = do
  Systems {systemMap} <- Maybe Systems -> Systems
forall a. HasCallStack => Maybe a -> a
unwrap (Maybe Systems -> Systems)
-> System (Maybe Systems) -> System Systems
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (forall c. Component c => System (Maybe c)
res @Systems)

  stableName <- liftIO $ makeStableName system
  systemMap' <- liftIO $ readIORef systemMap

  forM_
    (Map.lookup (sch, hashStableName stableName) systemMap')
    (removeSystemFromMap' stableName)

systemEntity :: forall sch. (HasCallStack, Schedule sch) => System () -> System Entity
systemEntity :: forall {k} (sch :: k).
(HasCallStack, Schedule sch) =>
System () -> System Entity
systemEntity System ()
s = do
  schId <- forall (sch :: k). Schedule sch => System Entity
forall {k} (sch :: k). Schedule sch => System Entity
scheduleEntity @sch
  x <- getSystemId (ScheduleId schId) s
  return x.id

self :: forall m w. (MonadSystem w m) => m Entity
self :: forall (m :: * -> *) w. MonadSystem w m => m Entity
self = do
  world <- m World
forall w (m :: * -> *). MonadSystem w m => m World
unsafeGetWorld
  let (SystemId sys) = world.systemId
  return sys

data ScheduledIn = ScheduledIn deriving (Typeable ScheduledIn
[HookRel ScheduledIn]
[Hook ScheduledIn]
Set DefaultComponentType
IsExclusive (IsExclusiveRel ScheduledIn)
(Typeable ScheduledIn, IsExclusive (IsExclusiveRel ScheduledIn)) =>
Set DefaultComponentType
-> [Hook ScheduledIn]
-> [Hook ScheduledIn]
-> [Hook ScheduledIn]
-> [HookRel ScheduledIn]
-> [HookRel ScheduledIn]
-> [HookRel ScheduledIn]
-> Component ScheduledIn
forall c.
(Typeable c, IsExclusive (IsExclusiveRel c)) =>
Set DefaultComponentType
-> [Hook c]
-> [Hook c]
-> [Hook c]
-> [HookRel c]
-> [HookRel c]
-> [HookRel c]
-> Component c
$crequired :: Set DefaultComponentType
required :: Set DefaultComponentType
$conAdd :: [Hook ScheduledIn]
onAdd :: [Hook ScheduledIn]
$conSet :: [Hook ScheduledIn]
onSet :: [Hook ScheduledIn]
$conRemove :: [Hook ScheduledIn]
onRemove :: [Hook ScheduledIn]
$conAddRel :: [HookRel ScheduledIn]
onAddRel :: [HookRel ScheduledIn]
$conSetRel :: [HookRel ScheduledIn]
onSetRel :: [HookRel ScheduledIn]
$conRemoveRel :: [HookRel ScheduledIn]
onRemoveRel :: [HookRel ScheduledIn]
Component)

getSystemTicks :: World -> IO (Tick, Tick)
getSystemTicks :: World -> IO (Tick, Tick)
getSystemTicks World
world = do
  let (SystemId Entity
sys) = World
world.systemId
  System (Tick, Tick) -> World -> IO (Tick, Tick)
forall a. System a -> World -> IO a
runSystem
    ( do
        Just (a, b) <- Query System (LastSystemTick, SystemTick)
-> System (Maybe (LastSystemTick, SystemTick))
forall w (m :: * -> *) out.
MonadSystem w m =>
Query m out -> m (Maybe out)
single (Query System (LastSystemTick, SystemTick)
 -> System (Maybe (LastSystemTick, SystemTick)))
-> Query System (LastSystemTick, SystemTick)
-> System (Maybe (LastSystemTick, SystemTick))
forall a b. (a -> b) -> a -> b
$ Entity
-> (C LastSystemTick, C SystemTick)
-> Query System (LastSystemTick, SystemTick)
forall qd out (m :: * -> *).
Queryable qd out =>
Entity -> qd -> Query m out
mkGet Entity
sys (forall a. C a
forall {k} (a :: k). C a
C @LastSystemTick, forall a. C a
forall {k} (a :: k). C a
C @SystemTick)
        return (a.inner, b.inner)
    )
    World
world