{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}

module Haskus.Utils.Dynamic
   ( -- * Dynamic
     module Data.Dynamic
   -- * Dynamic with equality
   , DynEq (..)
   , toDynEq
   , fromDynEq
   , fromDynEqMaybe
   )
where

import Data.Dynamic
import Type.Reflection

-- | Dynamic type with Eq and Ord instance
--
-- Can be used as Map keys for instance
data DynEq where
   DynEq :: forall a. (Eq a, Ord a) => TypeRep a -> a -> DynEq

instance Eq DynEq where
   (DynEq tra a) == (DynEq trb b) = case tra `eqTypeRep` trb of
      Nothing    -> False
      Just HRefl -> a == b

instance Ord DynEq where
   compare (DynEq tra a) (DynEq trb b) = case tra `eqTypeRep` trb of
      Nothing    -> compare (SomeTypeRep tra) (SomeTypeRep trb)
      Just HRefl -> compare a b

-- | Create a DynEq value
--
-- >>> toDynEq (10 :: Int) == toDynEq (12 :: Int)
-- False
-- >>> toDynEq (10 :: Int) <= toDynEq (12 :: Int)
-- True
-- >>> toDynEq (10 :: Int) /= toDynEq "Test"
-- True
toDynEq :: (Typeable a, Eq a, Ord a) => a -> DynEq
toDynEq a = DynEq typeRep a

-- | Get a value from a DynEq or the default one if the type doesn't match
fromDynEq :: Typeable a => DynEq -> a -> a
fromDynEq (DynEq tr a) def = case tr `eqTypeRep` typeOf def of
   Nothing    -> def
   Just HRefl -> a

-- | Get a value from a DynEq if the type matches
fromDynEqMaybe :: forall a. Typeable a => DynEq -> Maybe a
fromDynEqMaybe (DynEq tr a) = case tr `eqTypeRep` (typeRep :: TypeRep a) of
   Nothing    -> Nothing
   Just HRefl -> Just a