{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- |
-- Module      : Aztecs.ECS.World.Components
-- Copyright   : (c) Matt Hunzinger, 2025
-- License     : BSD-style (see the LICENSE file in the distribution)
--
-- Maintainer  : matt@hunzinger.me
-- Stability   : provisional
-- Portability : non-portable (GHC extensions)
module Aztecs.ECS.World.Components
  ( ComponentID (..),
    Components (..),
    empty,
    lookup,
    insert,
    insert',
  )
where

import Aztecs.ECS.Component
import Aztecs.ECS.World.Components.Internal (Components (..))
import qualified Data.Map.Strict as Map
import Data.Typeable
import Prelude hiding (lookup)

-- | Empty `Components`.
empty :: Components
empty :: Components
empty =
  Components
    { componentIds :: Map TypeRep ComponentID
componentIds = Map TypeRep ComponentID
forall a. Monoid a => a
mempty,
      nextComponentId :: ComponentID
nextComponentId = Int -> ComponentID
ComponentID Int
0
    }

-- | Lookup a component ID by type.
lookup :: forall a. (Typeable a) => Components -> Maybe ComponentID
lookup :: forall a. Typeable a => Components -> Maybe ComponentID
lookup Components
cs = TypeRep -> Map TypeRep ComponentID -> Maybe ComponentID
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Proxy a -> TypeRep
forall a. Typeable a => a -> TypeRep
typeOf (forall t. Proxy t
forall {k} (t :: k). Proxy t
Proxy @a)) (Components -> Map TypeRep ComponentID
componentIds Components
cs)

-- | Insert a component ID by type, if it does not already exist.
insert :: forall a m. (Component m a) => Components -> (ComponentID, Components)
insert :: forall a (m :: * -> *).
Component m a =>
Components -> (ComponentID, Components)
insert Components
cs = case forall a. Typeable a => Components -> Maybe ComponentID
lookup @a Components
cs of
  Just ComponentID
cId -> (ComponentID
cId, Components
cs)
  Maybe ComponentID
Nothing -> forall a (m :: * -> *).
Component m a =>
Components -> (ComponentID, Components)
insert' @a @m Components
cs

-- | Insert a component ID by type.
insert' :: forall c m. (Component m c) => Components -> (ComponentID, Components)
insert' :: forall a (m :: * -> *).
Component m a =>
Components -> (ComponentID, Components)
insert' Components
cs =
  let !cId :: ComponentID
cId = Components -> ComponentID
nextComponentId Components
cs
   in ( ComponentID
cId,
        Components
cs
          { componentIds = Map.insert (typeOf (Proxy @c)) cId (componentIds cs),
            nextComponentId = ComponentID (unComponentId cId + 1)
          }
      )