Module GenInd

From Ltac2 Require Import Ltac2.

Require Import List.
Require Import Rewrite.
Require Import Misc.

Ltac2 eassumption_tac () := ltac1:(eassumption).
Ltac2 Notation eassumption := eassumption_tac ().

Definition proj1_transparent {A B : Prop} (H : A /\ B) : A := match H with conj P Q => P end.
Definition proj2_transparent {A B : Prop} (H : A /\ B) : B := match H with conj P Q => Q end.

Lemma Exists_impl_transparent :
  forall (A : Type) (P Q : A -> Prop) (L : list A), (forall x, P x -> Q x) -> Exists P L -> Exists Q L.
Proof.   intros A P Q L H1 H2. induction H2.
  - apply Exists_cons_hd, H1. assumption.
  - apply Exists_cons_tl, IHExists.
Defined.

Lemma Exists_impl_strong_transparent :
  forall (A : Type) (P Q : A -> Prop) (L : list A), (forall x, P x -> Q x) -> Exists P L -> Exists (fun x => P x /\ Q x) L.
Proof.   intros A P Q L H1 H2. eapply Exists_impl_transparent > [|eassumption].
  intros x Hx; split > [apply Hx|apply H1, Hx].
Defined.

Lemma Forall3_impl_transparent :
  forall (A B C : Type) (P Q : A -> B -> C -> Prop) (l1 : list A) (l2 : list B) (l3 : list C), (forall x y z, P x y z -> Q x y z) -> Forall3 P l1 l2 l3 -> Forall3 Q l1 l2 l3.
Proof.   intros A B C P Q l1 l2 l3 H1 H2; induction H2; constructor.
  - apply H1. assumption.
  - assumption.
Defined.

Inductive if_Some {A : Type} (P : A -> Prop) : option A -> Prop :=
| if_Some_None : if_Some P None
| if_Some_Some : forall x, P x -> if_Some P (Some x).

Lemma if_Some_impl_transparent :
  forall (A : Type) (P Q : A -> Prop) (x : option A), (forall x, P x -> Q x) -> if_Some P x -> if_Some Q x.
Proof.   intros A P Q x H1 H2. inversion H2; subst.
  - constructor.
  - constructor; apply H1. assumption.
Defined.

Inductive if_Some2 {A B : Type} (P : A -> B -> Prop) : option A -> option B -> Prop :=
| if_Some2_None : if_Some2 P None None
| if_Some2_Some : forall x y, P x y -> if_Some2 P (Some x) (Some y).

Lemma if_Some2_impl_transparent :
  forall (A B : Type) (P Q : A -> B -> Prop) (x : option A) (y : option B), (forall x y, P x y -> Q x y) -> if_Some2 P x y -> if_Some2 Q x y.
Proof.   intros A B P Q x y H1 H2. inversion H2; subst.
  - constructor.
  - constructor; apply H1. assumption.
Defined.

Inductive if_Some3 {A B C : Type} (P : A -> B -> C -> Prop) : option A -> option B -> option C -> Prop :=
| if_Some3_None : if_Some3 P None None None
| if_Some3_Some : forall x y z, P x y z -> if_Some3 P (Some x) (Some y) (Some z).

Lemma if_Some3_impl_transparent :
  forall (A B C : Type) (P Q : A -> B -> C -> Prop) (x : option A) (y : option B) (z : option C), (forall x y z, P x y z -> Q x y z) -> if_Some3 P x y z -> if_Some3 Q x y z.
Proof.   intros A B C P Q x y z H1 H2. inversion H2; subst.
  - constructor.
  - constructor; apply H1. assumption.
Defined.



Ltac2 msg_list_concat (l : message list) (sep : message) :=
  match l with
  | [] => Message.of_string ""
  | x :: l => List.fold_left (fun m x => Message.concat m (Message.concat sep x)) l x
  end.

Ltac2 message_concat_list (l : message list) :=
  List.fold_left (fun m1 m2 => Message.concat m1 m2) l (Message.of_string "").

