{-# LANGUAGE UndecidableInstances #-}

-- | Open type representations and dynamic types

module Data.TypeRep.Internal where



import Control.Monad.Except
import Data.Char (isAlphaNum)

import Data.Constraint (Constraint, Dict (..))
import Data.Proxy (Proxy (..))

import Data.Syntactic



----------------------------------------------------------------------------------------------------
-- * Type representations
----------------------------------------------------------------------------------------------------

-- | 'Full'-indexed type representation
type TR = AST

-- | This class provides reification of type @a@ in a universe @t@. @`Typeable` t a@ means that @a@
-- is in the type universe represented by @t@.
class Typeable t a
  where
    typeRep' :: TR t (Full a)

-- | Representation of type @a@ in a type universe @t@
--
-- This type can also be seen as a witness that @a@ is a member of @t@ (i.e. @`Typeable` t a@); see
-- 'witTypeable'.
newtype TypeRep t a = TypeRep { unTypeRep :: TR t (Full a) }
  -- The newtype is mainly because 'TR' cannot be partially applied

instance Render t => Show (TypeRep t a)
  where
    show = render . desugar

instance Syntactic (TypeRep t a)
  where
    type Domain (TypeRep t a)   = t
    type Internal (TypeRep t a) = a
    desugar = unTypeRep
    sugar   = TypeRep

-- | Reification of type @a@ in a type universe @t@
typeRep :: Typeable t a => TypeRep t a
typeRep = TypeRep typeRep'

-- | Equality on type representations
class Render t => TypeEq t u
  where
    typeEqSym
        :: (t sig1, Args (AST u) sig1)
        -> (t sig2, Args (AST u) sig2)
        -> Either String (Dict (DenResult sig1 ~ DenResult sig2))
  -- The reason to have `Render` as a super class is not to leak unnecessary stuff in the type of
  -- `typeEq`.

instance (TypeEq t1 t, TypeEq t2 t) => TypeEq (t1 :+: t2) t
  where
    typeEqSym (InjL t1, as1) (InjL t2, as2) = typeEqSym (t1,as1) (t2,as2)
    typeEqSym (InjR t1, as1) (InjR t2, as2) = typeEqSym (t1,as1) (t2,as2)
    typeEqSym _ _ = throwError ""

instance TypeEq Empty t
  where
    typeEqSym = error "typeEqSym: Empty"

-- | Equality on type representations
typeEq :: (TypeEq t t, MonadError String m) => TypeRep t a -> TypeRep t b -> m (Dict (a ~ b))
typeEq t1@(TypeRep s1) t2@(TypeRep s2) = case go (s1, Nil) (s2, Nil) of
    Left _     -> throwError $ "type mismatch: " ++ show t1 ++ " /= " ++ show t2
    Right Dict -> return Dict
  where
    go :: TypeEq t t
      => (AST t sig1, Args (AST t) sig1)
      -> (AST t sig2, Args (AST t) sig2)
      -> Either String (Dict ((DenResult sig1 ~ DenResult sig2)))
    go (Sym t1, as1)   (Sym t2, as2)   = typeEqSym (t1,as1) (t2,as2)
    go (s1 :$ a1, as1) (s2 :$ a2, as2) = go (s1, a1 :* as1) (s2, a2 :* as2)
    go _ _ = throwError ""

-- | Type constructor matching. This function makes it possible to match on type representations
-- without dealing with the underlying 'AST' representation.
--
-- For example, to check that a 'TypeRep' represents the type @a -> Int@ for some @a@:
--
-- > is_atoi :: (TypeEq t t, IntType :<: t) => TypeRep t a -> Bool
-- > is_atoi t
-- >     | [E ta, E tb] <- matchCon t
-- >     , Just _       <- typeEq ta intType = True
-- >     | otherwise                         = False
matchCon :: TypeRep t c -> [E (TypeRep t)]
matchCon = simpleMatch (\_ -> foldrArgs (\t -> (E (TypeRep t) :)) []) . unTypeRep

-- | Monadic version of 'matchCon'
--
-- > matchConM = return . matchCon
--
-- 'matchConM' is convenient when matching types in a monad, e.g.:
--
-- > do ...
-- >    [E ta, E tb] <- matchConM t
-- >    Dict         <- typeEq ta tb
-- >    ...
matchConM :: Monad m => TypeRep t c -> m [E (TypeRep t)]
matchConM = return . matchCon

-- | Show the name of type classes
class ShowClass (p :: * -> Constraint)
  where
    -- | Show the name of a type class
    showClass :: Proxy p -> String

-- | Witness a type constraint for a reified type
class Witness p t u
  where
    witSym :: t sig -> Args (AST u) sig -> Dict (p (DenResult sig))

instance (Witness p t1 t, Witness p t2 t) => Witness p (t1 :+: t2) t
  where
    witSym (InjL s) as = witSym s as
    witSym (InjR s) as = witSym s as

instance Witness p t t => Witness p (AST t) t
  where
    witSym (Sym s)  as = witSym s as
    witSym (s :$ a) as = witSym s (a :* as)

-- | Partially witness a type constraint for a reified type
class (ShowClass p, Render t) => PWitness p t u
  where
    pwitSym :: t sig -> Args (AST u) sig -> Either String (Dict (p (DenResult sig)))
    pwitSym _ _ = throwError ""
  -- The reason to have `Render` as a super class is not to leak unnecessary stuff in the type of
  -- `pwit`.

instance (PWitness p t1 t, PWitness p t2 t) => PWitness p (t1 :+: t2) t
  where
    pwitSym (InjL s) as = pwitSym s as
    pwitSym (InjR s) as = pwitSym s as

-- | Default implementation of 'pwitSym' for types that have a 'Witness' instance
pwitSymDefault :: Witness p t u =>
    t sig -> Args (AST u) sig -> Either String (Dict (p (DenResult sig)))
pwitSymDefault t = return . witSym t

-- | Witness a type constraint for a reified type
wit :: forall p t a . Witness p t t => Proxy p -> TypeRep t a -> Dict (p a)
wit _ (TypeRep a) = witSym a (Nil :: Args (AST t) (Full a))

-- | Partially witness a type constraint for a reified type
pwit :: forall p t m a . (PWitness p t t, MonadError String m) =>
    Proxy p -> TypeRep t a -> m (Dict (p a))
pwit p t@(TypeRep a) = case go a Nil of
    Left _  -> throwError $ unwords ["cannot deduce", showClass p, classArg]
    Right a -> return a
  where
    st       = show t
    classArg = if all isAlphaNum st then st else "(" ++ st ++ ")"

    go :: AST t sig -> Args (AST t) sig -> Either String (Dict (p (DenResult sig)))
    go (Sym s)  as = pwitSym s as
    go (s :$ a) as = go s (a :* as)



----------------------------------------------------------------------------------------------------
-- * Dynamic types
----------------------------------------------------------------------------------------------------

-- | Safe cast (does not use @unsafeCoerce@)
cast :: forall t a b . (Typeable t a, Typeable t b, TypeEq t t) =>
    Proxy t -> a -> Either String b
cast _ a = do
    Dict <- typeEq (typeRep :: TypeRep t a) (typeRep :: TypeRep t b)
    return a

-- | Safe generalized cast (does not use @unsafeCoerce@)
gcast :: forall t a b c . (Typeable t a, Typeable t b, TypeEq t t) =>
    Proxy t -> c a -> Either String (c b)
gcast _ a = do
    Dict <- typeEq (typeRep :: TypeRep t a) (typeRep :: TypeRep t b)
    return a

-- | Dynamic type parameterized on a type universe
data Dynamic t
  where
    Dyn :: TypeRep t a -> a -> Dynamic t

toDyn :: Typeable t a => a -> Dynamic t
toDyn = Dyn typeRep

fromDyn :: forall t a . (Typeable t a, TypeEq t t) => Dynamic t -> Either String a
fromDyn (Dyn t a) = do
    Dict <- typeEq t (typeRep :: TypeRep t a)
    return a

instance (TypeEq t t, Witness Eq t t) => Eq (Dynamic t)
  where
    Dyn ta a == Dyn tb b
        | Right Dict <- typeEq ta tb
        , Dict       <- wit pEq ta
        = a == b
    _ == _ = False

instance Witness Show t t => Show (Dynamic t)
  where
    show (Dyn t a) | Dict <- wit pShow t = show a



----------------------------------------------------------------------------------------------------
-- * Specific types/classes
----------------------------------------------------------------------------------------------------

-- | The universal class
class    Any a
instance Any a

instance ShowClass Any          where showClass _ = "Any"
instance ShowClass Eq           where showClass _ = "Eq"
instance ShowClass Ord          where showClass _ = "Ord"
instance ShowClass Show         where showClass _ = "Show"
instance ShowClass Num          where showClass _ = "Num"
instance ShowClass Integral     where showClass _ = "Integral"
instance ShowClass (Typeable t) where showClass _ = "Typeable ..."

-- | Witness a 'Typeable' constraint for a reified type
witTypeable :: Witness (Typeable t) t t => TypeRep t a -> Dict (Typeable t a)
witTypeable = wit Proxy

-- | Partially witness a 'Typeable' constraint for a reified type
pwitTypeable :: PWitness (Typeable t) t t => TypeRep t a -> Either String (Dict (Typeable t a))
pwitTypeable = pwit Proxy

pAny :: Proxy Any
pAny = Proxy

pEq :: Proxy Eq
pEq = Proxy

pOrd :: Proxy Ord
pOrd = Proxy

pShow :: Proxy Show
pShow = Proxy

pNum :: Proxy Num
pNum = Proxy

pIntegral :: Proxy Integral
pIntegral = Proxy

data BoolType  a where BoolType  :: BoolType  (Full Bool)
data CharType  a where CharType  :: CharType  (Full Char)
data IntType   a where IntType   :: IntType   (Full Int)
data FloatType a where FloatType :: FloatType (Full Float)
data ListType  a where ListType  :: ListType  (a :-> Full [a])
data FunType   a where FunType   :: FunType   (a :-> b :-> Full (a -> b))

instance Render BoolType  where renderSym BoolType  = "Bool"
instance Render CharType  where renderSym CharType  = "Char"
instance Render IntType   where renderSym IntType   = "Int"
instance Render FloatType where renderSym FloatType = "Float"

instance Render ListType
  where
    renderSym ListType = "[]"
    renderArgs [a] ListType = "[" ++ a ++ "]"

instance Render FunType
  where
    renderSym FunType = "(->)"
    renderArgs [a,b] FunType = a ++ " -> " ++ b

boolType :: (Syntactic a, BoolType :<: Domain a, Internal a ~ Bool) => a
boolType = sugarSym BoolType

charType :: (Syntactic a, CharType :<: Domain a, Internal a ~ Char) => a
charType = sugarSym CharType

intType :: (Syntactic a, IntType :<: Domain a, Internal a ~ Int) => a
intType = sugarSym IntType

floatType :: (Syntactic a, FloatType :<: Domain a, Internal a ~ Float) => a
floatType = sugarSym FloatType

listType
    :: ( Syntactic list
       , Syntactic elem
       , Domain list ~ Domain elem
       , ListType :<: Domain list
       , Internal list ~ [Internal elem]
       , elem ~ c e
       , list ~ c l
           -- These last equalities are used to help type inference by forcing the representations
           -- to use the same type constructor (e.g. 'TR' or 'TypeRep')
       )
    => elem -> list
listType = sugarSym ListType

funType
    :: ( Syntactic fun
       , Syntactic a
       , Syntactic b
       , Domain fun ~ Domain a
       , Domain fun ~ Domain b
       , FunType :<: Domain fun
       , Internal fun ~ (Internal a -> Internal b)
       , a   ~ c x
       , b   ~ c y
       , fun ~ c z
       )
    => a -> b -> fun
funType = sugarSym FunType

instance (BoolType  :<: t)                             => Typeable t Bool     where typeRep' = boolType
instance (CharType  :<: t)                             => Typeable t Char     where typeRep' = charType
instance (IntType   :<: t)                             => Typeable t Int      where typeRep' = intType
instance (FloatType :<: t)                             => Typeable t Float    where typeRep' = floatType
instance (ListType  :<: t, Typeable t a)               => Typeable t [a]      where typeRep' = listType typeRep'
instance (FunType   :<: t, Typeable t a, Typeable t b) => Typeable t (a -> b) where typeRep' = funType typeRep' typeRep'

instance TypeEq BoolType  t where typeEqSym (BoolType, Nil)  (BoolType, Nil)  = return Dict
instance TypeEq CharType  t where typeEqSym (CharType, Nil)  (CharType, Nil)  = return Dict
instance TypeEq IntType   t where typeEqSym (IntType, Nil)   (IntType, Nil)   = return Dict
instance TypeEq FloatType t where typeEqSym (FloatType, Nil) (FloatType, Nil) = return Dict

instance TypeEq t t => TypeEq ListType t
  where
    typeEqSym (ListType, a :* Nil) (ListType, b :* Nil) = do
        Dict <- typeEq (TypeRep a) (TypeRep b)
        return Dict

instance TypeEq t t => TypeEq FunType t
  where
    typeEqSym (FunType, a1 :* b1 :* Nil) (FunType, a2 :* b2 :* Nil) = do
        Dict <- typeEq (TypeRep a1) (TypeRep a2)
        Dict <- typeEq (TypeRep b1) (TypeRep b2)
        return Dict

instance (BoolType  :<: t) => Witness (Typeable t) BoolType  t where witSym BoolType  Nil = Dict
instance (CharType  :<: t) => Witness (Typeable t) CharType  t where witSym CharType  Nil = Dict
instance (IntType   :<: t) => Witness (Typeable t) IntType   t where witSym IntType   Nil = Dict
instance (FloatType :<: t) => Witness (Typeable t) FloatType t where witSym FloatType Nil = Dict

instance (ListType :<: t, Witness (Typeable t) t t) => Witness (Typeable t) ListType t
  where
    witSym ListType (a :* Nil)
        | Dict <- witTypeable (TypeRep a) = Dict

instance (FunType :<: t, Witness (Typeable t) t t) => Witness (Typeable t) FunType t
  where
    witSym FunType (a :* b :* Nil)
        | Dict <- witTypeable (TypeRep a)
        , Dict <- witTypeable (TypeRep b)
        = Dict

instance (BoolType  :<: t)                            => PWitness (Typeable t) BoolType  t where pwitSym = pwitSymDefault
instance (CharType  :<: t)                            => PWitness (Typeable t) CharType  t where pwitSym = pwitSymDefault
instance (IntType   :<: t)                            => PWitness (Typeable t) IntType   t where pwitSym = pwitSymDefault
instance (FloatType :<: t)                            => PWitness (Typeable t) FloatType t where pwitSym = pwitSymDefault
instance (ListType  :<: t, PWitness (Typeable t) t t) => PWitness (Typeable t) ListType  t where pwitSym ListType (a :* Nil) = do Dict <- pwitTypeable (TypeRep a); return Dict
instance (FunType   :<: t, PWitness (Typeable t) t t) => PWitness (Typeable t) FunType   t where pwitSym FunType (a :* b :* Nil) = do Dict <- pwitTypeable (TypeRep a); Dict <- pwitTypeable (TypeRep b); return Dict

instance Witness Any BoolType  t where witSym _ _ = Dict
instance Witness Any CharType  t where witSym _ _ = Dict
instance Witness Any IntType   t where witSym _ _ = Dict
instance Witness Any FloatType t where witSym _ _ = Dict
instance Witness Any ListType  t where witSym _ _ = Dict
instance Witness Any FunType   t where witSym _ _ = Dict

instance PWitness Any BoolType  t where pwitSym _ _ = return Dict
instance PWitness Any CharType  t where pwitSym _ _ = return Dict
instance PWitness Any IntType   t where pwitSym _ _ = return Dict
instance PWitness Any FloatType t where pwitSym _ _ = return Dict
instance PWitness Any ListType  t where pwitSym _ _ = return Dict
instance PWitness Any FunType   t where pwitSym _ _ = return Dict

instance                   Witness Eq BoolType  t where witSym BoolType  Nil = Dict
instance                   Witness Eq CharType  t where witSym CharType  Nil = Dict
instance                   Witness Eq IntType   t where witSym IntType   Nil = Dict
instance                   Witness Eq FloatType t where witSym FloatType Nil = Dict
instance Witness Eq t t => Witness Eq ListType  t where witSym ListType (a :* Nil) | Dict <- wit pEq (TypeRep a) = Dict

instance                    PWitness Eq BoolType  t where pwitSym = pwitSymDefault
instance                    PWitness Eq CharType  t where pwitSym = pwitSymDefault
instance                    PWitness Eq IntType   t where pwitSym = pwitSymDefault
instance                    PWitness Eq FloatType t where pwitSym = pwitSymDefault
instance PWitness Eq t t => PWitness Eq ListType  t where pwitSym ListType (a :* Nil) = do Dict <- pwit pEq (TypeRep a); return Dict
instance PWitness Eq FunType t

instance                    Witness Ord BoolType  t where witSym BoolType  Nil = Dict
instance                    Witness Ord CharType  t where witSym CharType  Nil = Dict
instance                    Witness Ord IntType   t where witSym IntType   Nil = Dict
instance                    Witness Ord FloatType t where witSym FloatType Nil = Dict
instance Witness Ord t t => Witness Ord ListType  t where witSym ListType (a :* Nil) | Dict <- wit pOrd (TypeRep a) = Dict

instance                     PWitness Ord BoolType  t where pwitSym = pwitSymDefault
instance                     PWitness Ord CharType  t where pwitSym = pwitSymDefault
instance                     PWitness Ord IntType   t where pwitSym = pwitSymDefault
instance                     PWitness Ord FloatType t where pwitSym = pwitSymDefault
instance PWitness Ord t t => PWitness Ord ListType  t where pwitSym ListType (a :* Nil) = do Dict <- pwit pOrd (TypeRep a); return Dict
instance PWitness Ord FunType t

instance                     Witness Show BoolType  t where witSym BoolType  Nil = Dict
instance                     Witness Show CharType  t where witSym CharType  Nil = Dict
instance                     Witness Show IntType   t where witSym IntType   Nil = Dict
instance                     Witness Show FloatType t where witSym FloatType Nil = Dict
instance Witness Show t t => Witness Show ListType  t where witSym ListType (a :* Nil) | Dict <- wit pShow (TypeRep a) = Dict

instance                      PWitness Show BoolType  t where pwitSym = pwitSymDefault
instance                      PWitness Show CharType  t where pwitSym = pwitSymDefault
instance                      PWitness Show IntType   t where pwitSym = pwitSymDefault
instance                      PWitness Show FloatType t where pwitSym = pwitSymDefault
instance PWitness Show t t => PWitness Show ListType  t where pwitSym ListType (a :* Nil) = do Dict <- pwit pShow (TypeRep a); return Dict
instance PWitness Show FunType t

instance Witness Num IntType   t where witSym IntType   Nil = Dict
instance Witness Num FloatType t where witSym FloatType Nil = Dict

instance PWitness Num BoolType  t
instance PWitness Num CharType  t
instance PWitness Num IntType   t where pwitSym = pwitSymDefault
instance PWitness Num FloatType t where pwitSym = pwitSymDefault
instance PWitness Num ListType  t
instance PWitness Num FunType   t

instance Witness Integral IntType t where witSym IntType Nil = Dict

instance PWitness Integral BoolType  t
instance PWitness Integral CharType  t
instance PWitness Integral IntType   t where pwitSym = pwitSymDefault
instance PWitness Integral FloatType t
instance PWitness Integral ListType  t
instance PWitness Integral FunType   t

dynToInteger :: PWitness Integral t t => Dynamic t -> Either String Integer
dynToInteger (Dyn tr a) = do
    Dict <- pwit pIntegral tr
    return (toInteger a)