{-# LANGUAGE ScopedTypeVariables, Safe #-}
module Data.Generics.Record.Reify (reify, reifyMay, reflect) where
import Data.Generics.Record
import Data.Dynamic
import Data.Typeable
import Data.Data
import qualified Data.Map as M
import Data.Map (Map)
import Data.Maybe
import Control.Monad.State
import Control.Applicative

-- | Reify a record to a @Map@
reify :: forall a. Data a => RecordT a -> a -> Map String Dynamic
reify r a = M.fromList . flip zip (gmapQ toDyn a) $ fields r

-- | If @a@ is a record, this will return a @Map@ where the keys
-- are the field names and the values are wrapped in @toDyn@. Otherwise
-- @Nothing@ will be returned.
reifyMay :: forall a. Data a => a -> Maybe (Map String Dynamic)
reifyMay a = fmap (flip reify a) $ (recordT :: Maybe (RecordT a))

-- | Reflect a @Map@ of strings to an arbitrary type. If the type is a record, each of
-- its field names will be looked up in the record. If any of the types don't match
-- or if @a@ isn't a record, @Nothing@ will be returned.
reflect :: forall a. Data a => Map String Dynamic -> Maybe a
reflect vault = result
  where constrs     = dataTypeConstrs . dataTypeOf $ fromJust result
        fs          = (recordT :: Maybe (RecordT a)) >>= mapM (flip M.lookup vault) . fields
        result      = flip evalStateT fs . gmapM popFields . fromConstr . head $ constrs
        popFields _ = (get >>= lift . (fromDynamic . head =<<)) <* modify (fmap tail)