{-# LANGUAGE DataKinds, TypeFamilies, KindSignatures, TemplateHaskell, CPP #-}
module Data.Singletons.CustomStar (
  singletonStar,
  module Data.Singletons.Prelude.Eq,
  module Data.Singletons.Prelude.Bool,
  module Data.Singletons.TH
  ) where
import Language.Haskell.TH
import Data.Singletons.Util
import Data.Singletons.Deriving.Infer
import Data.Singletons.Deriving.Ord
import Data.Singletons.Deriving.Show
import Data.Singletons.Promote
import Data.Singletons.Promote.Monad
import Data.Singletons.Single.Monad
import Data.Singletons.Single.Data
import Data.Singletons.Single
import Data.Singletons.Syntax
import Data.Singletons.Names
import Data.Singletons.TH
import Control.Monad
import Data.Maybe
import Language.Haskell.TH.Desugar
import Data.Singletons.Prelude.Eq
import Data.Singletons.Prelude.Bool
singletonStar :: DsMonad q
              => [Name]        
              -> q [Dec]
singletonStar names = do
  kinds <- mapM getKind names
  ctors <- zipWithM (mkCtor True) names kinds
  let repDecl = DDataD Data [] repName [] (Just (DConT typeKindName)) ctors
                         [DDerivClause Nothing (map DConPr [''Eq, ''Ord, ''Read, ''Show])]
  fakeCtors <- zipWithM (mkCtor False) names kinds
  let dataDecl = DataDecl repName [] fakeCtors
  
  
  
  
  withLocalDeclarations (decToTH repDecl) $ do
    
    
    dataDeclEqCxt <- inferConstraints (DConPr ''Eq) (DConT repName) fakeCtors
    let dataDeclEqInst = DerivedDecl (Just dataDeclEqCxt) (DConT repName) dataDecl
    ordInst  <- mkOrdInstance Nothing (DConT repName) dataDecl
    showInst <- mkShowInstance Nothing (DConT repName) dataDecl
    (pInsts, promDecls) <- promoteM [] $ do promoteDataDec dataDecl
                                            promoteDerivedEqDec dataDeclEqInst
                                            traverse (promoteInstanceDec mempty)
                                              [ordInst, showInst]
    singletonDecls <- singDecsM [] $ do decs1 <- singDataD dataDecl
                                        decs2 <- singDerivedEqDecs dataDeclEqInst
                                        decs3 <- traverse singInstD pInsts
                                        return (decs1 ++ decs2 ++ decs3)
    return $ decsToTH $ repDecl :
                        promDecls ++
                        singletonDecls
  where 
        getKind :: DsMonad q => Name -> q [DKind]
        getKind name = do
          info <- reifyWithLocals name
          dinfo <- dsInfo info
          case dinfo of
            DTyConI (DDataD _ (_:_) _ _ _ _ _) _ ->
               fail "Cannot make a representation of a constrained data type"
            DTyConI (DDataD _ [] _ tvbs mk _ _) _ -> do
               all_tvbs <- buildDataDTvbs tvbs mk
               return $ map (fromMaybe (DConT typeKindName) . extractTvbKind) all_tvbs
            DTyConI (DTySynD _ tvbs _) _ ->
               return $ map (fromMaybe (DConT typeKindName) . extractTvbKind) tvbs
            DPrimTyConI _ n _ ->
               return $ replicate n $ DConT typeKindName
            _ -> fail $ "Invalid thing for representation: " ++ (show name)
        
        
        mkCtor :: DsMonad q => Bool -> Name -> [DKind] -> q DCon
        mkCtor real name args = do
          (types, vars) <- evalForPair $ mapM (kindToType []) args
          dataName <- if real then mkDataName (nameBase name) else return name
          return $ DCon (map DPlainTV vars) [] dataName
                        (DNormalC False (map (\ty -> (noBang, ty)) types))
                        (DConT repName)
            where
              noBang = Bang NoSourceUnpackedness NoSourceStrictness
        
        kindToType :: DsMonad q => [DType] -> DKind -> QWithAux [Name] q DType
        kindToType _    (DForallT _ _ _) = fail "Explicit forall encountered in kind"
        kindToType args (DAppT f a) = do
          a' <- kindToType [] a
          kindToType (a' : args) f
        kindToType args (DSigT t k) = do
          t' <- kindToType [] t
          k' <- kindToType [] k
          return $ DSigT t' k' `foldType` args
        kindToType args (DVarT n) = do
          addElement n
          return $ DVarT n `foldType` args
        kindToType args (DConT n)    = return $ DConT name    `foldType` args
          where name | isTypeKindName n = repName
                     | otherwise        = n
        kindToType args DArrowT      = return $ DArrowT       `foldType` args
        kindToType args k@(DLitT {}) = return $ k             `foldType` args
        kindToType args DWildCardT   = return $ DWildCardT    `foldType` args