(*  Title:      nominal_dt_alpha.ML
    Author:     Christian Urban
    Author:     Cezary Kaliszyk

  Performing quotient constructions, lifting theorems and
  deriving support properties for the quotient types.
*)

signature NOMINAL_DT_QUOT =
sig
  val define_qtypes: (string list * binding * mixfix) list -> typ list -> term list -> 
    thm list -> local_theory -> Quotient_Info.quotients list * local_theory

  val define_qconsts: typ list -> (string  * term * mixfix * thm) list -> local_theory -> 
    Quotient_Info.quotconsts list * local_theory

  val define_qperms: typ list -> string list -> (string * sort) list -> 
    (string * term * mixfix * thm) list -> thm list -> local_theory -> local_theory

  val define_qsizes: typ list -> string list -> (string * sort) list -> 
    (string * term * mixfix * thm) list -> local_theory -> local_theory

  val lift_thms: typ list -> thm list -> thm list -> Proof.context -> thm list * Proof.context

  val prove_supports: Proof.context -> thm list -> term list -> thm list  
  val prove_fsupp: Proof.context -> typ list -> thm -> thm list -> thm list

  val fs_instance: typ list -> string list -> (string * sort) list -> thm list ->  
    local_theory -> local_theory

  val prove_fv_supp: typ list -> term list -> term list -> term list -> term list -> thm list -> 
    thm list -> thm list -> thm list -> thm -> bclause list list -> Proof.context -> thm list 

  val prove_bns_finite: typ list -> term list -> thm -> thm list -> Proof.context -> thm list
 
  val prove_perm_bn_alpha_thms: typ list -> term list -> term list -> thm -> thm list -> thm list ->
    thm list -> Proof.context -> thm list

  val prove_permute_bn_thms: typ list -> term list -> term list -> thm -> thm list -> thm list ->
    thm list -> Proof.context -> thm list  

  val prove_strong_exhausts: Proof.context -> thm list -> bclause list list list -> thm list -> 
    thm list -> thm list -> thm list -> thm list -> thm list

  val prove_strong_induct: Proof.context -> thm -> thm list -> thm list -> bclause list list list -> 
    thm list
end

structure Nominal_Dt_Quot: NOMINAL_DT_QUOT =
struct

open Nominal_Permeq

fun lookup xs x = the (AList.lookup (op=) xs x)


(* defines the quotient types *)
fun define_qtypes qtys_descr alpha_tys alpha_trms alpha_equivp_thms lthy =
  let
    val qty_args1 = map2 (fn ty => fn trm => (ty, trm, false)) alpha_tys alpha_trms
    val qty_args2 = (map2 (fn descr => fn args1 => (descr, args1, (NONE, false))) qtys_descr qty_args1)
    val qty_args3 = qty_args2 ~~ alpha_equivp_thms
  in
    fold_map Quotient_Type.add_quotient_type qty_args3 lthy
  end 

