Theory Term_Subst
section "More on Substitutions"
theory Term_Subst
  imports Term
begin
fun subst_typ :: "((variable × sort) × typ) list ⇒ typ ⇒ typ" where
  "subst_typ insts (Ty a Ts) = 
    Ty a (map (subst_typ insts) Ts)"
| "subst_typ insts (Tv idn S) = the_default (Tv idn S) 
    (lookup (λx . x = (idn, S)) insts)"
lemma subst_typ_nil[simp]: "subst_typ [] T = T" 
  by (induction T) (auto simp add: map_idI)
 
lemma subst_typ_irrelevant_order:
  assumes "distinct (map fst pairs)" and "distinct (map fst pairs')" and "set pairs = set pairs'"
shows "subst_typ pairs T = subst_typ pairs' T"
  using assms
proof(induction T)
  case (Ty n Ts)
  then show ?case by (induction Ts) auto
next
  case (Tv idn S)
  then show ?case using lookup_eq_order_irrelevant by (metis subst_typ.simps(2))
qed
lemma subst_typ_simulates_tsubstT_gen': "distinct l ⟹ tvsT T ⊆ set l 
  ⟹ tsubstT T ρ = subst_typ (map (λ(x,y).((x,y), ρ x y)) l) T"
proof(induction T arbitrary: l)
  case (Ty n Ts)
  then show ?case by (induction Ts) auto
next
  case (Tv idn S)
  hence d: "distinct (map fst (map (λ(x,y).((x,y), ρ x y)) l))" 
    by (simp add: case_prod_beta map_idI)
  hence el: "((idn,S), ρ idn S) ∈ set (map (λa. case a of (x, y) ⇒ ((x, y), ρ x y)) l)" 
    using Tv by auto
  show ?case using iffD1[OF lookup_present_eq_key, OF _ el]  Tv.prems d by auto
qed
lemma subst_typ_simulates_tsubstT_gen: "tsubstT T ρ 
  = subst_typ (map (λ(x,y).((x,y), ρ x y)) (SOME l . distinct l ∧ tvsT T ⊆ set l)) T"
proof(rule someI2_ex)
  show "∃a. distinct a ∧ tvsT T ⊆ set a"
    using finite_tvsT finite_distinct_list
    by (metis order_refl)
next
  fix l assume l: "distinct l ∧ tvsT T ⊆ set l"
  then show "tsubstT T ρ = subst_typ (map (λa. case a of (x, y) ⇒ ((x, y), ρ x y)) l) T"
    using subst_typ_simulates_tsubstT_gen' by blast
qed
corollary subst_typ_simulates_tsubstT: "tsubstT T ρ 
  = subst_typ (map (λ(x,y).((x,y), ρ x y)) (SOME l . distinct l ∧ set l = tvsT T)) T"
  apply (rule someI2_ex) 
  using finite_tvsT finite_distinct_list apply metis
  using subst_typ_simulates_tsubstT_gen' apply simp
  done
lemma tsubstT_simulates_subst_typ: "subst_typ insts T
  = tsubstT T (λidn S . the_default (Tv idn S) (lookup (λx. x=(idn, S)) insts))"
  by (induction T) auto
lemma subst_typ_comp: 
  "subst_typ inst1 (subst_typ inst2 T) = subst_typ (map (apsnd (subst_typ inst1)) inst2 @ inst1) T"
proof (induction inst2 T arbitrary: inst1 rule: subst_typ.induct)
  case (1 insts a Ts)
  then show ?case
    by auto
next
  case (2 insts idn S)
  then show ?case 
    by (induction insts) auto
qed
lemma subst_typ_AList_clearjunk: "subst_typ insts T = subst_typ (AList.clearjunk insts) T"
proof (induction T)
  case (Ty n Ts)
  then show ?case 
    by auto
next
  case (Tv n S)
  then show ?case
  proof(induction insts)
    case Nil
    then show ?case
      by auto
  next
    case (Cons inst insts)
    then show ?case 
      by simp (metis clearjunk.simps(2) lookup_AList_clearjunk)
  qed
qed
  
fun subst_type_term :: "((variable × sort) × typ) list ⇒ 
    ((variable × typ) × term) list ⇒ term ⇒ term" where
  "subst_type_term instT insts (Ct c T) = Ct c (subst_typ instT T)"
| "subst_type_term instT insts (Fv idn T) = (let T' = subst_typ instT T in
    the_default (Fv idn T') (lookup (λx. x = (idn, T')) insts))"
| "subst_type_term _ _ (Bv n) = Bv n"
| "subst_type_term instT insts (Abs T t) = Abs (subst_typ instT T) (subst_type_term instT insts t)"
| "subst_type_term instT insts (t $ u) = subst_type_term instT insts t $ subst_type_term instT insts u"
lemma subst_type_term_empty_no_change[simp]: "subst_type_term [] [] t = t"
  by (induction t) (simp_all add:)
lemma subst_type_term_irrelevant_order:
  assumes instT_assms: "distinct (map fst instT)" "distinct (map fst instT')" "set instT = set instT'"
  assumes insts_assms: "distinct (map fst insts)" "distinct (map fst insts')" "set insts = set insts'"
shows "subst_type_term instT insts t = subst_type_term instT' insts' t"
  using assms
proof(induction t)
  case (Fv idn T)
  then show ?case 
    apply (simp add: Let_def subst_typ_irrelevant_order[OF Fv.prems(1-3)])
    using lookup_eq_order_irrelevant by (metis Fv.prems(4) Fv.prems(5) insts_assms)
next
  case (Abs T t)
  then show ?case using subst_typ_irrelevant_order[OF instT_assms] by simp
