-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Pass.Env
-- Copyright   :  (C) 2012-2013 Edward Kmett
-- License     :  BSD-style (see the file LICENSE)
--
-- Maintainer  :  Edward Kmett <ekmett@gmail.com>
-- Stability   :  experimental
-- Portability :  non-portable (GADTs, Rank2Types)
--
----------------------------------------------------------------------------
module Data.Pass.Env
  ( Env
  , empty
  , lookup
  , insert
  , cons
  ) where

import Control.Applicative hiding (empty)
import Data.Binary
import qualified Data.HashMap.Strict as HashMap
import Data.HashMap.Strict (HashMap)
import Data.Monoid (Monoid(..))
import Data.Typeable
import GHC.Prim (Any)
import Prelude hiding (lookup)
import Unsafe.Coerce
import Data.Pass.Call
import Data.Pass.Key
import Data.Pass.Named
import Data.Pass.Thrist

newtype Id a = Id { getId :: a }

instance Functor Id where
  fmap f (Id a) = Id (f a)

instance Applicative Id where
  pure = Id
  Id a <*> Id b = Id (a b)

mapWithKey :: (k -> a -> b) -> HashMap k a -> HashMap k b
mapWithKey f m = getId (HashMap.traverseWithKey (\k a -> Id (f k a)) m)

newtype Env k a = Env (HashMap (Key k a) Any)

data Fake = Any deriving Show

instance Named k => Show (Env k a) where
  showsPrec d (Env m) = showParen (d > 10) $
    showString "Env " . showsPrec 10 (Any <$ m)

empty :: Env k a
empty = Env HashMap.empty

lookup :: (Call k, Typeable b, Binary b, Monoid b) => Thrist k a b -> Env k a -> Maybe b
lookup k (Env m) = unsafeCoerce <$> HashMap.lookup (Key k) m

insert :: (Call k, Typeable b, Binary b, Monoid b) => Thrist k a b -> b -> Env k a -> Env k a
insert k v (Env m) = Env $ HashMap.insert (Key k) (unsafeCoerce v) m

cons :: Call k => a -> Env k a -> Env k a
cons a (Env m) = Env $ mapWithKey (\(Key k) old -> unsafeCoerce $ call k a `mappend` unsafeCoerce old) m
