-- | Capture avoiding substitution of types in types.
module DDC.Type.Transform.SubstituteT
        ( SubstituteT(..)
        , substituteT
        , substituteTs
        , substituteBoundT

        , BindStack(..)
        , pushBind
        , pushBinds
        , substBound)
import DDC.Type.Exp
import DDC.Type.Compounds
import DDC.Core.Collect
import DDC.Type.Transform.LiftT
import DDC.Type.Transform.Crush
import DDC.Type.Transform.Trim
import DDC.Type.Rewrite
import Data.Maybe
import qualified DDC.Type.Sum   as Sum
import qualified DDC.Type.Env   as Env
import qualified Data.Set       as Set
import Data.Set                 (Set)

-- | Substitute a `Type` for the `Bound` corresponding to some `Bind` in a thing.
substituteT :: (SubstituteT c, Ord n) => Bind n -> Type n -> c n -> c n
substituteT b t x
 = case takeSubstBoundOfBind b of
    Just u      -> substituteBoundT u t x
    _           -> x

-- | Wrapper for `substituteT` to substitute multiple things.
substituteTs :: (SubstituteT c, Ord n) => [(Bind n, Type n)] -> c n -> c n
substituteTs bts x
        = foldr (uncurry substituteT) x bts

-- | Substitute a `Type` for `Bound` in some thing.
substituteBoundT :: (SubstituteT c, Ord n) => Bound n -> Type n -> c n -> c n
substituteBoundT u t x
 = let -- Determine the free names in the type we're subsituting.
       -- We'll need to rename binders with the same names as these
       freeNames       = Set.fromList
                       $ mapMaybe takeNameOfBound 
                       $ Set.toList 
                       $ freeT Env.empty t

       stack           = BindStack [] [] 0 0
  in   substituteWithT u t freeNames stack x

-- SubstituteT ----------------------------------------------------------------
class SubstituteT (c :: * -> *) where

 -- | Substitute a type into some thing.
 --   In the target, if we find a named binder that would capture a free variable
 --   in the type to substitute, then we rewrite that binder to anonymous form,
 --   avoiding the capture.
        :: forall n. Ord n
        => Bound n       -- ^ Bound variable that we're subsituting into.
        -> Type n        -- ^ Type to substitute.
        -> Set  n        -- ^ Names of free varaibles in the type to substitute.
        -> BindStack n   -- ^ Bind stack.
        -> c n -> c n

-- Instances ------------------------------------------------------------------
instance SubstituteT Bind where
 substituteWithT u fvs t stack bb
  = let k'      = substituteWithT u fvs t stack $ typeOfBind bb
    in  replaceTypeOfBind k' bb
instance SubstituteT Type where
 substituteWithT u t fns stack tt
  = let down    = substituteWithT u t fns stack
    in  case tt of
         TCon{}          -> tt

         -- Crush out compound effects and closures as we substitute them.
         TApp t1 t2
          -> case t1 of
                TCon (TyConSpec TcConHeadRead)  
                  -> crushEffect      (TApp t1 (down t2))

                TCon (TyConSpec TcConDeepRead)  
                  -> crushEffect      (TApp t1 (down t2))

                TCon (TyConSpec TcConDeepWrite) 
                  -> crushEffect      (TApp t1 (down t2))

                TCon (TyConSpec TcConDeepAlloc) 
                  -> crushEffect      (TApp t1 (down t2))

                -- If the closure is miskinded then trimClosure can 
                -- return Nothing, so we leave it untrimmed.
                TCon (TyConSpec TcConDeepUse)
                  -> fromMaybe tt (trimClosure (TApp t1 (down t2)))

                _ -> TApp (down t1) (down t2)

         TSum ss        
          -> TSum (down ss)

         TForall b tBody
          | namedBoundMatchesBind u b -> tt
          | otherwise
          -> let -- Substitute into the annotation on the binder.
                 bSub            = down b

                 -- Push bind onto stack, and anonymise to avoid capture if needed
                 (stack', b')    = pushBind fns stack bSub
                 -- Substitute into body.
                 tBody'          = substituteWithT u t fns stack' tBody

             in  TForall b' tBody'

         TVar u'
          -> case substBound stack u u' of
                Left  u'' -> TVar u''
                Right n   -> liftT n t

instance SubstituteT TypeSum where
 substituteWithT u n fns stack ss
  = let k       = substituteWithT u n fns stack
                $ Sum.kindOfSum ss
    in  Sum.fromList k 
                $ map (substituteWithT u n fns stack)
                $ Sum.toList ss