{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE RecordWildCards #-}

{- |
Simple data structure that can act as a physical world to simulate.
I will likely implement more interesting world data structures in the future.
-}
module Physics.World where

import GHC.Generics (Generic)

import Control.DeepSeq
import Control.Lens
import qualified Data.IntMap.Strict as IM

import Physics.World.Class
import Utils.Utils

-- | A simple 'PhysicsWorld' implementation using 'IM.IntMap'
data World a =
  World { _worldObjs :: !(IM.IntMap a) -- ^ Inhabitants by unique 'Int' key
        , _worldNextKey :: !Int -- ^ Key to use for the next new inhabitant
        } deriving (Show, Generic, NFData)
makeLenses ''World

-- | A 'World' without any inhabitants.
emptyWorld :: World a
emptyWorld = World IM.empty 0
{-# INLINE emptyWorld #-}

-- | Add a new inhabitant to the 'World'
addObj :: World a -> a -> World a
addObj w = snd . addObj' w
{-# INLINE addObj #-}

-- | Add a new inhabitant to the 'World'. Also, get inhabitant's 'Int' key.
addObj' :: World a -> a -> (Int, World a)
addObj' w o = (n, w & worldObjs %~ IM.insert n o & worldNextKey .~ n + 1)
  where n = w ^. worldNextKey
{-# INLINE addObj' #-}

-- | Create a 'World' from a list of inhabitants
fromList :: [a] -- ^ Population for the new 'World'
         -> World a
fromList = foldl addObj emptyWorld
{-# INLINE fromList #-}

instance (Contactable a) => PhysicsWorld Int (World a) a where
  wKeys = IM.keys . _worldObjs
  wObj k = worldObjs . ix k
  wPair k = worldObjs . pairix k
  wObjs = worldObjs . itraversed