module DDC.Core.Tetra.Transform.Boxing
        (boxingModule)
where
import DDC.Core.Tetra.Compounds
import DDC.Core.Tetra.Prim
import DDC.Core.Transform.Boxing
import DDC.Core.Module
import DDC.Core.Exp


-- | Manage boxing of numeric values in a module.
boxingModule :: Show a => Module a Name -> Module a Name
boxingModule mm
        = boxing config mm


-- | Tetra-specific configuration for boxing transform.
config :: Config a Name
config  = Config
        { configIsValueIndexType        = isValueIndexType
        , configIsBoxedType             = isBoxedType
        , configIsUnboxedType           = isUnboxedType
        , configBoxedOfIndexType        = boxedOfIndexType
        , configUnboxedOfIndexType      = unboxedOfIndexType
        , configIndexTypeOfBoxed        = indexTypeOfBoxed
        , configIndexTypeOfUnboxed      = indexTypeOfUnboxed
        , configNameIsUnboxedOp         = isNameOfUnboxedOp 
        , configValueTypeOfLitName      = takeTypeOfLitName
        , configValueTypeOfPrimOpName   = takeTypeOfPrimOpName
        , configValueTypeOfForeignName  = const Nothing
        , configBoxedOfValue            = boxedOfValue
        , configValueOfBoxed            = valueOfBoxed
        , configBoxedOfUnboxed          = boxedOfUnboxed
        , configUnboxedOfBoxed          = unboxedOfBoxed }


-- | Check whether a value of this type needs boxing to make the 
--   program representational.
isValueIndexType :: Type Name -> Bool
isValueIndexType tt
        -- These types are listed out in full so anyone who adds more 
        -- constructors to the PrimTyCon type is forced to say whether
        -- those types refer to unboxed values or not.
        --
        | Just (NamePrimTyCon n, _)     <- takePrimTyConApps tt
        = case n of
                -- There should never be any value of type Void# being passed
                -- around, but say they don't need boxing anyway so we don't 
                -- complicate an already broken program.
                PrimTyConVoid           -> False

                PrimTyConBool           -> True
                PrimTyConNat            -> True
                PrimTyConInt            -> True
                PrimTyConWord{}         -> True
                PrimTyConFloat{}        -> True
                PrimTyConVec{}          -> True
                PrimTyConAddr{}         -> True
                PrimTyConPtr{}          -> True
                PrimTyConTag{}          -> True
                PrimTyConString{}       -> True

        -- These are all higher-kinded type constructors,
        -- with don't have a value-level representation.
        | Just (NameTyConTetra n, _)    <- takePrimTyConApps tt
        = case n of
                TyConTetraRef{}         -> False
                TyConTetraTuple{}       -> False
                TyConTetraB{}           -> False
                TyConTetraU{}           -> False

        | otherwise
        = False


-- | Check whether this is a boxed representation type.
isBoxedType :: Type Name -> Bool
isBoxedType tt
        | Just (n, _)   <- takePrimTyConApps tt
        , NameTyConTetra TyConTetraB    <- n
        = True

        | otherwise = False


-- | Check whether this is a boxed representation type.
isUnboxedType :: Type Name -> Bool
isUnboxedType tt
        | Just (n, _)   <- takePrimTyConApps tt
        , NameTyConTetra TyConTetraU    <- n
        = True

        | otherwise = False


-- | Take the index type from a boxed type, if it is one.
indexTypeOfBoxed :: Type Name -> Maybe (Type Name)
indexTypeOfBoxed tt
        | Just (n, [t]) <- takePrimTyConApps tt
        , NameTyConTetra TyConTetraB    <- n
        = Just t

        | otherwise
        = Nothing


-- | Take the index type from an unboxed type, if it is one.
indexTypeOfUnboxed :: Type Name -> Maybe (Type Name)
indexTypeOfUnboxed tt
        | Just (n, [t]) <- takePrimTyConApps tt
        , NameTyConTetra TyConTetraU    <- n
        = Just t

        | otherwise
        = Nothing


-- | Get the boxed version of some type of kind Data.
boxedOfIndexType :: Type Name -> Maybe (Type Name)
boxedOfIndexType tt
        | Just (NamePrimTyCon tc, [])   <- takePrimTyConApps tt
        = case tc of
                PrimTyConBool           -> Just $ tBoxed tBool
                PrimTyConNat            -> Just $ tBoxed tNat
                PrimTyConInt            -> Just $ tBoxed tInt
                PrimTyConWord  bits     -> Just $ tBoxed (tWord  bits)
                _                       -> Nothing

        | otherwise     = Nothing


-- | Get the unboxed version of some type of kind Data.
unboxedOfIndexType :: Type Name -> Maybe (Type Name)
unboxedOfIndexType tt
        | Just (NamePrimTyCon tc, [])   <- takePrimTyConApps tt
        = case tc of
                PrimTyConBool           -> Just $ tUnboxed tBool
                PrimTyConNat            -> Just $ tUnboxed tNat
                PrimTyConInt            -> Just $ tUnboxed tInt
                PrimTyConWord  bits     -> Just $ tUnboxed (tWord  bits)
                _                       -> Nothing

        | otherwise     = Nothing


-- | Check if the primitive operator with this name takes unboxed values
--   directly.
isNameOfUnboxedOp :: Name -> Bool
isNameOfUnboxedOp nn
 = case nn of
        NamePrimArith{} -> True
        NamePrimCast{}  -> True
        _               -> False


-- | Wrap a pure value into its boxed representation.
boxedOfValue :: a -> Exp a Name -> Type Name -> Maybe (Exp a Name)
boxedOfValue a xx tt
        | Just tBx      <- boxedOfIndexType tt
        = Just $ xCastConvert a tt tBx xx

        | otherwise     = Nothing


-- | Unwrap a boxed value.
valueOfBoxed :: a -> Exp a Name -> Type Name -> Maybe (Exp a Name)
valueOfBoxed a xx tt
        | Just tBx      <- boxedOfIndexType tt
        = Just $ xCastConvert a tBx tt xx

        | otherwise     = Nothing


-- | Box an expression of the given type.
boxedOfUnboxed :: a -> Exp a Name -> Type Name -> Maybe (Exp a Name)
boxedOfUnboxed a xx tt
        | Just tBx      <- boxedOfIndexType tt
        , Just tUx      <- unboxedOfIndexType tt
        = Just $ xCastConvert a tUx tBx xx

        | otherwise     = Nothing


-- | Unbox an expression of the given type.
unboxedOfBoxed :: a -> Exp a Name -> Type Name -> Maybe (Exp a Name)
unboxedOfBoxed a xx tt
        | Just tBx      <- boxedOfIndexType tt
        , Just tUx      <- unboxedOfIndexType tt
        = Just $ xCastConvert a tBx tUx xx

        | otherwise     = Nothing