{-# LANGUAGE ExistentialQuantification, ConstraintKinds, KindSignatures, GADTs, ScopedTypeVariables, Rank2Types #-}

module General.TypeMap(
    Map, empty, singleton, insert, map, lookup, unionWith, toList, size
    ) where

import qualified Data.HashMap.Strict as Map
import Data.Typeable
import Unsafe.Coerce
import Data.Functor
import qualified Prelude
import Prelude hiding (lookup, map)


data F f = forall a . F !(f a)

unF :: F f -> f a
unF x = case x of F x -> unsafeCoerce x

newtype Map f = Map (Map.HashMap TypeRep (F f))

empty :: Map f
empty = Map Map.empty

singleton :: Typeable a => f a -> Map f
singleton x = Map $ Map.singleton (typeRep x) (F x)

insert :: Typeable a => f a -> Map f -> Map f
insert x (Map mp) = Map $ Map.insert (typeRep x) (F x) mp

lookup :: forall a f . Typeable a => Map f -> Maybe (f a)
lookup (Map mp) = unF <$> Map.lookup (typeRep (Proxy :: Proxy a)) mp

unionWith :: (forall a . f a -> f a -> f a) -> Map f -> Map f -> Map f
unionWith f (Map mp1) (Map mp2) = Map $ Map.unionWith (\x1 x2 -> F $ f (unF x1) (unF x2)) mp1 mp2

map :: (forall a . f1 a -> f2 a) -> Map f1 -> Map f2
map f (Map mp) = Map $ Map.map (\(F a) -> F $ f a) mp

toList :: (forall a . f a -> b) -> Map f -> [b]
toList f (Map mp) = Prelude.map (\(F a) -> f a) $ Map.elems mp

size :: Map f -> Int
size (Map mp) = Map.size mp