Ltac2 message_of_hyps () :=
  message_concat_list (List.map (fun (id, _, c) => message_concat_list [Message.of_ident id; Message.of_string " : "; Message.of_constr c; Message.of_string "
"]) (Control.hyps ())).

Ltac2 typed_constr_message (c : constr) :=
  Message.concat (Message.concat (Message.of_constr c) (Message.of_string " : ")) (Message.of_constr (Constr.type c)).

Ltac2 msg_of_binder (b : binder) :=
  Message.concat
    (match Constr.Binder.name b with None => Message.of_string "_" | Some x => Message.of_ident x end)
    (Message.concat (Message.of_string " : ")
                    (typed_constr_message (Constr.Binder.type b))).

Ltac2 msg_of_list (l : message list) :=
  message_concat_list [Message.of_string "[" ; msg_list_concat l (Message.of_string "; "); Message.of_string "]"].

Ltac2 msg_of_array (a : message array) :=
  message_concat_list [Message.of_string "[|" ; msg_list_concat (Array.to_list a) (Message.of_string "; "); Message.of_string "|]"].

Ltac2 show_constr_kind (c : constr) :=
  let sp := Message.of_string " " in
  match Constr.Unsafe.kind c with
  | Constr.Unsafe.Rel n => Message.concat (Message.of_string "Rel ") (Message.of_int n)
  | Constr.Unsafe.Var x => Message.concat (Message.of_string "Var ") (Message.of_ident x)
  | Constr.Unsafe.Meta _ => Message.of_string "Meta _"
  | Constr.Unsafe.Evar e ca => Message.concat (Message.of_string "Evar _ ") (msg_of_array (Array.map Message.of_constr ca))
  | Constr.Unsafe.Sort _ => Message.of_string "Sort _"
  | Constr.Unsafe.Cast c1 _ c2 => message_concat_list [Message.of_string "Cast " ; Message.of_constr c1 ; Message.of_string " _ " ; Message.of_constr c2]
  | Constr.Unsafe.Prod b c => message_concat_list [Message.of_string "Prod " ; msg_of_binder b ; sp ; Message.of_constr c]
  | Constr.Unsafe.Lambda b c => message_concat_list [Message.of_string "Lambda " ; msg_of_binder b ; sp ; Message.of_constr c]
  | Constr.Unsafe.LetIn b c1 c2 => message_concat_list [Message.of_string "LetIn " ; msg_of_binder b ; sp ; Message.of_constr c1 ; sp ; Message.of_constr c2]
  | Constr.Unsafe.App c ca => message_concat_list [Message.of_string "App " ; Message.of_constr c ; sp; msg_of_array (Array.map Message.of_constr ca)]
  | Constr.Unsafe.Constant _ _ => Message.of_string "Constant _ _"
  | Constr.Unsafe.Ind _ _ => Message.of_string "Ind _ _"
  | Constr.Unsafe.Constructor _ _ => Message.of_string "Constructor _ _"
  | Constr.Unsafe.Case _ c1 ci c2 ca => message_concat_list [Message.of_string "Case _ " ; Message.of_constr c1 ; sp ; match ci with Constr.Unsafe.NoInvert => Message.of_string "NoInvert" | Constr.Unsafe.CaseInvert ca => message_concat_list [Message.of_string "(CaseInvert "; msg_of_array (Array.map Message.of_constr ca); Message.of_string ")"] end ; sp ; Message.of_constr c2 ; sp ; msg_of_array (Array.map Message.of_constr ca) ]
  | Constr.Unsafe.Fix ia i ba ca => message_concat_list [Message.of_string "Fix "; msg_of_array (Array.map Message.of_int ia); sp ; Message.of_int i ; sp ; msg_of_array (Array.map msg_of_binder ba) ; sp ; msg_of_array (Array.map Message.of_constr ca)]
  | Constr.Unsafe.CoFix i ba ca => message_concat_list [Message.of_string "CoFix "; Message.of_int i ; sp ; msg_of_array (Array.map msg_of_binder ba) ; sp ; msg_of_array (Array.map Message.of_constr ca)]
  | Constr.Unsafe.Proj _ c => message_concat_list [Message.of_string "Proj _ "; Message.of_constr c]
  | Constr.Unsafe.Uint63 _ => Message.of_string "Uint63 _"
  | Constr.Unsafe.Float _ => Message.of_string "Float _"
  | Constr.Unsafe.Array _ ca c1 c2 => message_concat_list [Message.of_string "Array _ "; msg_of_array (Array.map Message.of_constr ca) ; sp ; Message.of_constr c1 ; sp ; Message.of_constr c2]
  end.
Ltac2 get_binder_maybe (c : constr) :=
  match Constr.Unsafe.kind c with
  | Constr.Unsafe.Prod b _ => Some b
  | Constr.Unsafe.Lambda b _ => Some b
  | Constr.Unsafe.LetIn b _ _ => Some b
  | _ => None
  end.

Ltac2 get_binder (c : constr) :=
  match get_binder_maybe c with
  | Some b => b
  | None => Control.throw (Invalid_argument (Some (Message.of_constr c)))
  end.

Ltac2 head_binder_ident (c : constr) :=
  Fresh.in_goal (
      match get_binder_maybe c with
      | None => @_x
      | Some b =>
        match Constr.Binder.name b with
        | None => @_x
        | Some x => x
        end
      end).

Ltac2 rec rebuild_lam (t : constr) :=
  match Constr.Unsafe.kind t with
  | Constr.Unsafe.Lambda b t1 => Constr.Unsafe.make (Constr.Unsafe.Lambda b (rebuild_lam t1))
  | _ => t
  end.

Ltac2 rec rebuild_prod_cast (t : constr) :=
  match Constr.Unsafe.kind t with
  | Constr.Unsafe.Prod b t1 => Constr.Unsafe.make (Constr.Unsafe.Prod b (rebuild_prod_cast t1))
  | Constr.Unsafe.Lambda b t1 => Constr.Unsafe.make (Constr.Unsafe.Lambda b (rebuild_prod_cast t1))
  | Constr.Unsafe.Cast t1 _ _ => rebuild_prod_cast t1
  | _ => t
  end.

Ltac2 force_prop (tac : unit -> constr) :=
  '(ltac2:(Control.refine tac) : Prop).
Ltac2 force_prop_t (tac : unit -> constr) :=
  '(ltac2:(Control.refine tac) : _ : Prop).

Ltac2 inctx (name : ident) (t : constr) (body : constr -> constr) :=
  rebuild_lam (Constr.in_context name t (fun () => Control.refine (fun () => body (Control.hyp name)))).
Ltac2 inctx_p (name : ident) (t : constr) (body : constr -> constr) :=
  force_prop_t (fun () => inctx name t body).

Ltac2 make_forall (c : constr) :=
  match Constr.Unsafe.kind c with
  | Constr.Unsafe.Lambda b t => Constr.Unsafe.make (Constr.Unsafe.Prod b t)
  | _ => Control.throw (Invalid_argument (Some (Message.of_constr c)))
  end.

Ltac2 mk_app (u : constr) (v : constr) := '($u $v).
Ltac2 mk_app2 (u : constr) (v : constr) (w : constr) := '($u $v $w).
Ltac2 mk_app3 (u : constr) (v : constr) (w : constr) (x : constr) := '($u $v $w $x).

Ltac2 mk_forall_proof_smart (x : ident) (u : constr) (v : constr -> constr) :=
  lazy_match! (Std.eval_hnf u) with
  | True => v 'I
  | ?u =>
    let vx := inctx_p x u v in
    lazy_match! (Std.eval_hnf (Constr.type vx)) with
    | (_ -> True) => 'I
    | _ => vx
    end
  end.

Ltac2 mk_Forall_proof_smart (x : ident) (t : constr) (u : constr -> constr) (v : constr) :=
  let ux := inctx_p x t u in
  lazy_match! (Std.eval_hnf (Constr.type ux)) with
  | (_ -> True) => 'I
  | _ => '(Forall_True_transparent $t _ $v $ux)
  end.

Ltac2 mk_and_proof_smart (u : constr) (v : constr) :=
  let ut := Std.eval_hnf (Constr.type u) in
  let vt := Std.eval_hnf (Constr.type v) in
  lazy_match! '($ut /\ $vt) with
  | True /\ _ => v
  | _ /\ True => u
  | _ => '(conj $u $v)
  end.


Ltac2 mk_Forall_impl_smart (t : constr) (p : constr) (l : constr) (u : constr -> constr) (v : constr) :=
  let ux := inctx_p (head_binder_ident p) t (fun xx => inctx (Fresh.in_goal @H) (mk_app p xx) u) in
  lazy_match! (Std.eval_hnf (Constr.type ux)) with
  | (forall x H, True) => 'I
  | _ => '(Forall_impl_transparent $t _ _ $l $ux $v)
  end.

Ltac2 mk_Forall2_impl_smart (t1 : constr) (t2 : constr) (p : constr) (l1 : constr) (l2 : constr) (u : constr -> constr) (v : constr) :=
  let ux := inctx_p (head_binder_ident p) t1 (fun xx =>
    let y := head_binder_ident (Std.eval_hnf (mk_app p xx)) in
    inctx_p y t2 (fun yy =>
      inctx_p (Fresh.in_goal @H) (mk_app (mk_app p xx) yy) u)) in
  lazy_match! (Std.eval_hnf (Constr.type ux)) with
  | (forall x y H, True) => 'I
  | _ => '(Forall2_impl_transparent $t1 $t2 _ _ $l1 $l2 $ux $v)
  end.

Ltac2 mk_Forall3_impl_smart (t1 : constr) (t2 : constr) (t3 : constr) (p : constr) (l1 : constr) (l2 : constr) (l3 : constr) (u : constr -> constr) (v : constr) :=
  let ux := inctx_p (head_binder_ident p) t1 (fun xx =>
    let y := head_binder_ident (Std.eval_hnf (mk_app p xx)) in
    inctx_p y t2 (fun yy =>
      let z := head_binder_ident (Std.eval_hnf (mk_app (mk_app p xx) yy)) in
      inctx_p z t3 (fun zz =>
        inctx_p (Fresh.in_goal @H) (mk_app (mk_app (mk_app p xx) yy) zz) u))) in
  lazy_match! (Std.eval_hnf (Constr.type ux)) with
  | (forall x y z H, True) => 'I
  | _ => '(Forall3_impl_transparent $t1 $t2 $t3 _ _ $l1 $l2 $l3 $ux $v)
  end.

Ltac2 mk_if_Some_impl_smart (t : constr) (p : constr) (o : constr) (u : constr -> constr) (v : constr) :=
  let ux := inctx_p (head_binder_ident p) t (fun xx => inctx (Fresh.in_goal @H) (mk_app p xx) u) in
  lazy_match! (Std.eval_hnf (Constr.type ux)) with
  | (forall x H, True) => 'I
  | _ => '(if_Some_impl_transparent $t _ _ $o $ux $v)
  end.

Ltac2 mk_if_Some2_impl_smart (t1 : constr) (t2 : constr) (p : constr) (o1 : constr) (o2 : constr) (u : constr -> constr) (v : constr) :=
  let ux := inctx_p (head_binder_ident p) t1 (fun xx =>
    let y := head_binder_ident (Std.eval_hnf (mk_app p xx)) in
    inctx_p y t2 (fun yy =>
      inctx_p (Fresh.in_goal @H) (mk_app (mk_app p xx) yy) u)) in
  lazy_match! (Std.eval_hnf (Constr.type ux)) with
  | (forall x y H, True) => 'I
  | _ => '(if_Some2_impl_transparent $t1 $t2 _ _ $o1 $o2 $ux $v)
  end.

Ltac2 mk_if_Some3_impl_smart (t1 : constr) (t2 : constr) (t3 : constr) (p : constr) (o1 : constr) (o2 : constr) (o3 : constr) (u : constr -> constr) (v : constr) :=
  let ux := inctx_p (head_binder_ident p) t1 (fun xx =>
    let y := head_binder_ident (Std.eval_hnf (mk_app p xx)) in
    inctx_p y t2 (fun yy =>
      let z := head_binder_ident (Std.eval_hnf (mk_app (mk_app p xx) yy)) in
      inctx_p z t3 (fun zz =>
        inctx_p (Fresh.in_goal @H) (mk_app (mk_app (mk_app p xx) yy) zz) u))) in
  lazy_match! (Std.eval_hnf (Constr.type ux)) with
  | (forall x y z H, True) => 'I
  | _ => '(if_Some3_impl_transparent $t1 $t2 $t3 _ _ $o1 $o2 $o3 $ux $v)
  end.


Ltac2 mk_step_one_impl_smart (t : constr) (r : constr) (l1 : constr) (l2 : constr) (u : constr -> constr) (v : constr) :=
  let ux := inctx_p (head_binder_ident r) t (fun xx =>
    let y := head_binder_ident (Std.eval_hnf (mk_app r xx)) in
    inctx_p y t (fun yy =>
      inctx_p (Fresh.in_goal @H) (mk_app (mk_app r xx) yy) u)) in
  lazy_match! (Std.eval_hnf (Constr.type ux)) with
  | (forall x y H, True) => 'I
  | _ => '(step_one_impl_strong_transparent $t _ _ $l1 $l2 $ux $v)
  end.

Ltac2 mk_Exists_impl_smart (t : constr) (p : constr) (l : constr) (u : constr -> constr) (v : constr) :=
  let ux := inctx_p (head_binder_ident p) t (fun xx => inctx (Fresh.in_goal @H) (mk_app p xx) u) in
  lazy_match! (Std.eval_hnf (Constr.type ux)) with
  | (forall x H, True) => 'I
  | _ => '(Exists_impl_strong_transparent $t _ _ $l $ux $v)
  end.



Ltac2 log (c : constr) := Message.print (typed_constr_message c).
Ltac2 logged (r : constr) := log r; r.
Ltac2 log_context () := Message.print (Message.of_string "Context:"); Message.print (message_of_hyps ()).

Ltac2 rec is_ind_prefix (ind : constr) (t : constr) (args : constr list) :=
  if Constr.equal t ind then
    Some (List.rev args)
  else
    lazy_match! t with
    | ?t1 ?t2 => is_ind_prefix ind t1 (t2 :: args)
    | _ => None
    end.

Ltac2 rec is_ind_prefix_l (inds : constr list) (t : constr) (l : 'a list) :=
  match inds with
  | [] => None
  | ind :: inds =>
    match is_ind_prefix ind t [] with
    | Some args => Some (args, List.hd l)
    | None => is_ind_prefix_l inds t (List.tl l)
    end
  end.

Ltac2 rec applist (c : constr) (l : constr list) :=
  match l with
  | [] => c
  | x :: l => mk_app (applist c l) x
  end.

Ltac2 rec constrind_hyp (v : constr) (inds : constr list) (hrecs : constr list) :=
  let t := Std.eval_hnf (Constr.type v) in
  match is_ind_prefix_l inds t hrecs with
  | Some argshrec => let (args, hrec) := argshrec in mk_app (applist hrec args) v
  | None =>
    lazy_match! t with
    | list ?a => mk_Forall_proof_smart (Fresh.in_goal @x) a (fun x => constrind_hyp x inds hrecs) v
    | @Forall ?t ?p ?l =>
      mk_Forall_impl_smart t p l (fun h => constrind_hyp h inds hrecs) v
    | @Forall2 ?t1 ?t2 ?p ?l1 ?l2 =>
      mk_Forall2_impl_smart t1 t2 p l1 l2 (fun h => constrind_hyp h inds hrecs) v
    | @Forall3 ?t1 ?t2 ?t3 ?p ?l1 ?l2 ?l3 =>
      mk_Forall3_impl_smart t1 t2 t3 p l1 l2 l3 (fun h => constrind_hyp h inds hrecs) v
    | @if_Some ?t ?p ?x =>
      mk_if_Some_impl_smart t p x (fun h => constrind_hyp h inds hrecs) v
    | @if_Some2 ?t1 ?t2 ?p ?x ?y =>
      mk_if_Some2_impl_smart t1 t2 p x y (fun h => constrind_hyp h inds hrecs) v
    | @if_Some3 ?t1 ?t2 ?t3 ?p ?x ?y ?z =>
      mk_if_Some3_impl_smart t1 t2 t3 p x y z (fun h => constrind_hyp h inds hrecs) v
    | @step_one ?t ?r ?l1 ?l2 =>
      mk_step_one_impl_smart t r l1 l2 (fun h => constrind_hyp h inds hrecs) v
    | @Exists ?t ?p ?l =>
      mk_Exists_impl_smart t p l (fun h => constrind_hyp h inds hrecs) v
    | (?a * ?b)%type =>
      mk_and_proof_smart
        (constrind_hyp (mk_app 'fst v) inds hrecs)
        (constrind_hyp (mk_app 'snd v) inds hrecs)
    | ?a /\ ?b =>
      mk_and_proof_smart
        (constrind_hyp (mk_app 'proj1_transparent v) inds hrecs)
        (constrind_hyp (mk_app 'proj2_transparent v) inds hrecs)
    | forall (x : ?t1), @?t2 x =>
      mk_forall_proof_smart (head_binder_ident t) t1 (fun x => constrind_hyp (mk_app v x) inds hrecs)
    | _ => 'I
    end
  end.

Ltac2 add1 (t : constr) (l : constr list) :=
  lazy_match! (Std.eval_hnf (Constr.type t)) with
  | True => l
  | _ => t :: l
  end.

Ltac2 maybe_app (t1 : constr) (t2 : constr) :=
  lazy_match! (Constr.type (Constr.type t2)) with
  | Prop => t1
  | _ => mk_app t1 t2
  end.

Ltac2 rec constrind1 (c : constr) (cstr : constr) (cstrhyp : constr) (inds : constr list) (ps : constr list) (hrecs : constr list) (args : constr list) (args_recs : constr list) :=
  let c := Std.eval_hnf c in
  lazy_match! c with
  | forall (x : ?t1), @?t2 x =>
    let x := head_binder_ident c in
    inctx_p x t1 (fun xx =>
      constrind1 (mk_app t2 xx) cstr cstrhyp inds ps hrecs (xx :: args) (add1 (constrind_hyp xx inds hrecs) args_recs)
    )
  | ?c =>
    let nt := applist (applist cstrhyp args) args_recs in
    match is_ind_prefix_l inds (Constr.type (applist cstr args)) ps with
    | None => Control.throw Assertion_failure
    | Some targsp =>
      let (targs, p) := targsp in
      Std.unify (Constr.type nt) (maybe_app (applist p targs) (applist cstr args));
      nt
    end
  end.

Ltac2 get_constructors_base (ind : inductive) (inst : instance) (args : constr list) :=
  List.init (Ind.nconstructors (Ind.data ind))
            (fun i => let c := Ind.get_constructor (Ind.data ind) i in
                   applist (Constr.Unsafe.make (Constr.Unsafe.Constructor c inst)) (List.rev args)
            ).

Ltac2 rec split_ind (c : constr) (args : constr list) :=
  lazy_match! (Std.eval_hnf c) with
  | ?t1 ?t2 => split_ind t1 (t2 :: args)
  | ?c => match Constr.Unsafe.kind c with
         | Constr.Unsafe.Ind ind inst => (ind, inst, args)
         | _ => Control.throw (Invalid_argument (Some (Message.of_constr c)))
         end
  end.

Ltac2 get_constructors (c : constr) :=
  let (ind, inst, args) := split_ind c [] in
  get_constructors_base ind inst args.


Ltac2 rec ncontext (l : constr list) (l2 : constr list) (tac : constr list -> constr) :=
  match l with
  | [] => tac (List.rev l2)
  | _ :: l => inctx_p (Fresh.in_goal @H) '(_ : Prop) (fun h => ncontext l (h :: l2) tac)
  end.

Ltac2 rec nabs (n : int) (c : constr) :=
  if Int.equal n 0 then c else '(fun _ => ltac2:(Control.refine (fun () => nabs (Int.sub n 1) c))).

Ltac2 rec ind_principle_p (t : constr) :=
  lazy_match! (Constr.type t) with
  | forall (x : ?t1), _ =>
    let x := head_binder_ident (Constr.type t) in
    make_forall (inctx x t1 (fun xx => ind_principle_p (mk_app t xx)))
  | Set => '(($t -> Prop) : Prop)
  | Type => '(($t -> Prop) : Prop)
  | Prop => 'Prop
  end.

Ltac2 rec forall_principle (t : constr) (p : constr) :=
  force_prop (fun () =>
  lazy_match! (Constr.type t) with
  | forall (x : ?t1), _ =>
    let x := head_binder_ident (Constr.type t) in
    make_forall (inctx x t1 (fun xx => forall_principle (mk_app t xx) (mk_app p xx)))
  | Set => '(forall (t : $t), $p t)
  | Type => '(forall (t : $t), $p t)
  | Prop => '(($t -> $p) : Prop)
  end).

Ltac2 rec intro_hyp (name : ident) (c : constr) (tac : constr -> constr) :=
  force_prop_t (fun () =>
  lazy_match! (Constr.type c) with
  | forall (x : ?t1), _ =>
    inctx (head_binder_ident (Constr.type c)) t1 (fun xx => intro_hyp name (mk_app c xx) tac)
  | _ => inctx name c tac
  end).

Ltac2 rec rec_pos (t : constr) :=
  match Constr.Unsafe.kind t with
  | Constr.Unsafe.Prod _ t => Int.add 1 (rec_pos t)
  | _ => 0
  end.

Ltac2 rec eta_expand (c : constr) (p : constr) :=
  lazy_match! (Constr.type c) with
  | forall (x : ?t1), _ =>
    let x := head_binder_ident (Constr.type c) in
    inctx x t1 (fun xx => eta_expand (mk_app c xx) (mk_app p xx))
  | Prop => '(fun (_ : $c) => $p)
  | Set => '(fun (t : $c) => $p t)
  | Type => '(fun (t : $c) => $p t)
  end.

Ltac2 rec fresh_n (n : int) (x : ident) (s : Fresh.Free.t) :=
  if Int.equal n 0 then [] else
    let y := Fresh.fresh s x in
    y :: fresh_n (Int.sub n 1) x (Fresh.Free.union s (Fresh.Free.of_ids [y])).

Ltac2 rec fresh_n_goal (n : int) (x : ident) := fresh_n n x (Fresh.Free.of_goal ()).

Ltac2 inctxn (names : ident list) (ts : constr list) (body : constr list -> constr) :=
  List.fold_right2 (fun name t tac l => inctx name t (fun x => tac (x :: l))) (fun l => body (List.rev l)) names ts [].

Ltac2 ncontext_l (cstrsl : constr list list) (body : constr list list -> constr) :=
  List.fold_right (fun cstrs tac lhl => ncontext cstrs [] (fun lhyps => tac (lhyps :: lhl))) (fun l => body (List.rev l)) cstrsl [].

Ltac2 mkfix1 (guarded_arg : int) (name : ident) (t : constr) (body : constr -> constr) :=
  let (b, body) :=
      match Constr.Unsafe.kind (inctx name t body) with
      | Constr.Unsafe.Lambda b body => (b, body)
      | _ => Control.throw Assertion_failure
      end
  in
  Constr.Unsafe.make (Constr.Unsafe.Fix (Array.make 1 guarded_arg) 0 (Array.make 1 b) (Array.make 1 body)).

Ltac2 rec get_body (names : ident list) (bds : binder list) (t : constr) :=
  match names with
  | [] => (List.rev bds, t)
  | _ :: names =>
    match Constr.Unsafe.kind t with
    | Constr.Unsafe.Lambda b body => get_body names (b :: bds) body
    | _ => Control.throw Assertion_failure
    end
  end.

Ltac2 mkprod u v :=
  '(conj $u $v).

Ltac2 rec mkprodn (l : constr list) :=
  match List.tl l with
  | [] => List.hd l
  | _ => mkprod (List.hd l) (mkprodn (List.tl l))
  end.

Ltac2 rotate_left (l : 'a list) (n : int) :=
  List.append (List.skipn n l) (List.firstn n l).
Ltac2 rotate_right (l : 'a list) (n : int) :=
  rotate_left l (Int.sub (List.length l) n).

Ltac2 mkfix_one (guarded_arg : int list) (names : ident list) (ts : constr list) (body : (constr list -> constr) list) (n : int) :=
  let ts := List.map rebuild_prod_cast ts in
  let bodies := List.map rebuild_prod_cast (List.map (inctxn names ts) body) in
  let (binders, _) := get_body names [] (List.hd bodies) in
  let binders := Array.of_list binders in
  let guarded_arg := Array.of_list guarded_arg in
  let bodies := Array.of_list (List.map (fun body => let (_, body) := (get_body names [] body) in body) bodies) in
  Constr.Unsafe.make (Constr.Unsafe.Fix guarded_arg n binders bodies).

Ltac2 mkfix_one' (guarded_arg : int list) (names : ident list) (ts : constr list) (body : (constr list -> constr) list) (n : int) :=
  mkfix_one (rotate_left guarded_arg n) (rotate_left names n) (rotate_left ts n) (rotate_left (List.map (fun f l => f (rotate_right l n)) body) n) 0.

Ltac2 mkfix (guarded_arg : int list) (names : ident list) (ts : constr list) (body : (constr list -> constr) list) :=
  mkprodn (List.init (List.length names) (fun n => mkfix_one guarded_arg names ts body n)).

Ltac2 map3 f l1 l2 l3 := List.map2 (fun a (b, c) => f a b c) l1 (List.combine l2 l3).
Ltac2 map4 f l1 l2 l3 l4 := map3 (fun a b (c, d) => f a b c d) l1 l2 (List.combine l3 l4).
Ltac2 map5 f l1 l2 l3 l4 l5 := map4 (fun a b c (d, e) => f a b c d e) l1 l2 l3 (List.combine l4 l5).

Ltac2 gen_ind_principle (cl : constr list) :=
  let n := List.length cl in
  let indstl := List.map (fun c => split_ind c []) cl in
  let cstrsl := List.map (fun (ind, inst, args) => get_constructors_base ind inst args) indstl in
  inctxn (fresh_n_goal n @P) (List.map ind_principle_p cl) (fun pl =>
    ncontext_l cstrsl (fun lhypsl =>
      mkfix (List.map (fun c => rec_pos (Constr.type c)) cl) (fresh_n_goal n @Hrec) (List.map2 forall_principle cl pl)
            (map5 (fun c (ind, _, _) => fun p cstrs lhyps hrecs =>
        intro_hyp (Fresh.in_goal @x) c (fun xx =>
          let branches := List.map2 (fun cstr hcstr => force_prop_t (fun () => rebuild_lam (constrind1 (Constr.type cstr) cstr hcstr cl pl hrecs [] []))) cstrs lhyps in
          force_prop_t (fun () => Constr.Unsafe.make
            (Constr.Unsafe.Case
               (Constr.Unsafe.case ind)
               (rebuild_lam (eta_expand c p))
               Constr.Unsafe.NoInvert
               xx
               (Array.of_list branches)))
         )) cl indstl pl cstrsl lhypsl)
    )
  ).

Notation "'Induction' 'For' [ x ]" := ltac2:(let l := [x] in Control.refine (fun () => gen_ind_principle (List.map Constr.pretype l))) (at level 0, only parsing).
Notation "'Induction' 'For' [ x ; y ]" :=
  ltac2:(let l := [x; y] in Control.refine (fun () => gen_ind_principle (List.map Constr.pretype l))) (at level 0, only parsing).
Notation "'Induction' 'For' [ x ; y ; z ]" :=
  ltac2:(let l := [x; y; z] in Control.refine (fun () => gen_ind_principle (List.map Constr.pretype l))) (at level 0, only parsing).
Notation "'Induction' 'For' [ x ; y ; z ; w ]" :=
  ltac2:(let l := [x; y; z; w] in Control.refine (fun () => gen_ind_principle (List.map Constr.pretype l))) (at level 0, only parsing).
Notation "'Induction' 'For' [ x ; y ; z ; w ; t ]" :=
  ltac2:(let l := [x; y; z; w; t] in Control.refine (fun () => gen_ind_principle (List.map Constr.pretype l))) (at level 0, only parsing).