    -- |Compute a description of the generic representation that we use for a user defined data type.
    --
    -- During vectorisation, we generate a PRepr and PA instance for each user defined
    -- data type. The PA dictionary contains methods to convert the user type to and
    -- from our generic representation. This module computes a description of what
    -- that generic representation is.
    --
    module Vectorise.Generic.Description
      ( CompRepr(..)
      , ProdRepr(..)
      , ConRepr(..)
      , SumRepr(..)
      , tyConRepr
      , sumReprType
      , compOrigType
      )
    where

    import Vectorise.Utils
    import Vectorise.Monad
    import Vectorise.Builtins

    import CoreSyn
    import DataCon
    import TyCon
    import Type
    import Control.Monad
    import Outputable

    -- | Describes the generic representation of a data type.
    --   If the data type has multiple constructors then we bundle them
    --   together into a generic sum type.
    data SumRepr
            =  -- | Data type has no data constructors.
               EmptySum

            -- | Data type has a single constructor.
            | UnarySum ConRepr

            -- | Data type has multiple constructors.
            | Sum  { -- | Representation tycon for the sum (eg Sum2)
                     repr_sum_tc    :: TyCon

                   -- | PData version of the sum tycon     (eg PDataSum2)
                   --   This TyCon doesn't appear explicitly in the source program.
                   --   See Note [PData TyCons].
                   , repr_psum_tc   :: TyCon

                   -- | PDatas version of the sum tycon    (eg PDatasSum2)
                   , repr_psums_tc  :: TyCon

                   -- | Type of the selector               (eg Sel2)
                   , repr_sel_ty    :: Type

                   -- | Type of multi-selector             (eg Sel2s)
                   , repr_sels_ty   :: Type

                   -- | Function to get the length of a Sels of this type.
                   , repr_selsLength_v :: CoreExpr

                   -- | Type of each data constructor.
                   , repr_con_tys   :: [Type]

                   -- | Generic representation types of each data constructor.
                   , repr_cons      :: [ConRepr]
                   }

    -- | Describes the representation type of a data constructor.
    data ConRepr
            = ConRepr
                    { repr_dc       :: DataCon
                    , repr_prod     :: ProdRepr
                    }

    -- | Describes the representation type of the fields \/ components of a constructor.
    --   If the data constructor has multiple fields then we bundle them
    --   together into a generic product type.
    data ProdRepr
            = -- | Data constructor has no fields.
              EmptyProd

            -- | Data constructor has a single field.
            | UnaryProd CompRepr

            -- | Data constructor has several fields.
            | Prod { -- | Representation tycon for the product (eg Tuple2)
                     repr_tup_tc   :: TyCon

                     -- | PData  version of the product tycon  (eg PDataTuple2)
                   , repr_ptup_tc  :: TyCon

                     -- | PDatas version of the product tycon  (eg PDatasTuple2s)
                     --   Not all lifted backends use `PDatas`.
                   , repr_ptups_tc :: TyCon

                     -- | Types of each field.
                   , repr_comp_tys :: [Type]

                     -- | Generic representation types for each field.
                   , repr_comps    :: [CompRepr]
                   }

    -- | Describes the representation type of a data constructor field.
    data CompRepr
            = Keep Type
                   CoreExpr     -- PR dictionary for the type
            | Wrap Type

    -------------------------------------------------------------------------------

    -- |Determine the generic representation of a data type, given its tycon.
    --
    tyConRepr :: TyCon -> VM SumRepr
    tyConRepr tc
      = sum_repr (tyConDataCons tc)
      where
        -- Build the representation type for a data type with the given constructors.
        -- The representation types for each individual constructor are bundled
        -- together into a generic sum type.
        sum_repr :: [DataCon] -> VM SumRepr
        sum_repr []    = return EmptySum
        sum_repr [con] = liftM UnarySum (con_repr con)
        sum_repr cons
         = do  let arity    = length cons
               rs           <- mapM con_repr cons
               tys          <- mapM conReprType rs

               -- Get the 'Sum' tycon of this arity (eg Sum2).
               sum_tc       <- builtin (sumTyCon arity)

               -- Get the 'PData' and 'PDatas' tycons for the sum.
               psum_tc      <- pdataReprTyConExact  sum_tc
               psums_tc     <- pdatasReprTyConExact sum_tc

               sel_ty       <- builtin (selTy      arity)
               sels_ty      <- builtin (selsTy     arity)
               selsLength_v <- builtin (selsLength arity)
               return $ Sum
                      { repr_sum_tc         = sum_tc
                      , repr_psum_tc        = psum_tc
                      , repr_psums_tc       = psums_tc
                      , repr_sel_ty         = sel_ty
                      , repr_sels_ty        = sels_ty
                      , repr_selsLength_v   = selsLength_v
                      , repr_con_tys        = tys
                      , repr_cons           = rs
                      }

        -- Build the representation type for a single data constructor.
        con_repr con   = liftM (ConRepr con) (prod_repr (dataConRepArgTys con))

        -- Build the representation type for the fields of a data constructor.
        -- The representation types for each individual field are bundled
        -- together into a generic product type.
        prod_repr :: [Type] -> VM ProdRepr
        prod_repr []   = return EmptyProd
        prod_repr [ty] = liftM UnaryProd (comp_repr ty)
        prod_repr tys
         = do  let arity    = length tys
               rs           <- mapM comp_repr tys
               tys'         <- mapM compReprType rs

               -- Get the Prod \/ Tuple tycon of this arity (eg Tuple2)
               tup_tc       <- builtin (prodTyCon arity)

               -- Get the 'PData' and 'PDatas' tycons for the product.
               ptup_tc      <- pdataReprTyConExact  tup_tc
               ptups_tc     <- pdatasReprTyConExact tup_tc

               return $ Prod
                      { repr_tup_tc   = tup_tc
                      , repr_ptup_tc  = ptup_tc
                      , repr_ptups_tc = ptups_tc
                      , repr_comp_tys = tys'
                      , repr_comps    = rs
                      }

        -- Build the representation type for a single data constructor field.
        comp_repr ty = liftM (Keep ty) (prDictOfReprType ty)
                       `orElseV` return (Wrap ty)

    -- |Yield the type of this sum representation.
    --
    sumReprType :: SumRepr -> VM Type
    sumReprType EmptySum     = voidType
    sumReprType (UnarySum r) = conReprType r
    sumReprType (Sum { repr_sum_tc  = sum_tc, repr_con_tys = tys })
      = return $ mkTyConApp sum_tc tys

    -- Yield the type of this constructor representation.
    --
    conReprType :: ConRepr -> VM Type
    conReprType (ConRepr _ r) = prodReprType r

    -- Yield the type of of this product representation.
    --
    prodReprType :: ProdRepr -> VM Type
    prodReprType EmptyProd     = voidType
    prodReprType (UnaryProd r) = compReprType r
    prodReprType (Prod { repr_tup_tc = tup_tc, repr_comp_tys = tys })
      = return $ mkTyConApp tup_tc tys

    -- Yield the type of this data constructor field \/ component representation.
    --
    compReprType :: CompRepr -> VM Type
    compReprType (Keep ty _) = return ty
    compReprType (Wrap ty)   = mkWrapType ty

    -- |Yield the original component type of a data constructor component representation.
    --
    compOrigType :: CompRepr -> Type
    compOrigType (Keep ty _) = ty
    compOrigType (Wrap ty)   = ty

    -- Outputable instances -------------------------------------------------------
    instance Outputable SumRepr where
     ppr ss
      = case ss of
            EmptySum
             -> text "EmptySum"

            UnarySum con
             -> sep [text "UnarySum", ppr con]

            Sum sumtc psumtc psumstc selty selsty selsLength contys cons
             -> text "Sum" $+$ braces (nest 4
                    $ sep   [ text "repr_sum_tc       = " <> ppr sumtc
                            , text "repr_psum_tc      = " <> ppr psumtc
                            , text "repr_psums_tc     = " <> ppr psumstc
                            , text "repr_sel_ty       = " <> ppr selty
                            , text "repr_sels_ty      = " <> ppr selsty
                            , text "repr_selsLength_v = " <> ppr selsLength
                            , text "repr_con_tys      = " <> ppr contys
                            , text "repr_cons         = " <> ppr cons])

    instance Outputable ConRepr where
     ppr (ConRepr dc pr)
            = text "ConRepr" $+$ braces (nest 4
                    $ sep   [ text "repr_dc      = " <> ppr dc
                            , text "repr_prod    = " <> ppr pr])

    instance Outputable ProdRepr where
     ppr ss
      = case ss of
            EmptyProd
             -> text "EmptyProd"

            UnaryProd cr
             -> sep [text "UnaryProd", ppr cr]

            Prod tuptcs ptuptcs ptupstcs comptys comps
             -> sep [text "Prod", ppr tuptcs, ppr ptuptcs, ppr ptupstcs, ppr comptys, ppr comps]

    instance Outputable CompRepr where
     ppr ss
      = case ss of
            Keep t ce
             -> text "Keep" $+$ sep [ppr t, ppr ce]

            Wrap t
             -> sep [text "Wrap", ppr t]

    -- Notes ----------------------------------------------------------------------
    {-
    Note [PData TyCons]
    ~~~~~~~~~~~~~~~~~~~
    When PData is a type family, the compiler generates a type constructor for each
    instance, which is named after the family and instance type. This type
    constructor does not appear in the source program. Rather, it is implicitly
    defined by the data instance. For example with:

      data family PData a

      data instance PData (Sum2 a b)
            = PSum2  U.Sel2
                     (PData a)
                     (PData b)

    The type constructor corresponding to the instance will be named 'PDataSum2',
    and this is what we will get in the repr_psum_tc field of SumRepr.Sum.

    -}