module FreeC.IR.TypeSynExpansion
  ( 
    expandAllTypeSynonymsInDecl
  , expandAllTypeSynonymsInDeclWhere
     
  , expandAllTypeSynonymsInConDecl
  , expandAllTypeSynonymsInConDeclWhere
     
  , expandTypeSynonym
  , expandAllTypeSynonyms
  , expandAllTypeSynonymsWhere
  , expandTypeSynonyms
  , expandTypeSynonymsWhere
     
  , expandTypeSynonymAt
  ) where
import           Control.Applicative         ( (<|>) )
import           Control.Monad.Trans.Maybe   ( MaybeT(..) )
import           Data.Maybe                  ( fromMaybe )
import           FreeC.Environment
import           FreeC.IR.Subst
import           FreeC.IR.Subterm
import qualified FreeC.IR.Syntax             as IR
import           FreeC.Monad.Class.Hoistable ( hoistMaybe )
import           FreeC.Monad.Converter
expandAllTypeSynonymsInDecl :: IR.TypeDecl -> Converter IR.TypeDecl
expandAllTypeSynonymsInDecl = expandAllTypeSynonymsInDeclWhere (const True)
expandAllTypeSynonymsInDeclWhere
  :: (IR.QName -> Bool) -> IR.TypeDecl -> Converter IR.TypeDecl
expandAllTypeSynonymsInDeclWhere predicate
  (IR.TypeSynDecl srcSpan declIdent typeVarDecls typeExpr) = do
    typeExpr' <- expandAllTypeSynonymsWhere predicate typeExpr
    return (IR.TypeSynDecl srcSpan declIdent typeVarDecls typeExpr')
expandAllTypeSynonymsInDeclWhere predicate
  (IR.DataDecl srcSpan declIdent typeVarDecls conDecls) = do
    conDecls' <- mapM (expandAllTypeSynonymsInConDeclWhere predicate) conDecls
    return (IR.DataDecl srcSpan declIdent typeVarDecls conDecls')
expandAllTypeSynonymsInConDecl :: IR.ConDecl -> Converter IR.ConDecl
expandAllTypeSynonymsInConDecl = expandAllTypeSynonymsInConDeclWhere
  (const True)
expandAllTypeSynonymsInConDeclWhere
  :: (IR.QName -> Bool) -> IR.ConDecl -> Converter IR.ConDecl
expandAllTypeSynonymsInConDeclWhere predicate
  (IR.ConDecl srcSpan declIdent argTypes) = do
    argTypes' <- mapM (expandAllTypeSynonymsWhere predicate) argTypes
    return (IR.ConDecl srcSpan declIdent argTypes')
expandTypeSynonym :: IR.Type -> Converter IR.Type
expandTypeSynonym = expandTypeSynonyms 1
expandAllTypeSynonyms :: IR.Type -> Converter IR.Type
expandAllTypeSynonyms = expandAllTypeSynonymsWhere (const True)
expandAllTypeSynonymsWhere
  :: (IR.QName -> Bool) -> IR.Type -> Converter IR.Type
expandAllTypeSynonymsWhere = expandTypeSynonymsWhere (-1)
expandTypeSynonyms :: Int -> IR.Type -> Converter IR.Type
expandTypeSynonyms = flip expandTypeSynonymsWhere (const True)
expandTypeSynonymsWhere
  :: Int -> (IR.QName -> Bool) -> IR.Type -> Converter IR.Type
expandTypeSynonymsWhere maxDepth predicate t0
  | maxDepth == 0 = return t0
  | otherwise = do
    t0' <- expandTypeSynonyms' t0 []
    return (fromMaybe t0 t0')
 where
  expandTypeSynonyms' :: IR.Type -> [IR.Type] -> Converter (Maybe IR.Type)
  expandTypeSynonyms' (IR.TypeCon _ typeConName) args = do
    mTypeSynonym <- inEnv $ lookupTypeSynonym typeConName
    case mTypeSynonym of
      Just (typeVars, typeExpr)
        | predicate typeConName -> do
          let subst     = composeSubsts
                (zipWith (singleSubst . IR.UnQual . IR.Ident) typeVars args)
              typeExpr' = applySubst subst typeExpr
          Just <$> expandTypeSynonymsWhere (maxDepth - 1) predicate typeExpr'
      _ -> return Nothing
  expandTypeSynonyms' (IR.TypeApp srcSpan t1 t2) args = do
    t2' <- expandTypeSynonymsWhere (maxDepth - 1) predicate t2
    let args' = t2' : args
    t1' <- expandTypeSynonyms' t1 args'
    return (t1' <|> Just (IR.typeApp srcSpan t1 args'))
  expandTypeSynonyms' (IR.FuncType srcSpan t1 t2) _   = do
    t1' <- expandTypeSynonymsWhere (maxDepth - 1) predicate t1
    t2' <- expandTypeSynonymsWhere (maxDepth - 1) predicate t2
    return (Just (IR.FuncType srcSpan t1' t2'))
  expandTypeSynonyms' (IR.TypeVar _ _) _              = return Nothing
expandTypeSynonymAt :: Pos -> IR.Type -> Converter IR.Type
expandTypeSynonymAt pos typeExpr = case parentPos pos of
  Just pos' | maybe False isTypeApp (selectSubterm typeExpr pos') ->
              expandTypeSynonymAt pos' typeExpr
  _         -> fmap (fromMaybe typeExpr) $ runMaybeT $ do
    subterm <- hoistMaybe $ selectSubterm typeExpr pos
    subterm' <- lift $ expandTypeSynonym subterm
    hoistMaybe $ replaceSubterm typeExpr pos subterm'
 where
  
  
  isTypeApp :: IR.Type -> Bool
  isTypeApp (IR.TypeApp _ _ _) = True
  isTypeApp _                  = False