qed (simp_all add: subst_typ_irrelevant_order[OF instT_assms])
lemma subst_type_term_simulates_subst_tsubst_gen':
  assumes lty_assms: "distinct lty" "tvs t ⊆ set lty"
  assumes lt_assms: "distinct lt" "fv (tsubst t ρty) ⊆ set lt"
  shows "subst (tsubst t ρty) ρt 
    = subst_type_term (map (λ(x,y).((x,y), ρty x y)) lty) (map (λ(x,y).((x,y), ρt x y)) lt) t"
proof-
  let ?lty = "map (λ(x,y).((x,y), ρty x y)) lty"
  have p1ty: "distinct (map fst ?lty)" using lty_assms
    by (simp add: case_prod_beta map_idI)
  let ?lt = "map (λ(x,y).((x,y), ρt x y)) lt"
  have p1t: "distinct (map fst ?lt)" using lt_assms
    by (simp add: case_prod_beta map_idI)
  show ?thesis using assms
  proof(induction t arbitrary: lty lt)
    case (Fv idn T)
  
    let ?T = "tsubstT T ρty"  
    have el: "((idn, ?T), ρt idn ?T) ∈ set (map (λ(x,y).((x,y), ρt x y)) lt)" 
      using Fv by auto
    have d: "distinct (map fst (map (λ(x,y).((x,y), ρt x y)) lt))" 
      using Fv by (simp add: case_prod_beta map_idI)
    show ?case using  Fv.prems d 
      by (auto simp add: iffD1[OF lookup_present_eq_key, OF d el] 
          subst_typ_simulates_tsubstT_gen'[symmetric] Let_def)
  qed (simp_all add: subst_typ_simulates_tsubstT_gen')
qed
corollary subst_type_term_simulates_subst_tsubst: "subst (tsubst t ρty) ρt 
    = subst_type_term (map (λ(x,y).((x,y), ρty x y)) (SOME lty . distinct lty ∧ tvs t = set lty)) 
      (map (λ(x,y).((x,y), ρt x y)) (SOME lt . distinct lt ∧ fv (tsubst t ρty) = set lt)) t"
  apply (rule someI2_ex)
  using finite_fv finite_distinct_list apply metis
  apply (rule someI2_ex) 
  using finite_tvs finite_distinct_list apply metis
  using subst_type_term_simulates_subst_tsubst_gen' by simp
abbreviation "subst_typ' pairs t ≡ map_types (subst_typ pairs) t"
lemma subst_typ'_nil[simp]: "subst_typ' [] A = A" 
  by (induction A) (auto simp add:)
lemma subst_typ'_simulates_tsubst_gen': "distinct pairs ⟹ tvs t ⊆ set pairs
  ⟹ tsubst t ρ = subst_typ' (map (λ(x,y).((x,y), ρ x y)) pairs) t"
  by (induction t arbitrary: pairs ρ) 
    (auto simp add: subst_typ_simulates_tsubstT_gen')
lemma subst_typ'_simulates_tsubst_gen: "tsubst t ρ 
  = subst_typ' (map (λ(x,y).((x,y), ρ x y)) (SOME l . distinct l ∧ tvs t ⊆ set l)) t"
proof(rule someI2_ex)
  show "∃a. distinct a ∧ tvs t ⊆ set a"
    using finite_tvs finite_distinct_list
    by (metis order_refl)
next
  fix l assume l: "distinct l ∧ tvs t ⊆ set l"
  
  then show "tsubst t ρ = subst_typ' (map (λa. case a of (x, y) ⇒ ((x, y), ρ x y)) l) t"
    using subst_typ'_simulates_tsubst_gen' by blast
qed
lemma tsubst_simulates_subst_typ': "subst_typ' insts T
  = tsubst T (λidn S . the_default (Tv idn S) (lookup (λx. x=(idn, S)) insts))"
  by (induction T) (auto simp add: tsubstT_simulates_subst_typ)
lemma subst_type_add_degenerate_instance: 
  "(idx,s) ∉ set (map fst insts) ⟹ subst_typ insts T = subst_typ (((idx,s), Tv idx s)#insts) T"
  by (induction T) (auto simp add: lookup_eq_key_not_present)
lemma subst_typ'_add_degenerate_instance: 
  "(idx,s) ∉ set (map fst insts) ⟹ subst_typ' insts t = subst_typ' (((idx,s), Tv idx s)#insts) t"
  by (induction t) (auto simp add: subst_type_add_degenerate_instance)
lemma subst_typ'_comp: 
  "subst_typ' inst1 (subst_typ' inst2 t) = subst_typ' (map (apsnd (subst_typ inst1)) inst2 @ inst1) t"
  by (induction t) (use subst_typ_comp in auto)
lemma subst_typ'_AList_clearjunk: "subst_typ' insts t = subst_typ' (AList.clearjunk insts) t"
  by (induction t) (use subst_typ_AList_clearjunk in auto)
fun subst_term :: "((variable * typ) * term) list ⇒ term ⇒ term" where
  "subst_term insts (Ct c T) = Ct c T"
| "subst_term insts (Fv idn T) = the_default (Fv idn T) (lookup (λx. x=(idn, T)) insts)"
| "subst_term _ (Bv n) = Bv n"
| "subst_term insts (Abs T t) = Abs T (subst_term insts t)"
| "subst_term insts (t $ u) = subst_term  insts t $ subst_term insts u"
lemma subst_term_empty_no_change[simp]: "subst_term [] t = t" 
  by (induction t) auto
lemma subst_type_term_without_type_insts_eq_subst_term[simp]: 
  "subst_type_term [] insts t = subst_term insts t"
  by (induction insts t rule: subst_term.induct) simp_all
lemma subst_type_term_split_levels: 
  "subst_type_term instT insts t = subst_term insts (subst_typ' instT t)"
  by (induction t) (auto simp add: Let_def)
lemma subst_typ_stepwise:
  assumes "distinct (map fst instT)"
  assumes "⋀x . x ∈ (⋃t ∈ snd ` set instT . tvsT t) ⟹ x ∉ fst ` set instT"
  shows "subst_typ instT T = fold (λsingle acc . subst_typ [single] acc) instT T"
using assms proof (induction instT T rule: subst_typ.induct)
  case (1 inst a Ts)
  then show ?case
  proof (induction Ts arbitrary: inst)
    case Nil
    then show ?case by (induction inst) auto
  next
    case (Cons T Ts)
    hence "subst_typ inst (Ty a Ts) = fold (λsingle. subst_typ [single]) inst (Ty a Ts)" 
      by simp
    moreover have "subst_typ inst T = fold (λsingle. subst_typ [single]) inst T" 
        using Cons 1 by simp
    moreover have "fold (λsingle. subst_typ [single]) inst (Ty a (T#Ts)) 
      = (Ty a (map (fold (λsingle. subst_typ [single]) inst) (T#Ts)))"
    proof (induction inst rule: rev_induct)
      case Nil
      then show ?case by simp
    next
      case (snoc x xs)
      hence "fold (λsingle. subst_typ [single]) (xs @ [x]) (Ty a (T # Ts)) =
        Ty a (map (subst_typ [x]) (map (fold (λsingle. subst_typ [single]) xs) (T # Ts)))"
        by simp
      then show ?case by simp
    qed
    ultimately show ?case
      using Cons.prems(1) Cons.prems(2) local.Cons(4) by auto
  qed
next
  case (2 inst idn S)
  then show ?case
  proof (cases "lookup (λx . x = (idn, S)) (inst)")
    case None
    hence "fst p ≠ (idn, S)" if "p∈set inst" for p using that by (auto simp add: lookup_None_iff)
    hence "subst_typ [p] (Tv idn S) = Tv idn S" if "p∈set inst" for p 
      using that by (cases p) fastforce
    from this None show ?thesis by (induction inst) (auto split: if_splits)
  next
    case (Some a)
    have elem: "((idn, S), a) ∈ set inst" using Some lookup_present_eq_key'' 2 by fastforce 
    from this obtain fs bs where split: "inst = fs @ ((idn, S), a) # bs"
      by (meson split_list) 
    hence "(idn, S) ∉ set (map fst fs)" and "(idn, S) ∉ set (map fst bs)" using 2 by simp_all
    hence "fst p ≠ (idn, S)" if "p∈set fs" for p 
      using that by force
    hence id_subst_fs: "subst_typ [p] (Tv idn S) = Tv idn S" if "p∈set fs" for p
      using that by (cases p) fastforce
    hence fs_step: "fold (λsingle. subst_typ [single]) fs (Tv idn S) = Tv idn S"
      by (induction fs) (auto split: if_splits)
    have change_step: "subst_typ [((idn, S), a)] (Tv idn S) = a" by simp
    have bs_sub: "set bs ⊆ set inst" using split by auto
    hence "x ∉ fst ` set bs" 
      if "x∈ ⋃ (tvsT ` snd ` set bs)" for x
      using 2 that split by (auto simp add: image_iff)
    have "v ∉ fst ` set bs" if "v ∈ tvsT a" for v
      using that 2 elem bs_sub by (fastforce simp add: image_iff)
    
    hence id_subst_bs: "subst_typ [p] a = a" if "p ∈ set bs" for p
    using that proof(cases p, induction a)
      case (Ty n Ts)
      then show ?case
        by (induction Ts) auto
    next
      case (Tv n S)
      then show ?case
        by force
    qed
    hence bs_step: "fold (λsingle. subst_typ [single]) bs a = a"
      by (induction bs) auto
    from fs_step change_step bs_step split Some show ?thesis by simp 
  qed
qed
corollary subst_typ_split_first:
  assumes "distinct (map fst (x#xs))"
  assumes "⋀y . y ∈ (⋃t ∈ snd ` set (x#xs) . tvsT t) ⟹ y ∉ fst ` (set (x#xs))"
  shows "subst_typ (x#xs) T = subst_typ xs (subst_typ [x] T)"
proof-
  have "subst_typ (x#xs) T = fold (λsingle . subst_typ [single]) (x#xs) T" 
    using assms subst_typ_stepwise by blast
  also have "… = fold (λsingle . subst_typ [single]) xs (subst_typ [x] T)"
    by simp
  also have "… = subst_typ xs (subst_typ [x] T)"
    using assms subst_typ_stepwise by simp
  finally show ?thesis .
qed
corollary subst_typ_split_last:
  assumes "distinct (map fst (xs @ [x]))"
  assumes "⋀y . y ∈ (⋃t ∈ snd ` (set (xs @ [x])) . tvsT t) ⟹ y ∉ fst ` (set (xs @ [x]))"
  shows "subst_typ (xs @ [x]) T = subst_typ [x] (subst_typ xs T)"
proof-
  have "subst_typ (xs @ [x]) T = fold (λsingle . subst_typ [single]) (xs@[x]) T" 
    using assms subst_typ_stepwise by blast
  also have "… = subst_typ [x] (fold (λsingle . subst_typ [single]) xs T)"
    by simp
  also have "… = subst_typ [x] (subst_typ xs T)"
    using assms subst_typ_stepwise by simp
  finally show ?thesis .
qed
lemma subst_typ'_stepwise:
  assumes "distinct (map fst instT)"
  assumes "⋀x . x ∈ (⋃t ∈ snd ` (set instT) . tvsT t) ⟹ x ∉ fst ` (set instT)"
  shows "subst_typ' instT t = fold (λsingle acc . subst_typ' [single] acc) instT t"
using assms proof (induction instT arbitrary: t rule: rev_induct)
  case Nil
  then show ?case by simp 
next
  case (snoc x xs)
  then show ?case
    apply (induction t) 
    using subst_typ_split_last apply simp_all
    apply (metis map_types.simps)+ 
    done
qed
lemma subst_term_stepwise:
  assumes "distinct (map fst insts)"
  assumes "⋀x . x ∈ (⋃t ∈ snd ` (set insts) . fv t) ⟹ x ∉ fst ` (set insts)"
  shows "subst_term insts t = fold (λsingle acc . subst_term [single] acc) insts t"
using assms proof (induction insts arbitrary: t rule: rev_induct)
  case Nil
  then show ?case by simp
next
  case (snoc x xs)
  then show ?case
  proof (induction t)
    case (Fv idn T)
    
    define insts where insts_def: "insts = xs @ [x]"
    have insts_thm1: "distinct (map fst insts)" using insts_def snoc by simp
    have insts_thm2: "x ∉ fst ` set insts" if "x ∈ ⋃ (fv ` snd ` set insts)" for x
      using insts_def snoc that by blast
    from Fv show ?case 
    
    proof (cases "lookup (λx . x = (idn, T)) insts")
      case None
      hence "fst p ≠ (idn, T)" if "p∈set insts" for p using that by (auto simp add: lookup_None_iff)
      hence "subst_term [p] (Fv idn T) = Fv idn T" if "p∈set insts" for p 
        using that by (cases p) fastforce
      from this None show ?thesis 
        unfolding insts_def[symmetric] 
        by (induction insts) (auto split: if_splits)
    next
      case (Some a)
  
      have elem: "((idn, T), a) ∈ set insts" using Some lookup_present_eq_key'' insts_thm1 by fastforce
      from this obtain fs bs where split: "insts = fs @ ((idn, T), a) # bs"
        by (meson split_list) 
      hence "(idn, T) ∉ set (map fst fs)" and "(idn, T) ∉ set (map fst bs)" using insts_thm1 by simp_all
  
      hence "fst p ~= (idn, T)" if "p∈set fs" for p 
        using that by force
      hence id_subst_fs: "subst_term [p] (Fv idn T) = Fv idn T" if "p∈set fs" for p
        using that by (cases p) fastforce
      hence fs_step: "fold (λsingle. subst_term [single]) fs (Fv idn T) = Fv idn T"
        by (induction fs) (auto split: if_splits)
  
      have change_step: "subst_term [((idn, T), a)] (Fv idn T) = a" by simp
  
      have bs_sub: "set bs ⊆ set insts" using split by auto
      hence "x ∉ fst ` set bs" 
        if "x∈ ⋃ (fv ` snd ` set bs)" for x
        using insts_thm2 that split by (auto simp add: image_iff)
  
      have "v ∉ fst ` set bs" if "v ∈ fv a" for v
        using that insts_thm2 elem bs_sub by (fastforce simp add: image_iff)
      
      hence id_subst_bs: "subst_term [p] a = a" if "p∈set bs" for p
        using that by (cases p, induction a) force+
      hence bs_step: "fold (λsingle. subst_term [single]) bs a = a"
        by (induction bs) auto
  
      from fs_step change_step bs_step split Some show ?thesis by (simp add: insts_def) 
    qed
  qed (simp, metis subst_term.simps)+
qed
corollary subst_term_split_last:
  assumes "distinct (map fst (xs @ [x]))"
  assumes "⋀y . y ∈ (⋃t ∈ snd ` (set (xs @ [x])) . fv t) ⟹ y ∉ fst ` (set (xs @ [x]))"
  shows "subst_term (xs @ [x]) t = subst_term [x] (subst_term xs t)"
proof-
  have "subst_term (xs @ [x]) t = fold (λsingle . subst_term [single]) (xs@[x]) t" 
    using assms subst_term_stepwise by blast
  also have "… = subst_term [x] (fold (λsingle . subst_term [single]) xs t)"
    by simp
  also have "… = subst_term [x] (subst_term xs t)"
    using assms subst_term_stepwise by simp
  finally show ?thesis .
qed
corollary subst_type_term_stepwise:
  assumes "distinct (map fst instT)"
  assumes "⋀x . x ∈ (⋃T ∈ snd ` (set instT) . tvsT T) ⟹ x ∉ fst ` (set instT)"
  assumes "distinct (map fst insts)"
  assumes "⋀x . x ∈ (⋃t ∈ snd ` (set insts) . fv t) ⟹ x ∉ fst ` (set insts)"
  shows "subst_type_term instT insts t 
    = fold (λsingle . subst_term [single]) insts (fold (λsingle . subst_typ' [single]) instT t)"
  using assms subst_typ'_stepwise subst_term_stepwise subst_type_term_split_levels by auto
lemma distinct_fst_imp_distinct: "distinct (map fst l) ⟹ distinct l" by (induction l) auto
lemma distinct_kv_list: "distinct l ⟹ distinct (map (λx. (x, f x)) l)" by (induction l) auto
lemma subst_subst_term: 
  assumes "distinct l" and "fv t ⊆ set l"
  shows "subst t ρ = subst_term (map (λx.(x, case_prod ρ x)) l) t"
using assms proof (induction t arbitrary: l)
  case (Fv idn T)
  then show ?case 
  proof (cases "(idn, T) ∈ set l")
    case True
    hence "((idn, T), ρ idn T) ∈ set (map (λx.(x, case_prod ρ x)) l)" by auto
    moreover have "distinct (map fst (map (λx.(x, case_prod ρ x)) l))" 
      using Fv(1) by (induction l) auto 
    ultimately have "(lookup (λx. x = (idn, T)) (map (λx. (x, case x of (x, xa) ⇒ ρ x xa)) l))
      = Some (ρ idn T)" using lookup_present_eq_key by fast
    then show ?thesis by simp
  next
    case False
    then show ?thesis using Fv by simp
  qed
qed auto
lemma subst_term_subst:
  assumes "distinct (map fst l)"
  shows "subst_term l t = subst t (fold (λ((idn, T), t) f x y. if x=idn ∧y=T then t else f x y) l Fv)"
using assms proof (induction t)
  case (Fv idn T)
  then show ?case
  proof (cases "lookup (λx. x = (idn, T)) l")
    case None
    hence "(idn, T) ∉ set (map fst l)"
      by (metis (full_types) lookup_None_iff)
    
    hence "(fold (λ((idn, T), t) f x y. if x=idn ∧y=T then t else f x y) l Fv) idn T = Fv idn T"
      by (induction l rule: rev_induct) (auto split: if_splits prod.splits) 
      
    then show ?thesis by (simp add: None)
  next
    case (Some a)
    have elem: "((idn, T), a) ∈ set l" 
      using Some lookup_present_eq_key'' Fv by fastforce
    from this obtain fs bs where split: "l = fs @ ((idn, T), a) # bs"
      by (meson split_list) 
    hence "(idn, T) ∉ set (map fst fs)" and not_in_bs: "(idn, T) ∉ set (map fst bs)" using Fv by simp_all
    hence "fst p ~= (idn, T)" if "p∈set fs" for p 
      using that by force
    hence fs_step: "(fold (λ((idn, T), t) f x y. if x=idn ∧y=T then t else f x y) fs Fv) idn T = Fv idn T"
      by (induction fs rule: rev_induct) (fastforce split: if_splits prod.splits)+
    have bs_sub: "set bs ⊆ set l" using split by auto
    
    have "fst p ~= (idn, T)" if "p∈set bs" for p 
      using that not_in_bs by force
    hence bs_step: "(fold (λ((idn, T), t) f x y. if x=idn ∧y=T then t else f x y) bs f) idn T = f idn T"
      for f
      by (induction bs rule: rev_induct) (fastforce split: if_splits prod.splits)+
    from fs_step bs_step split Some show ?thesis by simp
  qed
qed auto
lemma subst_typ_combine_single:
  assumes "fresh_idn ∉ fst ` tvsT τ"
  shows "subst_typ [((fresh_idn, S), τ2)] (subst_typ [((idn, S), Tv fresh_idn S)] τ)
    = subst_typ [((idn, S), τ2)] τ"
  using assms by (induction τ) auto
lemma subst_typ_combine:
  assumes "length fresh_idns = length insts"
  assumes "distinct fresh_idns"
  assumes "distinct (map fst insts)"
  assumes "∀idn ∈ set fresh_idns . idn ∉ fst ` (tvsT τ ∪ (⋃ty∈snd ` set insts . (tvsT ty)) 
    ∪ (fst ` set insts))"
  shows "subst_typ insts τ 
    = subst_typ (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
      (subst_typ (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) τ)"
  using assms proof (induction insts τ arbitrary: fresh_idns rule: subst_typ.induct)
  case (1 inst a Ts)
  then show ?case by fastforce 
next
  case (2 inst idn S)
  show ?case
  proof (cases "lookup (λx. x = (idn, S)) inst")
    case None
    hence "((idn, S)) ∉ fst ` set inst"
      by (metis (mono_tags, lifting) list.set_map lookup_None_iff)
    hence 1: "(lookup (λx. x = (idn, S)) 
      (zip (map fst inst) (map2 Tv fresh_idns (map (snd ∘ fst) inst)))) = None" 
      using 2 by (simp add: lookup_eq_key_not_present)
    have "(idn, S) ∉ set (zip fresh_idns (map (snd ∘ fst) inst))"
      using 2 set_zip_leftD by fastforce 
    hence "(lookup (λx. x = (idn, S)) 
      (zip (zip fresh_idns (map (snd ∘ fst) inst)) (map snd inst))) = None"
      using 2 by (simp add: lookup_eq_key_not_present)
    then show ?thesis using None 1 by simp
  next
    case (Some ty)
    from this obtain idx where idx: "inst ! idx = ((idn, S), ty)" "idx < length inst"
    proof (induction inst)
      case Nil
      then show ?case 
        by simp
    next
      case (Cons a as) thm Cons.IH
      have "(⋀idx. as ! idx = ((idn, S), ty) ⟹ idx < length as ⟹ thesis)"
        by (metis Cons.prems(1) in_set_conv_nth list.set_intros(2))
      then show ?case
        by (meson Cons.prems(1) Cons.prems(2) in_set_conv_nth lookup_present_eq_key')
    qed
    from this obtain fresh_idn where fresh_idn: "fresh_idns ! idx = fresh_idn" by simp
    from 2(1) idx fresh_idn have ren:
      "(zip (map fst inst) (map2 Tv fresh_idns (map (snd ∘ fst) inst))) ! idx 
      = ((idn, S), Tv fresh_idn S) "
      by auto
    from this idx(2) have "((idn, S), Tv fresh_idn S) ∈ set
      (zip (map fst inst) (map2 Tv fresh_idns (map (snd ∘ fst) inst)))"
      by (metis (no_types, opaque_lifting) "2.prems"(1) length_map map_fst_zip map_map map_snd_zip nth_mem)
    from this have 1: "(lookup (λx. x = (idn, S)) 
      (zip (map fst inst) (map2 Tv fresh_idns (map (snd ∘ fst) inst)))) = Some (Tv fresh_idn S)"
      by (simp add: "2.prems"(1) "2.prems"(3) lookup_present_eq_key'')
    from 2(1) idx fresh_idn 1 have "((fresh_idn, S), ty) 
      ∈ set (zip (zip fresh_idns (map (snd ∘ fst) inst)) (map snd inst))"
      using in_set_conv_nth by fastforce
    hence 2: "(lookup (λx. x = (fresh_idn, S)) 
      (zip (zip fresh_idns (map (snd ∘ fst) inst)) (map snd inst))) = Some ty"
      by (simp add: "2.prems"(1) "2.prems"(2) distinct_zipI1 lookup_present_eq_key'')
    then show ?thesis using Some 1 2 by simp
  qed
qed
lemma subst_typ_combine':
  assumes "length fresh_idns = length insts"
  assumes "distinct fresh_idns"
  assumes "distinct (map fst insts)"
  assumes "∀idn ∈ set fresh_idns . idn ∉ fst ` (tvsT τ ∪ (⋃ty∈snd ` set insts . (tvsT ty)) 
    ∪ (fst ` set insts))"
  shows "subst_typ insts τ 
    = fold (λsingle acc . subst_typ [single] acc) (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
      (fold (λsingle acc . subst_typ [single] acc) (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) τ)"
proof-
  have s1: "fst ` set (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts))))
    = fst ` set insts "
  proof-
    have "fst ` set (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts))))
      = set (map fst (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))))"
      by auto
    also have "… = set (map fst insts)" using map_fst_zip assms(1) by auto
    also have "… = fst ` set insts" by simp
    finally show ?thesis .
  qed
  have "snd ` set (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts))))
    = set (map2 Tv fresh_idns (map snd (map fst insts)))" using map_snd_zip assms(1)
    by (metis (no_types, lifting) image_set length_map)
  hence "(⋃ (tvsT ` snd ` set (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts))))))
    = (⋃ (tvsT ` set (map2 Tv fresh_idns (map snd (map fst insts)))))" 
    by simp
  from assms(1) this have s2:
    "(⋃ (tvsT ` snd ` set (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts))))))
    = (set (zip fresh_idns (map snd (map fst insts))))"
    using assms(1) by (induction fresh_idns insts rule: list_induct2) auto
  hence s3: "⋃ (tvsT ` snd ` set (zip (map fst insts)
                   (map2 Tv fresh_idns (map (snd ∘ fst) insts))))
    = set (zip fresh_idns (map snd (map fst insts)))" by simp
  have "idn ∉ fst ` fst ` set insts" if "idn ∈ set fresh_idns" for idn
    using that assms by auto
  hence I: "(idn, S) ∉ fst ` set insts" if "idn ∈ set fresh_idns" for idn S
    using that assms by (metis fst_conv image_eqI)
  have u1: "(subst_typ (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) τ)
    = fold (λsingle acc . subst_typ [single] acc) (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) τ"
    apply (rule subst_typ_stepwise)
    using assms apply simp 
    apply (simp only: s1 s2)  
     using assms I by (metis prod.collapse set_zip_leftD)
  moreover have u2: "subst_typ (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
    (subst_typ (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) τ)
  = fold (λsingle acc . subst_typ [single] acc) (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
    (subst_typ (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) τ)"
   apply (rule subst_typ_stepwise)
   using assms apply (simp add: distinct_zipI1)
   using assms
   by (smt UnCI imageE image_eqI length_map map_snd_zip prod.collapse set_map set_zip_leftD)
  ultimately have unfold: "subst_typ (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
    (subst_typ (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) τ)
   = fold (λsingle acc . subst_typ [single] acc) (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
        (fold (λsingle acc . subst_typ [single] acc) (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) τ)"
     by simp
  show ?thesis using assms subst_typ_combine unfold by auto
qed
lemma subst_typ'_combine:
  assumes "length fresh_idns = length insts"
  assumes "distinct fresh_idns"
  assumes "distinct (map fst insts)"
  assumes "∀idn ∈ set fresh_idns . idn ∉ fst ` (tvs t ∪ (⋃ty∈snd ` set insts . (tvsT ty)) 
    ∪ (fst ` set insts))"
  shows "subst_typ' insts t
    = subst_typ' (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
      (subst_typ' (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) t)"
using assms proof (induction t arbitrary: fresh_idns insts)
  case (Abs T t)
  moreover have "tvs t ⊆ tvs (Abs T t) " by simp
  ultimately have "subst_typ' insts t =
    subst_typ' (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts))
     (subst_typ' (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) t)" 
    by blast
  moreover have "subst_typ insts T =
    subst_typ (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts))
     (subst_typ (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) T)"
    using subst_typ_combine Abs.prems by fastforce
  ultimately show ?case by simp 
next
  case (App t1 t2)
  moreover have "tvs t1 ⊆ tvs (t1 $ t2)" "tvs t2 ⊆ tvs (t1 $ t2)" by auto
  ultimately have "subst_typ' insts t1 =
    subst_typ' (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts))
     (subst_typ' (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) t1)" 
  and "subst_typ' insts t2 =
    subst_typ' (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts))
     (subst_typ' (zip (map fst insts) (map2 Tv fresh_idns (map snd (map fst insts)))) t2)"
    by blast+
  then show ?case by simp
qed (use subst_typ_combine in auto)
lemma subst_term_combine:
  assumes "length fresh_idns = length insts"
  assumes "distinct fresh_idns"
  assumes "distinct (map fst insts)"
  assumes "∀idn ∈ set fresh_idns . idn ∉ fst ` (fv t ∪ (⋃t∈snd ` set insts . (fv t)) 
    ∪ (fst ` set insts))"
  shows "subst_term insts t
    = subst_term (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
      (subst_term (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t)"
using assms proof (induction t arbitrary: fresh_idns insts)
  case (Fv idn ty)
  
  then show ?case
  proof (cases "lookup (λx. x = (idn, ty)) insts")
    case None
    hence "((idn, ty)) ∉ fst ` set insts"
      by (metis (mono_tags, lifting) list.set_map lookup_None_iff)
    hence 1: "(lookup (λx. x = (idn, ty)) 
      (zip (map fst insts) (map2 Fv fresh_idns (map (snd ∘ fst) insts)))) = None" 
      using Fv by (simp add: lookup_eq_key_not_present)
    have "(idn, ty) ∉ set (zip fresh_idns (map (snd ∘ fst) insts))"
      using Fv set_zip_leftD by fastforce 
    hence "(lookup (λx. x = (idn, ty)) 
      (zip (zip fresh_idns (map (snd ∘ fst) insts)) (map snd insts))) = None"
      using Fv by (simp add: lookup_eq_key_not_present)
    then show ?thesis using None 1 by simp
  next
    case (Some u)
    from this obtain idx where idx: "insts ! idx = ((idn, ty), u)" "idx < length insts"
    proof (induction insts)
      case Nil
      then show ?case
        by simp
    next
      case (Cons a as)
      have "(⋀idx. as ! idx = ((idn, ty), u) ⟹ idx < length as ⟹ thesis)"
        by (metis Cons.prems(1) in_set_conv_nth insert_iff list.set(2))
      then show ?case 
        by (meson Cons.prems(1) Cons.prems(2) in_set_conv_nth lookup_present_eq_key')
    qed
    from this obtain fresh_idn where fresh_idn: "fresh_idns ! idx = fresh_idn" by simp
    from Fv(1) idx fresh_idn have ren:
      "(zip (map fst insts) (map2 Fv fresh_idns (map (snd ∘ fst) insts))) ! idx 
      = ((idn, ty), Fv fresh_idn ty)"
      by auto
    from this idx(2) have "((idn, ty), Fv fresh_idn ty) ∈ set
      (zip (map fst insts) (map2 Fv fresh_idns (map (snd ∘ fst) insts)))"
      by (metis (no_types, opaque_lifting) "Fv.prems"(1) length_map map_fst_zip map_map map_snd_zip nth_mem)
    from this have 1: "(lookup (λx. x = (idn, ty)) 
      (zip (map fst insts) (map2 Fv fresh_idns (map (snd ∘ fst) insts)))) = Some (Fv fresh_idn ty)"
      by (simp add: "Fv.prems"(1) "Fv.prems"(3) lookup_present_eq_key'')
    
    from Fv(1) idx fresh_idn 1 have "((fresh_idn, ty), u) 
      ∈ set (zip (zip fresh_idns (map (snd ∘ fst) insts)) (map snd insts))"
      using in_set_conv_nth by fastforce
    hence 2: "(lookup (λx. x = (fresh_idn, ty)) 
      (zip (zip fresh_idns (map (snd ∘ fst) insts)) (map snd insts))) = Some u"
      by (simp add: "Fv.prems"(1) "Fv.prems"(2) distinct_zipI1 lookup_present_eq_key'')
    then show ?thesis using Some 1 2 by simp
  qed
next
  case (App t1 t2)
  moreover have "fv t1 ⊆ fv (t1 $ t2)" "fv t2 ⊆ fv (t1 $ t2)" by simp_all
  ultimately have "subst_term insts t1 =
    subst_term (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts))
     (subst_term (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t1)" 
  and "subst_term insts t2 =
    subst_term (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts))
     (subst_term (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t2)" 
    by blast+
  then show ?case by simp
qed auto
corollary subst_term_combine':
  assumes "length fresh_idns = length insts"
  assumes "distinct fresh_idns"
  assumes "distinct (map fst insts)"
  assumes "∀idn ∈ set fresh_idns . idn ∉ fst ` (fv t ∪ (⋃t∈snd ` set insts . (fv t)) 
    ∪ (fst ` set insts))"
  shows "subst_term insts t
    = fold (λsingle acc . subst_term [single] acc) (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
      (fold (λsingle acc . subst_term [single] acc) (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t)"
proof-
  have s1: "fst ` set (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts))))
    = fst ` set insts "
  proof-
    have "fst ` set (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts))))
      = set (map fst (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))))"
      by auto
    also have "… = set (map fst insts)" using map_fst_zip assms(1) by auto
    also have "… = fst ` set insts" by simp
    finally show ?thesis .
  qed
  have "snd ` set (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts))))
    = set (map2 Fv fresh_idns (map snd (map fst insts)))" using map_snd_zip assms(1)
    by (metis (no_types, lifting) image_set length_map)
  hence "(⋃ (fv ` snd ` set (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts))))))
    = (⋃ (fv ` set (map2 Fv fresh_idns (map snd (map fst insts)))))" 
    by simp
  from assms(1) this have s2:
    "(⋃ (fv ` snd ` set (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts))))))
    = (set (zip fresh_idns (map snd (map fst insts))))"
    using assms(1) by (induction fresh_idns insts rule: list_induct2) auto
  hence s3: "⋃ (fv ` snd ` set (zip (map fst insts)
                   (map2 Fv fresh_idns (map (snd ∘ fst) insts))))
    = set (zip fresh_idns (map snd (map fst insts)))" by simp
  have "idn ∉ fst ` fst ` set insts" if "idn ∈ set fresh_idns" for idn
    using that assms by auto
  hence I: "(idn, T) ∉ fst ` set insts" if "idn ∈ set fresh_idns" for idn T
    using that assms by (metis fst_conv image_eqI)
  have u1: "(subst_term (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t)
    = fold (λsingle acc . subst_term [single] acc) (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t"
    apply (rule subst_term_stepwise)
    using assms apply simp 
    apply (simp only: s1 s2)  
     using assms I by (metis prod.collapse set_zip_leftD)
  moreover have u2: "subst_term (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
    (subst_term (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t)
  = fold (λsingle acc . subst_term [single] acc) (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
    (subst_term (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t)"
   apply (rule subst_term_stepwise)
   using assms apply (simp add: distinct_zipI1)
   using assms
   by (smt UnCI imageE image_eqI length_map map_snd_zip prod.collapse set_map set_zip_leftD)
  ultimately have unfold: "subst_term (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
    (subst_term (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t)
   = fold (λsingle acc . subst_term [single] acc) (zip (zip fresh_idns (map snd (map fst insts))) (map snd insts)) 
        (fold (λsingle acc . subst_term [single] acc) (zip (map fst insts) (map2 Fv fresh_idns (map snd (map fst insts)))) t)"
     by simp
  show ?thesis using assms subst_term_combine unfold by auto
qed
lemma subst_term_not_loose_bvar:
  assumes "¬ loose_bvar t n" "is_closed b" 
  shows "¬ loose_bvar (subst_term [((idn,T),b)] t) n"
  using assms by (induction t arbitrary: n idn T b) (auto simp add: is_open_def loose_bvar_leq)
  
lemma bind_fv2_subst_bv1_eq_subst_term: 
  assumes "¬loose_bvar t n" "is_closed b"
  shows "subst_term [((idn,T),b)] t = subst_bv1 (bind_fv2 (idn, T) n t) n b"
  using assms by (induction t arbitrary: n idn T b) (auto simp add: is_open_def incr_boundvars_def)
corollary
  assumes "is_closed t" "is_closed b" 
  shows "subst_bv b (bind_fv (idn, T) t) = (subst_term [((idn, T),b)] t)"
  using assms bind_fv2_subst_bv1_eq_subst_term
  by (simp add: bind_fv_def subst_bv_def is_open_def)
corollary instantiate_var_same_typ:
  assumes typ_a: "typ_of a = Some τ"
  assumes closed_B: "¬ loose_bvar B lev"
  shows "subst_bv1 (bind_fv2 (x, τ) lev B) lev a = subst_term [((x, τ), a)] B"
  using bind_fv2_subst_bv1_eq_subst_term assms typ_of_imp_closed by metis
corollary instantiate_var_same_typ':
  assumes typ_a: "typ_of a = Some τ"
  assumes closed_B: "is_closed B"
  shows "subst_bv a (bind_fv (x, τ) B) = subst_term [((x, τ), a)] B"
  using instantiate_var_same_typ bind_fv_def subst_bv_def is_open_def assms by auto
corollary instantiate_var_same_type'':
  assumes typ_a: "typ_of a = Some τ"
  assumes closed_B: "is_closed B"
  shows "Abs τ (bind_fv (x, τ) B) ∙ a = subst_term [((x, τ), a)] B"
  using assms instantiate_var_same_typ' by simp
lemma instantiate_vars_same_typ:
  assumes typs: "list_all (λ((idx, ty), t) . typ_of t = Some ty) insts"
  assumes closed_B: "¬ loose_bvar B lev"
  shows "fold (λ((idx, ty), t) B . subst_bv1 (bind_fv2 (idx, ty) lev B) lev t) insts B
    = fold (λsingle . subst_term [single]) insts B"
using assms proof (induction insts arbitrary: B lev)
  case Nil
  then show ?case by simp
next
  case (Cons x xs)
  from this obtain idn ty t where x: "x = ((idn, ty), t)" by (metis prod.collapse)
  hence typ_a: "typ_of t = Some ty" using Cons.prems by simp
  have typs: "list_all (λ((idx, ty), t) . typ_of t = Some ty) xs" using Cons.prems by simp
  have not_loose: "¬ loose_bvar (subst_term [((idn, ty), t)] B) lev" 
    using Cons.prems subst_term_not_loose_bvar typ_a typ_of_imp_closed by simp
  note single = instantiate_var_same_typ[OF typ_a Cons.prems(2), of idn]
  have "fold (λ((idx, ty), t) B . subst_bv1 (bind_fv2 (idx, ty) lev B) lev t) (x # xs) B
    = fold (λ((idx, ty), t) B. subst_bv1 (bind_fv2 (idx, ty) lev B) lev t) xs 
        (subst_bv1 (bind_fv2 (idn, ty) lev B) lev t)"
    by (simp add: x)
  also have "… = fold (λ((idx, ty), t) B. subst_bv1 (bind_fv2 (idx, ty) lev B) lev t) xs
    (subst_term [((idn, ty), t)] B)"
    using single by simp
  also have "… = fold (λsingle. subst_term [single]) xs (subst_term [((idn, ty), t)] B)"
    using Cons.IH[where B = "subst_term [((idn, ty), t)] B", OF typs not_loose] Cons.prems by blast
  also have "… = fold (λsingle. subst_term [single]) (x # xs) B" 
    by (simp add: x)
  finally show ?case .
qed
corollary instantiate_vars_same_typ':
  assumes typs: "list_all (λ((idx, ty), t) . typ_of t = Some ty) insts"
  assumes closed_B: "¬ loose_bvar B lev"
  assumes distinct: "distinct (map fst insts)"
  assumes no_overlap: "⋀x . x ∈ (⋃t ∈ snd ` (set insts) . fv t) ⟹ x ∉ fst ` (set insts)"
  shows "fold (λ((idx, ty), t) B . subst_bv1 (bind_fv2 (idx, ty) lev B) lev t) insts B
    = subst_term insts B"
  using instantiate_vars_same_typ subst_term_stepwise[symmetric] assms by simp  
end