{-# LANGUAGE ScopedTypeVariables, MagicHash, ExistentialQuantification #-}
-- Copyright (c) Jean-Philippe Bernardy 2005-2007.

module Yi.Dynamic 
 (
  Initializable(..),
  toDyn, fromDynamic, dynamicValueA, emptyDV,
  Typeable, Dynamic, DynamicValues
 )
  where

import Prelude ()
import Yi.Prelude

import GHC.Exts
import Data.Accessor
import Data.Maybe
import Data.Typeable
import Data.Map as M
-- ---------------------------------------------------------------------
-- | Class of values that can go in the extensible state component
--


-- | The default value. If a function tries to get a copy of the state, but the state
--   hasn't yet been created, 'initial' will be called to supply *some* value. The value
--   of initial will probably be something like Nothing,  \[\], \"\", or 'Data.Sequence.empty' - compare 
--   the 'mempty' of "Data.Monoid".
class (Typeable a) => Initializable a where
    initial :: a

-- Unfortunately, this is not serializable: there is no way to recover a type from a TypeRep.
data Dynamic = forall a. Initializable a => Dynamic a

-- | An extensible record, indexed by type
type DynamicValues = M.Map String Dynamic

toDyn :: Initializable a => a -> Dynamic
toDyn = Dynamic

fromDynamic :: forall a. Typeable a => Dynamic -> Maybe a
fromDynamic (Dynamic b) = if typeOf (undefined :: a) == typeOf b then Just (unsafeCoerce# b) else Nothing

instance (Typeable a) => Initializable (Maybe a) where
    initial = Nothing

-- | Accessor for a dynamic component
dynamicValueA :: Initializable a => Accessor DynamicValues a
dynamicValueA = accessor getDynamicValue setDynamicValue
    where
      setDynamicValue :: forall a. Initializable a => a -> DynamicValues -> DynamicValues
      setDynamicValue v = M.insert (show $ typeOf (undefined::a)) (toDyn v)

      getDynamicValue :: forall a. Initializable a => DynamicValues -> a
      getDynamicValue dv = case M.lookup (show $ typeOf (undefined::a)) dv of
                             Nothing -> initial
                             Just x -> fromJust $ fromDynamic x

-- | The empty record
emptyDV :: DynamicValues
emptyDV = M.empty