{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections     #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFunctor     #-}
{-# LANGUAGE TemplateHaskell   #-}
{-# OPTIONS_GHC -cpp                      #-}
{-# OPTIONS_GHC -Wno-name-shadowing       #-}
{-# OPTIONS_GHC -Wno-unused-pattern-binds #-}
module Generics.MRSOP.TH
  ( deriveFamilyWith
  , deriveFamilyWithTy
  , deriveFamily
  , genFamilyDebug
  ) where
import Data.Function (on)
import Data.Char (isAlphaNum)
import Data.List (sortBy)
import qualified Data.SOP.NS as SOP (NS(..))
import Control.Monad
import Control.Monad.State
import Control.Monad.Writer   (WriterT, tell, runWriterT)
import Control.Monad.Identity (runIdentity)
import Language.Haskell.TH hiding (match)
import Language.Haskell.TH.Syntax (liftString)
import Generics.MRSOP.Util
import Generics.MRSOP.Base.Class
import Generics.MRSOP.Base.NP
import Generics.MRSOP.Base.Universe hiding (match)
import qualified Generics.MRSOP.Base.Metadata as Meta
import qualified Data.Map as M
data OpaqueData = OpaqueData
  { opaqueName   :: Name
  
  
  , opaqueTable  :: M.Map Name Name
  
  , opaqueCons   :: M.Map Name Name
  } deriving (Eq , Show)
deriveFamilyWith :: Name -> Q Type -> Q [Dec]
deriveFamilyWith opqName t
  = do sty                <- t >>= convertType
       opqData            <- reifyOpaqueType opqName
       (_ , (Idxs _ m _)) <- runIdxsM (reifySTy opqData sty)
       
       
       m' <- mapM extractDTI (M.toList m)
       let final = sortBy (compare `on` second) m'
       
       res <- genFamily opqData sty final
       return res 
  where
    second (_ , x , _) = x
    extractDTI (sty , (_ix , Nothing))
      = fail $ "Type " ++ show sty ++ " has no datatype information."
    extractDTI (sty , (ix , Just dti))
      = return (sty , ix , dti)
deriveFamilyWithTy :: Q Type -> Q Type -> Q [Dec]
deriveFamilyWithTy opq ty
  = do opqTy <- opq
       case opqTy of
         ConT opqName -> deriveFamilyWith opqName ty
         _            -> fail $ "Type " ++ show opqTy ++ " must be a name!"
deriveFamily :: Q Type -> Q [Dec]
deriveFamily = deriveFamilyWith (mkName "Singl")
type DataName  = Name
type ConName   = Name
type FieldName = Name
type Args      = [Name]
data DTI ty
  = ADT DataName Args [ CI ty ]
  | New DataName Args (CI ty)
  deriving (Eq , Show , Functor)
data CI ty
  = Normal ConName [ty]
  | Infix  ConName Fixity ty ty
  | Record ConName [ (FieldName , ty) ]
  deriving (Eq , Show , Functor)
ciMapM :: (Monad m) => (ty -> m tw) -> CI ty -> m (CI tw)
ciMapM f (Normal name tys)  = Normal name  <$> mapM f tys
ciMapM f (Infix name x l r) = Infix name x <$> f l <*> f r
ciMapM f (Record name tys)  = Record name  <$> mapM (rstr . (id *** f)) tys
  where
    rstr (a , b) = b >>= return . (a,)
dtiMapM :: (Monad m) => (ty -> m tw) -> DTI ty -> m (DTI tw)
dtiMapM f (ADT name args ci) = ADT name args <$> mapM (ciMapM f) ci
dtiMapM f (New name args ci) = New name args <$> ciMapM f ci
dti2ci :: DTI ty -> [CI ty]
dti2ci (ADT _ _ cis) = cis
dti2ci (New _ _ ci)  = [ ci ]
ci2ty :: CI ty -> [ty]
ci2ty (Normal _ tys)  = tys
ci2ty (Infix _ _ a b) = [a , b]
ci2ty (Record _ tys)  = map snd tys
ciName :: CI ty -> Name
ciName (Normal n _)    = n
ciName (Infix n _ _ _) = n
ciName (Record n _)    = n
ci2Pat :: CI ty -> Q ([Name] , Pat)
ci2Pat ci
  = do ns <- mapM (const (newName "x")) (ci2ty ci)
       return (ns , (ConP (ciName ci) (map VarP ns)))
ci2Exp :: CI ty -> Q ([Name], Exp)
ci2Exp ci
  = do ns <- mapM (const (newName "y")) (ci2ty ci)
       return (ns , foldl (\e n -> AppE e (VarE n)) (ConE (ciName ci)) ns)
data STy
  = AppST STy STy
  | VarST Name
  | ConST Name
  deriving (Eq , Show, Ord)
styFold :: (a -> a -> a) -> (Name -> a) -> (Name -> a) -> STy -> a
styFold app var con (AppST a b) = app (styFold app var con a) (styFold app var con b)
styFold _   var _   (VarST n)   = var n
styFold _   _   con (ConST n)   = con n
isClosed :: STy -> Bool
isClosed = styFold (&&) (const False) (const True)
convertType :: (Monad m) => Type -> m STy
convertType (AppT a b)  = AppST <$> convertType a <*> convertType b
convertType (SigT t _)  = convertType t
convertType (VarT n)    = return (VarST n)
convertType (ConT n)    = return (ConST n)
convertType (ParensT t) = convertType t
convertType ListT       = return (ConST (mkName "[]"))
convertType (TupleT n)  = return (ConST (mkName $ '(':replicate (n-1) ',' ++ ")"))
convertType t           = fail ("convertType: Unsupported Type: " ++ show t)
trevnocType :: STy -> Type
trevnocType (AppST a b) = AppT (trevnocType a) (trevnocType b)
trevnocType (VarST n)   = VarT n
trevnocType (ConST n)
  | n == mkName "[]" = ListT
  | isTupleN n       = TupleT $ length (show n) - 1
  | otherwise        = ConT n
  where isTupleN n0 = take 2 (show n0) == "(,"
stySubst :: STy -> Name -> STy -> STy
stySubst (AppST a b) m n = AppST (stySubst a m n) (stySubst b m n)
stySubst (ConST a)   _ _ = ConST a
stySubst (VarST x)   m n
  | x == m    = n
  | otherwise = VarST x
styReduce :: [(Name , STy)] -> STy -> STy
styReduce parms t = foldr (\(n , m) ty -> stySubst ty n m) t parms
styFlatten :: STy -> (STy , [STy])
styFlatten (AppST a b) = id *** (++ [b]) $ styFlatten a
styFlatten sty         = (sty , [])
reifyDec :: Name -> Q Dec
reifyDec name =
  do info <- reify name
     case info of TyConI dec -> return dec
                  _          -> fail $ show name ++ " is not a declaration"
argInfo :: TyVarBndr -> Name
argInfo (PlainTV  n)   = n
argInfo (KindedTV n _) = n
decInfo :: Dec -> Q (DTI STy)
decInfo (TySynD     _name _args _ty)       = fail "Type Synonyms not supported"
decInfo (DataD    _ name args    _ cons _) = ADT name (map argInfo args) <$> mapM conInfo cons
decInfo (NewtypeD _ name args    _ con _)  = New name (map argInfo args) <$> conInfo con
decInfo _                                  = fail "Only type declarations are supported"
conInfo :: Con -> Q (CI STy)
conInfo (NormalC  name ty) = Normal name <$> mapM (convertType . snd) ty
conInfo (RecC     name ty) = Record name <$> mapM (\(s , _ , t) -> (s,) <$> convertType t) ty
conInfo (InfixC l name r)
  = do info <- reifyFixity name
       let fixity = maybe defaultFixity id $ info
       Infix name fixity <$> convertType (snd l) <*> convertType (snd r)
conInfo (ForallC _ _ _) = fail "Existentials not supported"
#if MIN_VERSION_template_haskell(2,11,0)
conInfo (GadtC _ _ _)    = fail "GADTs not supported"
conInfo (RecGadtC _ _ _) = fail "GADTs not supported"
#endif
dtiReduce :: DTI STy -> [STy] -> DTI STy
dtiReduce (ADT name args cons) parms
  = ADT name [] (map (ciReduce (zip args parms)) cons)
dtiReduce (New name args con)  parms
  = New name [] (ciReduce (zip args parms) con)
ciReduce :: [(Name , STy)] -> CI STy -> CI STy
ciReduce parms ci = runIdentity (ciMapM (return . styReduce parms) ci)
data IK
  = AtomI Int
  | AtomK Name
  deriving (Eq , Show)
data Idxs
  = Idxs { idxsNext :: Int
         , idxsMap  :: M.Map STy (Int , Maybe (DTI IK))
         , idxsSyns :: M.Map STy STy
         }
  deriving (Show)
onMap :: (M.Map STy (Int , Maybe (DTI IK)) -> M.Map STy (Int , Maybe (DTI IK)))
      -> Idxs -> Idxs
onMap f (Idxs n m eqs) = Idxs n (f m) eqs
type IdxsM = StateT Idxs
runIdxsM :: (Monad m) => IdxsM m a -> m (a , Idxs)
runIdxsM = flip runStateT (Idxs 0 M.empty M.empty)
type M = IdxsM Q
indexOf :: (Monad m) => STy -> IdxsM m Int
indexOf name
  = do st <- get
       case M.lookup name (idxsSyns st) of
         Just orig -> indexOf orig 
         Nothing ->
           case M.lookup name (idxsMap st) of
             Just i  -> return (fst i)
             Nothing -> let i = idxsNext st
                         in put (Idxs (i + 1)
                                      (M.insert name (i , Nothing) (idxsMap st))
                                      (idxsSyns st))
                         >> return i
register :: (Monad m) => STy -> DTI IK -> IdxsM m ()
register ty info = indexOf ty 
                              
                >> modify (onMap $ M.adjust (id *** const (Just info)) ty)
lkup :: (Monad m) => STy -> IdxsM m (Maybe (Int , Maybe (DTI IK)))
lkup ty = M.lookup ty . idxsMap <$> get
addTySynEquiv :: (Monad m) => STy -> STy -> IdxsM m ()
addTySynEquiv orig new =
  modify (\st -> st { idxsSyns = M.insert new orig (idxsSyns st) })
lkupData :: (Monad m) => STy -> IdxsM m (Maybe (DTI IK))
lkupData ty = join . fmap snd <$> lkup ty
hasData :: (Monad m) => STy -> IdxsM m Bool
hasData ty = maybe False (const True) <$> lkupData ty
reifyOpaqueType :: Name -> Q OpaqueData
reifyOpaqueType opq
  = do triples <- (extract <.> reifyDec) opq
       let (hsTyMap , consMap) = genMaps triples
       return $ OpaqueData opq hsTyMap consMap
  where
    genMaps :: [(Name , Name , Name)] -> (M.Map Name Name , M.Map Name Name)
    genMaps xys = (M.fromList (map (\(x , y , _) -> (x , y)) xys)
                 ,M.fromList (map (\(_ , x , y) -> (x , y)) xys))
    extract :: Dec -> Q [(Name , Name , Name)]
    extract (DataD _ _ _ _ cs _) = mapM extractCon cs
    extract _
      = failMsg
    extractCon :: Con -> Q (Name , Name , Name)
    extractCon (GadtC [opqC] [(_ , ConT hsTy)] (AppT _ (PromotedT ty)))
      = return (hsTy , ty , opqC)
    extractCon _
      = failMsg
    failMsg = fail $ "The opaque-type universe you provided is of the wrong form;"
                  ++ "Check documentation for Generics.MRSOP.TH.reifyOpaqueType"
reifySTy :: OpaqueData -> STy -> M ()
reifySTy opq sty0
  = do _ <- indexOf sty0 
                        
       (dec , args) <- preprocess sty0
       go dec args
  where
    preprocess :: STy -> M (DTI STy , [STy])
    preprocess ty =
      let (head , args) = styFlatten ty
       in case head of
         ConST name -> do
           dec <- lift (reifyDec name)
           resolveTySyn (addTySynEquiv ty) dec args
         _ -> fail "I can't convert appST or varST in reifySTy"
    resolveTySyn :: (STy -> M ()) -> Dec -> [STy] -> M (DTI STy , [STy])
    resolveTySyn upd8 (TySynD _ defargs def) localargs = do
      sdef <- convertType def
      let dict = zip (map argInfo defargs) localargs
      let res = styReduce dict sdef
      upd8 res
      preprocess res
    resolveTySyn _ def localargs = (,localargs) <$> lift (decInfo def)
    go :: DTI STy -> [STy] -> M ()
    go dec args
      = do 
           let res = dtiReduce dec args
           (final , todo) <- runWriterT $ dtiMapM (convertSTy (opaqueTable opq)) res
           register sty0 final
           mapM_ (reifySTy opq) todo
    
    
    convertSTy :: M.Map Name Name -> STy -> WriterT [STy] M IK
    convertSTy opqTable ty
      
      
      | ty == sty0 = AtomI <$> lift (indexOf ty)
      | isClosed ty
      = case makeCons opqTable ty of
          Just k  -> return (AtomK k)
          Nothing -> do ix     <- lift (indexOf ty)
                        hasDti <- lift (hasData ty)
                        when (not hasDti) (tell [ty])
                        return (AtomI ix)
      | otherwise
      = fail $ "I can't convert type variable " ++ show ty
              ++ " when converting " ++ show sty0
    makeCons :: M.Map Name Name -> STy -> Maybe Name
    makeCons opqTable (ConST n) = M.lookup n opqTable
    makeCons _        _         = Nothing
type Input = [(STy , Int , DTI IK)]
tlListOf :: (a -> Type) -> [a] -> Type
tlListOf f = foldr (\h r -> AppT (AppT PromotedConsT (f h)) r) PromotedNilT
int2Type :: Int -> Type
int2Type 0 = tyZ
int2Type n = AppT tyS (int2Type (n - 1))
int2SNatPat :: Int -> Pat
int2SNatPat 0 = ConP (mkName "SZ") []
int2SNatPat n = ConP (mkName "SS") [int2SNatPat $ n-1]
tyS , tyZ , tyI , tyK :: Type
tyS = PromotedT (mkName "S")
tyZ = PromotedT (mkName "Z")
tyI = PromotedT (mkName "I")
tyK = PromotedT (mkName "K")
inputToCodes :: Input -> Q Type
inputToCodes = return . tlListOf dti2Codes . map third
  where
    third (_ , _ , x) = x
dti2Codes :: DTI IK -> Type
dti2Codes = tlListOf ci2Codes . dti2ci
ci2Codes :: CI IK -> Type
ci2Codes = tlListOf ik2Codes . ci2ty
ik2Codes :: IK -> Type
ik2Codes (AtomI n) = AppT tyI $ int2Type n 
ik2Codes (AtomK k) = AppT tyK $ PromotedT k
inputToFam :: Input -> Q Type
inputToFam = return . tlListOf trevnocType . map first
  where
    first (x , _ , _) = x
styToName :: STy -> Name
styToName = mkName . styFold (++) nameBase (fixList . nameBase)
  where
    
    
    fixList :: String -> String
    fixList n
      | n == "[]"        = "List"
      | take 2 n == "(," = "Tup" ++ show (length n - 2)
      | otherwise        = n
onBaseName :: (String -> String) -> Name -> Name
onBaseName f = mkName . f . nameBase
codesName :: STy -> Q Name
codesName = return . onBaseName ("Codes" ++) . styToName
familyName :: STy -> Q Name
familyName = return . onBaseName ("Fam" ++) . styToName
genPiece1 :: STy -> Input -> Q [Dec]
genPiece1 first ls
  = do codes <- TySynD <$> codesName first
                       <*> return []
                       <*> inputToCodes ls
       fam   <- TySynD <$> familyName first
                       <*> return []
                       <*> inputToFam ls
       return [fam , codes]
idxPatSynName :: STy -> Name
idxPatSynName = styToName . (AppST (ConST (mkName "Idx")))
idxPatSyn :: STy -> Pat
idxPatSyn = flip ConP [] . idxPatSynName
genIdxPatSyn :: STy -> Int -> Q Dec
genIdxPatSyn sty ix
  = return (PatSynD (idxPatSynName sty) (PrefixPatSyn []) ImplBidir (int2SNatPat ix))
genPiece2 :: OpaqueData -> STy -> Input -> Q [Dec]
genPiece2 opq first ls
  = do p21  <- mapM (\(sty , ix , _dti) -> genIdxPatSyn sty ix) ls
       p22  <- genPiece2_2 opq first ls
       
       return $ p21 ++ p22
genPiece2_2 :: OpaqueData -> STy -> Input -> Q [Dec]
genPiece2_2 _opq first ls
  = concat <$> mapM (\(sty , ix , dti) -> genTagPatSyns sty ix dti) ls
  where
    genTagPatSyns :: STy -> Int -> DTI IK -> Q [Dec]
    genTagPatSyns sty ix dti
      = concat <$> mapM (uncurry $ genTagPatSynFor ix sty)
                        (zip [0..] $ dti2ci dti)
    genTagPatSynFor :: Int -> STy -> Int -> CI IK -> Q [Dec]
    genTagPatSynFor ix sty cidx ci
      = let fields = ci2ty ci
         in do vars <- mapM (const (newName "p")) fields
               let namedFields = zip fields vars
               name <- patSynName sty cidx ci
               pat <- [p| Tag $(int2Constr cidx) $(tagPatSynProd namedFields) |]
               let pDef = PatSynD name (PrefixPatSyn vars) ImplBidir pat
               phiN <- newName "phi"
               konN <- newName "kon"
               patTy <- genTagPatType ix phiN konN fields
               let pTy = PatSynSigD name patTy
               return [pTy , pDef]
    genTagPatType :: Int -> Name -> Name -> [IK] -> Q Type
    genTagPatType tyIx phi kon (AtomK konst : rest)
      = [t| $(return $ VarT kon) $(return (ConT konst))
            -> $(genTagPatType tyIx phi kon rest) |]
    genTagPatType tyIx phi kon (AtomI ni : rest)
      = [t| $(return (VarT phi)) $(return $ int2Type ni)
            -> $(genTagPatType tyIx phi kon rest) |]
    genTagPatType tyIx phi kon []
      = [t| View $(return $ VarT kon)
                 $(return $ VarT phi)
                 (Lkup $(return $ int2Type tyIx)
                       $(ConT <$> codesName first))
        |]
    patSynName :: STy -> Int -> CI IK -> Q Name
    patSynName sty cidx ci
      | ciHasIllegalName ci
      = let styname = nameBase $ styToName sty
         in return . mkName $ styname ++ "_Ifx" ++ show cidx
    
    
      | ConST _ <- sty
      = return . mkName $ nameBase (ciName ci) ++ "_"
    
    
      | otherwise
      = let styname = nameBase $ styToName sty
         in return . mkName $ styname ++ nameBase (ciName ci) ++ "_"
    ciHasIllegalName :: CI ty -> Bool
    ciHasIllegalName (Infix _ _ _ _) = True
    ciHasIllegalName ci = any (not . isAlphaNum) $ nameBase (ciName ci)
    tagPatSynProd :: [(IK , Name)] -> Q Pat
    tagPatSynProd []     = [p| Nil |]
    tagPatSynProd (h:hs) = [p| $(tagPatSynProdHead h) :* ( $(tagPatSynProd hs) ) |]
    int2Constr :: Int -> Q Pat
    int2Constr 0 = [p| CZ |]
    int2Constr n = [p| CS $(int2Constr (n-1)) |]
    tagPatSynProdHead :: (IK , Name) -> Q Pat
    tagPatSynProdHead (AtomI _ , name) = [p| NA_I $(return . VarP $ name) |]
    tagPatSynProdHead (AtomK _ , name) = [p| NA_K $(return . VarP $ name) |]
genPiece3 :: OpaqueData -> STy -> Input -> Q Dec
genPiece3 opq first ls
  = head <$> [d| instance Family $(return $ ConT $ opaqueName opq)
                                 $(ConT <$> familyName first)
                                 $(ConT <$> codesName first)
                   where sfrom' = $(genPiece3_1 opq ls)
                         sto'   = $(genPiece3_2 opq ls) |]
ci2PatExp :: OpaqueData -> Int -> Int -> CI IK -> Q (Pat , Exp)
ci2PatExp opq _dtiIx cIdx ci
  = do (vars , pat) <- ci2Pat ci
       bdy          <- [e| Rep $(mkInj cIdx $ genBdy (zip vars (ci2ty ci))) |]
       return (ConP (mkName "El") [pat] , bdy)
  where
    mkInj :: Int -> Q Exp -> Q Exp
    mkInj 0 e = [e| SOP.Z $e               |]
    mkInj n e = [e| SOP.S $(mkInj (n-1) e) |]
    genBdy :: [(Name , IK)] -> Q Exp
    genBdy []       = [e| Nil |]
    genBdy (x : xs) = [e| $(mkHead x) :* ( $(genBdy xs) ) |]
    mkHead (x , AtomI _) = [e| NA_I (El $(return (VarE x))) |]
    mkHead (x , AtomK k) = [e| NA_K $(makeK opq k (\r -> AppE (ConE r) (VarE x))) |]
    
ci2ExpPat :: OpaqueData -> Int -> Int -> CI IK -> Q (Pat , Exp)
ci2ExpPat opq _dtiIx cIdx ci
  = do (vars , myexp) <- ci2Exp ci
       pat            <- [p| Rep $(mkInj cIdx $ genBdy (zip vars (ci2ty ci))) |]
       return (pat , AppE (ConE $ mkName "El") myexp)
  where
    mkInj :: Int -> Q Pat -> Q Pat
    mkInj 0 e = [p| SOP.Z $e                |]
    mkInj n e = [p| SOP.S $(mkInj (n-1) e) |]
    genBdy :: [(Name , IK)] -> Q Pat
    genBdy []       = [p| Nil |]
    genBdy (x : xs) = [p| $(mkHead x) :* ( $(genBdy xs) ) |]
    mkHead (x , AtomI _) = [p| NA_I (El $(return (VarP x))) |]
    mkHead (x , AtomK k) = [p| NA_K $(makeK opq k (flip ConP [VarP x])) |]
    
makeK :: OpaqueData -> Name -> (Name -> a) -> Q a
makeK opq n cont
  = case M.lookup n (opaqueCons opq) of
      Nothing -> fail $  "makeK: Can't find constructor for " ++ show n ++ " in opaque def"
      Just c  -> return $ cont c
match :: Pat -> Exp -> Match
match pat bdy = Match pat (NormalB bdy) []
genPiece3_1 :: OpaqueData -> Input -> Q Exp
genPiece3_1 opq input
  = LamCaseE <$> mapM (\(sty , ix , dti) -> clauseForIx sty ix dti) input
  where
    clauseForIx :: STy -> Int -> DTI IK -> Q Match
    clauseForIx sty ix dti = match (idxPatSyn sty)
                       <$> (LamCaseE <$> genMatchFor ix dti)
    genMatchFor :: Int -> DTI IK -> Q [Match]
    genMatchFor ix dti = map (uncurry match) <$> mapM (uncurry $ ci2PatExp opq ix)
                                                      (zip [0..] $ dti2ci dti)
genPiece3_2 :: OpaqueData -> Input -> Q Exp
genPiece3_2 opq input
  = LamCaseE <$> mapM (\(sty , ix , dti) -> clauseForIx sty ix dti) input
  where
    clauseForIx :: STy -> Int -> DTI IK -> Q Match
    clauseForIx sty ix dti = match (idxPatSyn sty)
                       <$> (LamCaseE <$> genMatchFor ix dti)
    genMatchFor :: Int -> DTI IK -> Q [Match]
    genMatchFor ix dti = map (uncurry match) <$> mapM (uncurry $ ci2ExpPat opq ix)
                                                      (zip [0..] $ dti2ci dti)
genPiece4 :: OpaqueData -> STy -> Input -> Q [Dec]
genPiece4 opq first ls
  = [d| instance Meta.HasDatatypeInfo $opqName
                                      $(ConT <$> familyName first)
                                      $(ConT <$> codesName first)
          where datatypeInfo _ = $(genDatatypeInfoClauses ls) |]
  where
    opqName = return (ConT $ opaqueName opq)
    genDatatypeInfoClauses :: Input -> Q Exp
    genDatatypeInfoClauses input
      = LamCaseE <$> mapM genDatatypeInfoMatch input
    genDatatypeInfoMatch :: (STy , Int , DTI IK) -> Q Match
    genDatatypeInfoMatch (sty , idx , dti)
      = match (int2SNatPat idx) <$> genInfo sty dti
    genMod :: Name -> Q Exp
    genMod = strlit . maybe "" id . nameModule
    strlit :: String -> Q Exp
    strlit = return . LitE . StringL
    genDatatypeName :: STy -> Q Exp
    genDatatypeName = styFold (\e1 e2 -> [e| ( $e1 Meta.:@: $e2 ) |])
                              (\n -> [e| Meta.Name $(strlit (nameBase n)) |] )
                              (\n -> [e| Meta.Name $(strlit (nameBase n)) |] )
    genInfo :: STy -> DTI IK -> Q Exp
    genInfo sty (ADT name _ cis)
      = [e| Meta.ADT $(genMod name) $(genDatatypeName sty) $(genConInfoNP cis) |]
    genInfo sty (New name _ ci)
      = [e| Meta.New $(genMod name) $(genDatatypeName sty) $(genConInfo ci) |]
    genConInfo :: CI IK -> Q Exp
    genConInfo (Record conname fields)
      = [e| Meta.Record $(strlit $ nameBase conname) $(genFieldInfo $ map fst fields) |]
    genConInfo (Normal conname _)
      = [e| Meta.Constructor $(strlit $ nameBase conname) |]
    genConInfo (Infix conname fix _ _)
      = [e| Meta.Infix $(strlit $ nameBase conname) $(genAssoc fix) $(genFix fix) |]
      where
        genAssoc (Fixity _ InfixL) = [e| Meta.LeftAssociative  |]
        genAssoc (Fixity _ InfixR) = [e| Meta.RightAssociative |]
        genAssoc (Fixity _ InfixN) = [e| Meta.NotAssociative   |]
        genFix (Fixity i _) = return . LitE . IntegerL . fromIntegral $ i
    genFieldInfo :: [ FieldName ] -> Q Exp
    genFieldInfo []     = [e| Nil |]
    genFieldInfo (f:fs) = [e| Meta.FieldInfo $(strlit . nameBase $ f) :* ( $(genFieldInfo fs) ) |]
    genConInfoNP :: [ CI IK ] -> Q Exp
    genConInfoNP []       = [e| Nil |]
    genConInfoNP (ci:cis) = [e| $(genConInfo ci) :* ( $(genConInfoNP cis) ) |]
genFamily :: OpaqueData -> STy -> Input -> Q [Dec]
genFamily opq first ls
  = do p1 <- genPiece1 first ls      
       p2 <- genPiece2 opq first ls  
       p3 <- genPiece3 opq first ls  
       p4 <- genPiece4 opq first ls  
       return $ p1 ++ p2 ++ [p3] ++ p4
genFamilyDebug :: STy -> [(STy , Int , DTI IK)] -> Q [Dec]
genFamilyDebug _ ms = concat <$> mapM genDec ms
  where
    genDec :: (STy , Int , DTI IK) -> Q [Dec]
    genDec (_sty , ix , dti)
      = [d| $( genPat ix ) = $(mkBody dti) |]
    mkBody :: DTI IK -> Q Exp
    mkBody dti = [e| $(liftString $ show dti) |]
    genPat :: Int -> Q Pat
    genPat n = genName n >>= \name -> return (VarP name)
    genName :: Int -> Q Name
    genName n = return (mkName $ "tyInfo_" ++ show n)