-- | @instantiateVars f l t@ substitutes each dangling bound variable
-- @LocalVar j t@ with the term @f i j t@, where @i@ is the number of
-- binders surrounding @LocalVar j t@.
instantiateVars :: (DeBruijnIndex -> DeBruijnIndex -> Term)
                -> DeBruijnIndex -> Term -> Term
instantiateVars f initialLevel = go initialLevel
  where goList :: DeBruijnIndex -> [Term] -> [Term]
        goList _ []  = []
        goList l (e:r) = go l e : goList (l+1) r

        gof l ftf =
          case ftf of
            PairValue x y -> PairValue (go l x) (go l y)
            PairType a b  -> PairType (go l a) (go l b)
            PairLeft x    -> PairLeft (go l x)
            PairRight x   -> PairRight (go l x)
            RecordValue m -> RecordValue $ go l <$> m
            RecordSelector x fld -> RecordSelector (go l x) fld
            RecordType m      -> RecordType $ go l <$> m
            CtorApp c ll      -> CtorApp c (goList l ll)
            DataTypeApp dt ll -> DataTypeApp dt (goList l ll)
            _ -> ftf
        go :: DeBruijnIndex -> Term -> Term
        go l (Term tf) =
          case tf of
            FTermF ftf ->  Term $ FTermF $ gof l ftf
            App x y         -> Term $ App (go l x) (go l y)
            Constant _ _rhs _ -> Term tf -- assume rhs is a closed term, so leave it unchanged
            Lambda i tp rhs -> Term $ Lambda i (go l tp) (go (l+1) rhs)
            Pi i lhs rhs    -> Term $ Pi i (go l lhs) (go (l+1) rhs)
            Let defs r      -> Term $ Let (procDef <$> defs) (go l' r)
              where l' = l + length defs
                    procDef (Def sym qual tp eqs) = Def sym qual tp' eqs'
                      where tp' = go l tp
                            eqs' = procEq <$> eqs
                    procEq (DefEqn pats rhs) = DefEqn pats (go eql rhs)
                      where eql = l' + sum (patBoundVarCount <$> pats)
            LocalVar i
              | i < l -> Term $ LocalVar i
              | otherwise -> f l i

-- | @incVars j k t@ increments free variables at least @j@ by @k@.
-- e.g., incVars 1 2 (C ?0 ?1) = C ?0 ?3
instantiateVars :: (DeBruijnIndex -> DeBruijnIndex -> Term)
                -> DeBruijnIndex -> Term -> Term
instantiateVars f initialLevel = go initialLevel
  where goList :: DeBruijnIndex -> [Term] -> [Term]
        goList _ []  = []
        goList l (e:r) = go l e : goList (l+1) r

        gof l ftf =
          case ftf of
            PairValue x y -> PairValue (go l x) (go l y)
            PairType a b  -> PairType (go l a) (go l b)
            PairLeft x    -> PairLeft (go l x)
            PairRight x   -> PairRight (go l x)
            RecordValue m -> RecordValue $ go l <$> m
            RecordSelector x fld -> RecordSelector (go l x) fld
            RecordType m      -> RecordType $ go l <$> m
            CtorApp c ll      -> CtorApp c (goList l ll)
            DataTypeApp dt ll -> DataTypeApp dt (goList l ll)
            _ -> ftf
        go :: DeBruijnIndex -> Term -> Term
        go l (Term tf) =
          case tf of
            FTermF ftf ->  Term $ FTermF $ gof l ftf
            App x y         -> Term $ App (go l x) (go l y)
            Constant _ _rhs _ -> Term tf -- assume rhs is a closed term, so leave it unchanged
            Lambda i tp rhs -> Term $ Lambda i (go l tp) (go (l+1) rhs)
            Pi i lhs rhs    -> Term $ Pi i (go l lhs) (go (l+1) rhs)
            Let defs r      -> Term $ Let (procDef <$> defs) (go l' r)
              where l' = l + length defs
                    procDef (Def sym qual tp eqs) = Def sym qual tp' eqs'
                      where tp' = go l tp
                            eqs' = procEq <$> eqs
                    procEq (DefEqn pats rhs) = DefEqn pats (go eql rhs)
                      where eql = l' + sum (patBoundVarCount <$> pats)
            LocalVar i
              | i < l -> Term $ LocalVar i
              | otherwise -> f l i

-- | @incVars j k t@ increments free variables at least @j@ by @k@.
-- e.g., incVars 1 2 (C ?0 ?1) = C ?0 ?3
instantiateVars f initialLevel = go initialLevel
  where goList :: DeBruijnIndex -> [Term] -> [Term]
        goList _ []  = []
        goList l (e:r) = go l e : goList (l+1) r

        gof l ftf =
          case ftf of
            PairValue x y -> PairValue (go l x) (go l y)
            PairType a b  -> PairType (go l a) (go l b)
            PairLeft x    -> PairLeft (go l x)
            PairRight x   -> PairRight (go l x)
            RecordValue m -> RecordValue $ go l <$> m
            RecordSelector x fld -> RecordSelector (go l x) fld
            RecordType m      -> RecordType $ go l <$> m
            CtorApp c ll      -> CtorApp c (goList l ll)
            DataTypeApp dt ll -> DataTypeApp dt (goList l ll)
            _ -> ftf
        go :: DeBruijnIndex -> Term -> Term
        go l (Term tf) =
          case tf of
            FTermF ftf ->  Term $ FTermF $ gof l ftf
            App x y         -> Term $ App (go l x) (go l y)
            Constant _ _rhs _ -> Term tf -- assume rhs is a closed term, so leave it unchanged
            Lambda i tp rhs -> Term $ Lambda i (go l tp) (go (l+1) rhs)
            Pi i lhs rhs    -> Term $ Pi i (go l lhs) (go (l+1) rhs)
            Let defs r      -> Term $ Let (procDef <$> defs) (go l' r)
              where l' = l + length defs
                    procDef (Def sym qual tp eqs) = Def sym qual tp' eqs'
                      where tp' = go l tp
                            eqs' = procEq <$> eqs
                    procEq (DefEqn pats rhs) = DefEqn pats (go eql rhs)
                      where eql = l' + sum (patBoundVarCount <$> pats)
            LocalVar i
              | i < l -> Term $ LocalVar i
              | otherwise -> f l i

-- | @incVars j k t@ increments free variables at least @j@ by @k@.
-- e.g., incVars 1 2 (C ?0 ?1) = C ?0 ?3