(* a wrapper for lifting a raw constant *)
fun lift_raw_const qtys (qconst_name, rconst, mx', rsp_thm) lthy =
  let
    val rty = fastype_of rconst
    val qty = Quotient_Term.derive_qtyp lthy qtys rty
    val lhs_raw = Free (qconst_name, qty)
    val rhs_raw = rconst

    val raw_var = (Binding.name qconst_name, NONE, mx')
    val ([(binding, _, mx)], ctxt) = Proof_Context.cert_vars [raw_var] lthy
    val lhs = Syntax.check_term ctxt lhs_raw
    val rhs = Syntax.check_term ctxt rhs_raw

    val (lhs_str, lhs_ty) = dest_Free lhs handle TERM _ => error "Constant already defined."
    val _ = if null (strip_abs_vars rhs) then () else error "The definiens cannot be an abstraction"
    val _ = if is_Const rhs then () else warning "The definiens is not a constant"

  in
    Quotient_Def.add_quotient_def (((binding, mx), Attrib.empty_binding), (lhs, rhs)) rsp_thm  ctxt
  end


(* defines quotient constants *)
fun define_qconsts qtys consts_specs lthy =
  let
    val (qconst_infos, lthy') = 
      fold_map (lift_raw_const qtys) consts_specs lthy
    val phi = Proof_Context.export_morphism lthy' lthy
  in
    (map (Quotient_Info.transform_quotconsts phi) qconst_infos, lthy')
  end


(* defines the quotient permutations and proves pt-class *)
fun define_qperms qtys qfull_ty_names tvs perm_specs raw_perm_laws lthy =
  let
    val lthy1 = 
      lthy
      |> Local_Theory.exit_global
      |> Class.instantiation (qfull_ty_names, tvs, @{sort pt}) 
  
    val (_, lthy2) = define_qconsts qtys perm_specs lthy1

    val ((_, raw_perm_laws'), lthy3) = Variable.importT raw_perm_laws lthy2

    val lifted_perm_laws = 
      map (Quotient_Tacs.lifted lthy3 qtys []) raw_perm_laws'
      |> Variable.exportT lthy3 lthy2

    fun tac _ =
      Class.intro_classes_tac [] THEN
        (ALLGOALS (resolve_tac lifted_perm_laws))
  in
    lthy2
    |> Class.prove_instantiation_exit tac 
    |> Named_Target.theory_init
  end


(* defines the size functions and proves size-class *)
fun define_qsizes qtys qfull_ty_names tvs size_specs lthy =
  let
    val tac = K (Class.intro_classes_tac [])
  in
    lthy
    |> Local_Theory.exit_global
    |> Class.instantiation (qfull_ty_names, tvs, @{sort size})
    |> snd o (define_qconsts qtys size_specs)
    |> Class.prove_instantiation_exit tac
    |> Named_Target.theory_init
  end


(* lifts a theorem and cleans all "_raw" parts
   from variable names *)

local
  val any = Scan.one (Symbol.not_eof)
  val raw = Scan.this_string "_raw"
  val exclude =
    Scan.repeat (Scan.unless raw any) --| raw >> implode
  val parser = Scan.repeat (exclude || any)
in
  fun unraw_str s =
    s |> raw_explode
      |> Scan.finite Symbol.stopper parser >> implode 
      |> fst
end

fun unraw_vars_thm thm =
  let
    fun unraw_var_str ((s, i), T) = ((unraw_str s, i), T)

    val vars = Term.add_vars (prop_of thm) []
    val vars' = map (Var o unraw_var_str) vars
  in
    Thm.certify_instantiate ([], (vars ~~ vars')) thm
  end

fun unraw_bounds_thm th =
  let
    val trm = Thm.prop_of th
    val trm' = Term.map_abs_vars unraw_str trm
  in
    Thm.rename_boundvars trm trm' th
  end

fun lift_thms qtys simps thms ctxt =
  (map (Quotient_Tacs.lifted ctxt qtys simps
        #> unraw_bounds_thm
        #> unraw_vars_thm
        #> Drule.zero_var_indexes) thms, ctxt)



fun mk_supports_goal ctxt qtrm =
  let  
    val vs = fresh_args ctxt qtrm
    val rhs = list_comb (qtrm, vs)
    val lhs = fold (curry HOLogic.mk_prod) vs @{term "()"}
      |> mk_supp
  in
    mk_supports lhs rhs
    |> HOLogic.mk_Trueprop
  end

fun supports_tac ctxt perm_simps =
  let
    val ss1 = HOL_basic_ss addsimps @{thms supports_def fresh_def[symmetric]}
    val ss2 = HOL_ss addsimps @{thms swap_fresh_fresh fresh_Pair}
  in
    EVERY' [ simp_tac ss1,
             eqvt_tac ctxt (eqvt_strict_config addpres perm_simps),
             simp_tac ss2 ]
  end

fun prove_supports_single ctxt perm_simps qtrm =
  let
    val goal = mk_supports_goal ctxt qtrm 
    val ctxt' = Variable.auto_fixes goal ctxt
  in
    Goal.prove ctxt' [] [] goal
      (K (HEADGOAL (supports_tac ctxt perm_simps)))
    |> singleton (Proof_Context.export ctxt' ctxt)
  end

fun prove_supports ctxt perm_simps qtrms =
  map (prove_supports_single ctxt perm_simps) qtrms


(* finite supp lemmas for qtypes *)

fun prove_fsupp ctxt qtys qinduct qsupports_thms =
  let
    val (vs, ctxt') = Variable.variant_fixes (replicate (length qtys) "x") ctxt
    val goals = vs ~~ qtys
      |> map Free
      |> map (mk_finite o mk_supp)
      |> foldr1 (HOLogic.mk_conj)
      |> HOLogic.mk_Trueprop

    val tac = 
      EVERY' [ rtac @{thm supports_finite},
               resolve_tac qsupports_thms,
               asm_simp_tac (HOL_ss addsimps @{thms finite_supp supp_Pair finite_Un}) ]
  in
    Goal.prove ctxt' [] [] goals
      (K (HEADGOAL (rtac qinduct THEN_ALL_NEW tac)))
    |> singleton (Proof_Context.export ctxt' ctxt)
    |> Datatype_Aux.split_conj_thm
    |> map zero_var_indexes
  end


(* finite supp instances *)

fun fs_instance qtys qfull_ty_names tvs qfsupp_thms lthy =
  let
    val lthy1 = 
      lthy
      |> Local_Theory.exit_global
      |> Class.instantiation (qfull_ty_names, tvs, @{sort fs}) 
  
    fun tac _ =
      Class.intro_classes_tac [] THEN
        (ALLGOALS (resolve_tac qfsupp_thms))
  in
    lthy1
    |> Class.prove_instantiation_exit tac 
    |> Named_Target.theory_init
  end


(* proves that fv and fv_bn equals supp *)

fun gen_mk_goals fv supp =
  let
    val arg_ty = 
      fastype_of fv
      |> domain_type
  in
    (arg_ty, fn x => HOLogic.mk_eq (fv $ x, supp x))
  end

fun mk_fvs_goals fv = gen_mk_goals fv mk_supp
fun mk_fv_bns_goals fv_bn alpha_bn = gen_mk_goals fv_bn (mk_supp_rel alpha_bn)

fun add_ss thms =
  HOL_basic_ss addsimps thms

fun symmetric thms = 
  map (fn thm => thm RS @{thm sym}) thms

val supp_Abs_set = @{thms supp_Abs(1)[symmetric]}
val supp_Abs_res = @{thms supp_Abs(2)[symmetric]}
val supp_Abs_lst = @{thms supp_Abs(3)[symmetric]}

fun mk_supp_abs ctxt (BC (Set, _, _)) = EqSubst.eqsubst_tac ctxt [1] supp_Abs_set 
  | mk_supp_abs ctxt (BC (Res, _, _)) = EqSubst.eqsubst_tac ctxt [1] supp_Abs_res
  | mk_supp_abs ctxt (BC (Lst, _, _)) = EqSubst.eqsubst_tac ctxt [1] supp_Abs_lst

fun mk_supp_abs_tac ctxt [] = []
  | mk_supp_abs_tac ctxt (BC (_, [], _)::xs) = mk_supp_abs_tac ctxt xs
  | mk_supp_abs_tac ctxt (bc::xs) = (DETERM o mk_supp_abs ctxt bc)::mk_supp_abs_tac ctxt xs

fun mk_bn_supp_abs_tac trm =
  trm
  |> fastype_of
  |> body_type
  |> (fn ty => case ty of
        @{typ "atom set"}  => simp_tac (add_ss supp_Abs_set)
      | @{typ "atom list"} => simp_tac (add_ss supp_Abs_lst)
      | _ => raise TERM ("mk_bn_supp_abs_tac", [trm]))


val thms1 = @{thms supp_Pair supp_eqvt[symmetric] Un_assoc conj_assoc}
val thms2 = @{thms de_Morgan_conj Collect_disj_eq finite_Un}
val thms3 = @{thms alphas prod_alpha_def prod_fv.simps prod_rel_def permute_prod_def 
  prod.recs prod.cases prod.inject not_True_eq_False empty_def[symmetric] finite.emptyI}

fun prove_fv_supp qtys qtrms fvs fv_bns alpha_bns fv_simps eq_iffs perm_simps 
  fv_bn_eqvts qinduct bclausess ctxt =
  let
    val goals1 = map mk_fvs_goals fvs
    val goals2 = map2 mk_fv_bns_goals fv_bns alpha_bns   

    fun tac ctxt =
      SUBGOAL (fn (goal, i) =>
        let
          val (fv_fun, arg) = 
            goal |> Envir.eta_contract
                 |> Logic.strip_assums_concl
                 |> HOLogic.dest_Trueprop
                 |> fst o HOLogic.dest_eq
                 |> dest_comb
          val supp_abs_tac = 
            case (AList.lookup (op=) (qtrms ~~ bclausess) (head_of arg)) of
              SOME bclauses => EVERY' (mk_supp_abs_tac ctxt bclauses)
            | NONE => mk_bn_supp_abs_tac fv_fun
          val eqvt_rconfig = eqvt_relaxed_config addpres (perm_simps @ fv_bn_eqvts)
        in
          EVERY' [ TRY o asm_full_simp_tac (add_ss (@{thm supp_Pair[symmetric]}::fv_simps)),
                   TRY o supp_abs_tac,
                   TRY o simp_tac (add_ss @{thms supp_def supp_rel_def}),
                   TRY o eqvt_tac ctxt eqvt_rconfig, 
                   TRY o simp_tac (add_ss (@{thms Abs_eq_iff} @ eq_iffs)),
                   TRY o asm_full_simp_tac (add_ss thms3),
                   TRY o simp_tac (add_ss thms2),
                   TRY o asm_full_simp_tac (add_ss (thms1 @ (symmetric fv_bn_eqvts)))] i
        end)
  in
    induct_prove qtys (goals1 @ goals2) qinduct tac ctxt
    |> map atomize
    |> map (simplify (HOL_basic_ss addsimps @{thms fun_eq_iff[symmetric]}))
  end


fun prove_bns_finite qtys qbns qinduct qbn_simps ctxt =
  let
    fun mk_goal qbn = 
      let
        val arg_ty = domain_type (fastype_of qbn)
        val finite = @{term "finite :: atom set => bool"}
      in
        (arg_ty, fn x => finite $ (to_set (qbn $ x)))
      end

    val props = map mk_goal qbns
    val ss_tac = asm_full_simp_tac (HOL_basic_ss addsimps (qbn_simps @ 
      @{thms set.simps set_append finite_insert finite.emptyI finite_Un}))
  in
    induct_prove qtys props qinduct (K ss_tac) ctxt
  end


fun prove_perm_bn_alpha_thms qtys qperm_bns alpha_bns qinduct qperm_bn_simps qeq_iffs qalpha_refls ctxt =
  let 
    val ([p], ctxt') = Variable.variant_fixes ["p"] ctxt
    val p = Free (p, @{typ perm})

    fun mk_goal qperm_bn alpha_bn =
      let
        val arg_ty = domain_type (fastype_of alpha_bn)
      in
        (arg_ty, fn x => (mk_id (Abs ("", arg_ty, alpha_bn $ Bound 0 $ (qperm_bn $ p $ Bound 0)))) $ x)
      end

    val props = map2 mk_goal qperm_bns alpha_bns
    val ss = @{thm id_def}::qperm_bn_simps @ qeq_iffs @ qalpha_refls
    val ss_tac = asm_full_simp_tac (HOL_ss addsimps ss)
  in
    induct_prove qtys props qinduct (K ss_tac) ctxt'
    |> Proof_Context.export ctxt' ctxt
    |> map (simplify (HOL_basic_ss addsimps @{thms id_def})) 
  end

fun prove_permute_bn_thms qtys qbns qperm_bns qinduct qperm_bn_simps qbn_defs qbn_eqvts ctxt =
  let 
    val ([p], ctxt') = Variable.variant_fixes ["p"] ctxt
    val p = Free (p, @{typ perm})

    fun mk_goal qbn qperm_bn =
      let
        val arg_ty = domain_type (fastype_of qbn)
      in
        (arg_ty, fn x => 
          (mk_id (Abs ("", arg_ty, 
             HOLogic.mk_eq (mk_perm p (qbn $ Bound 0), qbn $ (qperm_bn $ p $ Bound 0)))) $ x))
      end

    val props = map2 mk_goal qbns qperm_bns
    val ss = @{thm id_def}::qperm_bn_simps @ qbn_defs
    val ss_tac = 
      EVERY' [asm_full_simp_tac (HOL_basic_ss addsimps ss),
              TRY o eqvt_tac ctxt' (eqvt_strict_config addpres qbn_eqvts),
              TRY o asm_full_simp_tac HOL_basic_ss]
  in
    induct_prove qtys props qinduct (K ss_tac) ctxt'
    |> Proof_Context.export ctxt' ctxt 
    |> map (simplify (HOL_basic_ss addsimps @{thms id_def})) 
  end



(*** proves strong exhauts theorems ***)

(* fixme: move into nominal_library *)
fun abs_const bmode ty =
  let
    val (const_name, binder_ty, abs_ty) = 
      case bmode of
        Lst => (@{const_name "Abs_lst"}, @{typ "atom list"}, @{type_name abs_lst})
      | Set => (@{const_name "Abs_set"}, @{typ "atom set"},  @{type_name abs_set})
      | Res => (@{const_name "Abs_res"}, @{typ "atom set"},  @{type_name abs_res})
  in
    Const (const_name, [binder_ty, ty] ---> Type (abs_ty, [ty]))
  end

fun mk_abs bmode trm1 trm2 =
  abs_const bmode (fastype_of trm2) $ trm1 $ trm2  

fun is_abs_eq thm =
  let
    fun is_abs trm =
      case (head_of trm) of
        Const (@{const_name "Abs_set"}, _) => true
      | Const (@{const_name "Abs_lst"}, _) => true
      | Const (@{const_name "Abs_res"}, _) => true
      | _ => false
  in 
    thm |> prop_of 
        |> HOLogic.dest_Trueprop
        |> HOLogic.dest_eq
        |> fst
        |> is_abs
  end


(* adds a freshness condition to the assumptions *)
fun mk_ecase_prems lthy c (params, prems, concl) bclauses =
  let
    val tys = map snd params
    val binders = get_all_binders bclauses
   
    fun prep_binder (opt, i) = 
      let
        val t = Bound (length tys - i - 1)
      in
        case opt of
          NONE => setify_ty lthy (nth tys i) t 
        | SOME bn => to_set_ty (fastype_of1 (tys, bn $ t)) (bn $ t)   
      end 

    val prems' = 
      case binders of
        [] => prems                        (* case: no binders *)
      | _ => binders                       (* case: binders *) 
          |> map prep_binder
          |> fold_union_env tys
          |> (fn t => mk_fresh_star t c)
          |> (fn t => HOLogic.mk_Trueprop t :: prems)
  in
    mk_full_horn params prems' concl
  end  


(* derives the freshness theorem that there exists a p, such that 
   (p o as) #* (c, t1,..., tn) *)
fun fresh_thm ctxt c parms binders bn_finite_thms =
  let
    fun prep_binder (opt, i) = 
      case opt of
        NONE => setify ctxt (nth parms i) 
      | SOME bn => to_set (bn $ (nth parms i))  

    fun prep_binder2 (opt, i) = 
      case opt of
        NONE => atomify ctxt (nth parms i)
      | SOME bn => bn $ (nth parms i) 

    val rhs = HOLogic.mk_tuple ([c] @ parms @ (map prep_binder2 binders))
    val lhs = binders
      |> map prep_binder
      |> fold_union
      |> mk_perm (Bound 0)

    val goal = mk_fresh_star lhs rhs
      |> mk_exists ("p", @{typ perm})
      |> HOLogic.mk_Trueprop   
 
    val ss = bn_finite_thms @ @{thms supp_Pair finite_supp finite_sets_supp} 
      @ @{thms finite.intros finite_Un finite_set finite_fset}  
  in
    Goal.prove ctxt [] [] goal
      (K (HEADGOAL (rtac @{thm at_set_avoiding1}
          THEN_ALL_NEW (simp_tac (HOL_ss addsimps ss)))))
  end


(* derives an abs_eq theorem of the form 

   Exists q. [as].x = [p o as].(q o x)  for non-recursive binders 
   Exists q. [as].x = [q o as].(q o x)  for recursive binders
*)
fun abs_eq_thm ctxt fprops p parms bn_eqvt permute_bns (bclause as (BC (bmode, binders, bodies))) =
  case binders of
    [] => [] 
  | _ =>
      let
        val rec_flag = is_recursive_binder bclause
        val binder_trm = comb_binders ctxt bmode parms binders
        val body_trm = foldl1 HOLogic.mk_prod (map (nth parms) bodies)

        val abs_lhs = mk_abs bmode binder_trm body_trm
        val abs_rhs = 
          if rec_flag
          then mk_abs bmode (mk_perm (Bound 0) binder_trm) (mk_perm (Bound 0) body_trm)
          else mk_abs bmode (mk_perm p binder_trm) (mk_perm (Bound 0) body_trm)
        
        val abs_eq = HOLogic.mk_eq (abs_lhs, abs_rhs)
        val peq = HOLogic.mk_eq (mk_perm (Bound 0) binder_trm, mk_perm p binder_trm) 

        val goal = HOLogic.mk_conj (abs_eq, peq)
          |> (fn t => HOLogic.mk_exists ("q", @{typ "perm"}, t))
          |> HOLogic.mk_Trueprop  
 
        val ss = fprops @ @{thms set.simps set_append union_eqvt}
          @ @{thms fresh_star_Un fresh_star_Pair fresh_star_list fresh_star_singleton fresh_star_fset
          fresh_star_set} 
  
        val tac1 = 
          if rec_flag
          then resolve_tac @{thms Abs_rename_set' Abs_rename_res' Abs_rename_lst'}
          else resolve_tac @{thms Abs_rename_set  Abs_rename_res  Abs_rename_lst}
        
        val tac2 = EVERY' [simp_tac (HOL_basic_ss addsimps ss), TRY o simp_tac HOL_ss] 
     in
       [ Goal.prove ctxt [] [] goal (K (HEADGOAL (tac1 THEN_ALL_NEW tac2)))
         |> (if rec_flag
             then eqvt_rule ctxt (eqvt_strict_config addpres bn_eqvt)
             else eqvt_rule ctxt (eqvt_strict_config addpres permute_bns)) ]
     end


val setify = @{lemma "xs = ys ==> set xs = set ys" by simp}

fun case_tac ctxt c bn_finite_thms eq_iff_thms bn_eqvt permute_bns perm_bn_alphas 
  prems bclausess qexhaust_thm =
  let
    fun aux_tac prem bclauses =
      case (get_all_binders bclauses) of
        [] => EVERY' [rtac prem, atac]
      | binders => Subgoal.SUBPROOF (fn {params, prems, concl, context = ctxt, ...} =>  
          let
            val parms = map (term_of o snd) params
            val fthm = fresh_thm ctxt c parms binders bn_finite_thms 
  
            val ss = @{thms fresh_star_Pair union_eqvt fresh_star_Un}
            val (([(_, fperm)], fprops), ctxt') = Obtain.result
              (K (EVERY1 [etac exE, 
                          full_simp_tac (HOL_basic_ss addsimps ss), 
                          REPEAT o (etac @{thm conjE})])) [fthm] ctxt 
  
            val abs_eq_thms = flat 
             (map (abs_eq_thm ctxt fprops (term_of fperm) parms bn_eqvt permute_bns) bclauses)

            val ((_, eqs), ctxt'') = Obtain.result
              (K (EVERY1 
                   [ REPEAT o (etac @{thm exE}), 
                     REPEAT o (etac @{thm conjE}),
                     REPEAT o (dtac setify),
                     full_simp_tac (HOL_basic_ss addsimps @{thms set_append set.simps})])) abs_eq_thms ctxt' 

            val (abs_eqs, peqs) = split_filter is_abs_eq eqs

            val fprops' = 
              map (eqvt_rule ctxt (eqvt_strict_config addpres permute_bns)) fprops
              @ map (eqvt_rule ctxt (eqvt_strict_config addpres bn_eqvt)) fprops

            (* for freshness conditions *) 
            val tac1 = SOLVED' (EVERY'
              [ simp_tac (HOL_basic_ss addsimps peqs),
                rewrite_goal_tac (@{thms fresh_star_Un[THEN eq_reflection]}),
                conj_tac (DETERM o resolve_tac fprops') ]) 

            (* for equalities between constructors *)
            val tac2 = SOLVED' (EVERY' 
              [ rtac (@{thm ssubst} OF prems),
                rewrite_goal_tac (map safe_mk_equiv eq_iff_thms),
                rewrite_goal_tac (map safe_mk_equiv abs_eqs),
                conj_tac (DETERM o resolve_tac (@{thms refl} @ perm_bn_alphas)) ]) 

            (* proves goal "P" *)
            val side_thm = Goal.prove ctxt'' [] [] (term_of concl)
              (K (EVERY1 [ rtac prem, RANGE [tac1, tac2] ]))
              |> singleton (Proof_Context.export ctxt'' ctxt)  
          in
            rtac side_thm 1 
          end) ctxt
  in
    EVERY1 [rtac qexhaust_thm, RANGE (map2 aux_tac prems bclausess)]
  end


fun prove_strong_exhausts lthy exhausts bclausesss bn_finite_thms eq_iff_thms bn_eqvt permute_bns
  perm_bn_alphas =
  let
    val ((_, exhausts'), lthy') = Variable.import true exhausts lthy 

    val ([c, a], lthy'') = Variable.variant_fixes ["c", "'a"] lthy'
    val c = Free (c, TFree (a, @{sort fs}))

    val (ecases, main_concls) = exhausts' (* ecases are of the form (params, prems, concl) *)
      |> map prop_of
      |> map Logic.strip_horn
      |> split_list

    val ecases' = (map o map) strip_full_horn ecases

    val premss = (map2 o map2) (mk_ecase_prems lthy'' c) ecases' bclausesss
   
    fun tac bclausess exhaust {prems, context} =
      case_tac context c bn_finite_thms eq_iff_thms bn_eqvt permute_bns perm_bn_alphas 
        prems bclausess exhaust

    fun prove prems bclausess exhaust concl =
      Goal.prove lthy'' [] prems concl (tac bclausess exhaust)
  in
    map4 prove premss bclausesss exhausts' main_concls
    |> Proof_Context.export lthy'' lthy
  end



(** strong induction theorems **)

fun add_c_prop c c_ty trm =
  let
    val (P, arg) = dest_comb trm
    val (P_name, P_ty) = dest_Free P
    val (ty_args, bool) = strip_type P_ty
  in
    Free (P_name, (c_ty :: ty_args) ---> bool) $ c $ arg
  end

fun add_qnt_c_prop c_name c_ty trm =
  trm |> HOLogic.dest_Trueprop
      |> incr_boundvars 1
      |> add_c_prop (Bound 0) c_ty
      |> HOLogic.mk_Trueprop 
      |> mk_all (c_name, c_ty)

fun prep_prem lthy c c_name c_ty bclauses (params, prems, concl) =
  let
    val tys = map snd params
    val binders = get_all_binders bclauses
   
    fun prep_binder (opt, i) = 
      let
        val t = Bound (length tys - i - 1)
      in
        case opt of
          NONE => setify_ty lthy (nth tys i) t 
        | SOME bn => to_set_ty (fastype_of1 (tys, bn $ t)) (bn $ t)   
      end 

    val prems' = prems
      |> map (incr_boundvars 1) 
      |> map (add_qnt_c_prop c_name c_ty)

    val prems'' = 
      case binders of
        [] => prems'                       (* case: no binders *)
      | _ => binders                       (* case: binders *) 
          |> map prep_binder
          |> fold_union_env tys
          |> incr_boundvars 1
          |> (fn t => mk_fresh_star_ty c_ty t (Bound 0))
          |> (fn t => HOLogic.mk_Trueprop t :: prems')

    val concl' = concl
      |> HOLogic.dest_Trueprop
      |> incr_boundvars 1 
      |> add_c_prop (Bound 0) c_ty  
      |> HOLogic.mk_Trueprop
  in  
    mk_full_horn (params @ [(c_name, c_ty)]) prems'' concl'
  end

fun prove_strong_induct lthy induct exhausts size_thms bclausesss =
  let
    val ((_, [induct']), lthy') = Variable.import true [induct] lthy

    val ([c_name, a], lthy'') = Variable.variant_fixes ["c", "'a"] lthy'
    val c_ty = TFree (a, @{sort fs})
    val c = Free (c_name, c_ty)

    val (prems, concl) = induct'
      |> prop_of
      |> Logic.strip_horn 

    val concls = concl
      |> HOLogic.dest_Trueprop
      |> HOLogic.dest_conj 
      |> map (add_c_prop c c_ty) 
      |> map HOLogic.mk_Trueprop

    val prems' = prems
      |> map strip_full_horn
      |> map2 (prep_prem lthy'' c c_name c_ty) (flat bclausesss)

    fun pat_tac ctxt thm =
      Subgoal.FOCUS (fn {params, context, ...} => 
        let
          val thy = Proof_Context.theory_of context
          val ty_parms = map (fn (_, ct) => (fastype_of (term_of ct), ct)) params
          val vs = Term.add_vars (prop_of thm) []
          val vs_tys = map (Type.legacy_freeze_type o snd) vs
          val vs_ctrms = map (cterm_of thy o Var) vs
          val assigns = map (lookup ty_parms) vs_tys          
          
          val thm' = cterm_instantiate (vs_ctrms ~~ assigns) thm
        in
          rtac thm' 1
        end) ctxt
      THEN_ALL_NEW asm_full_simp_tac HOL_basic_ss
 
    val size_simp_tac = 
      simp_tac (size_simpset addsimps (@{thms comp_def snd_conv} @ size_thms))
  in
    Goal.prove_multi lthy'' [] prems' concls
      (fn {prems, context} => 
        Induction_Schema.induction_schema_tac context prems  
        THEN RANGE (map (pat_tac context) exhausts) 1
        THEN prove_termination_ind context 1
        THEN ALLGOALS size_simp_tac)
    |> Proof_Context.export lthy'' lthy
  end


end (* structure *)

