Require Import FOL.Proofmode.StringToIdent.
From Equations Require Import Equations.
Require Import Equations.Type.DepElim.
From Undecidability.Shared Require Import Dec ListAutomation.
From FOL Require Import FullSyntax.
From FOL.Proofmode Require Import Theories.

Require Import List Lia String.
Import ListNotations.
Import ListAutomationNotations ListAutomationFacts ListAutomationHints ListAutomationInstances.

Open Scope string_scope.

Definition pm {p cf cp ff} C phi := @prv p cf cp ff C .
Arguments pm {_} {_} {_} {_} _ _.

Definition tpm {p cf cp ff} C phi := @tprv p cf cp ff C .
Arguments tpm {_} {_} {_} {_} _ _.

Section PM.

Context {Σ_funcs : funcs_signature}.
Context {Σ_preds : preds_signature}.
Context {ff : falsity_flag}.

Definition cnil := @nil form.
Definition ccons (s : string) phi C := @cons form C.
Definition cblackbox (A : list form) := A.

Definition tnil : theory := _ False.
Definition tcons (s : string) phi (T : theory) : theory := extend T .
Definition tblackbox (T : theory) := T.

End PM.

Ltac custom_fold := idtac.

 Ltac custom_unfold := idtac.

Ltac custom_simpl := idtac.


Class DeductionRules `{funcs_signature, preds_signature, falsity_flag} (context : Type) (ent : context form Type) (cons : form context context) (map : (form form) context context) (In : form context Prop) :=
{
  II A phi psi : ent (cons A) ent A ( ) ;
  IE A phi psi : ent A ( ) ent A ent A ;
  AllI A phi : ent (map (subst_form ) A) ent A ( ) ;
  AllE A t phi : ent A ( ) ent A ([t..]) ;
  ExI A t phi : ent A ([t..]) ent A ( ) ;
  ExE A phi psi : ent A ( ) ent (cons (map (subst_form ) A)) ([]) ent A ;
  Ctx A phi : In A ent A ;
  CI A phi psi : ent A ent A ent A ( ) ;
  CE1 A phi psi : ent A ( ) ent A ;
  CE2 A phi psi : ent A ( ) ent A ;
  DI1 A phi psi : ent A ent A ( ) ;
  DI2 A phi psi : ent A ent A ( ) ;
  DE A phi psi theta : ent A ( ) ent (cons A) ent (cons A) ent A ;
}.
Arguments DeductionRules {_} {_} {_} _ _ _ _ _.

Class ClassicalDeductionRules `{funcs_signature, preds_signature, falsity_flag} (context : Type) (ent : context form Type) :=
{
  Pc A phi psi : ent A ((( ) ) )
}.
Arguments ClassicalDeductionRules {_} {_} {_} _ _.

Class FalsityDeductionRules `{funcs_signature, preds_signature} (context : Type) (ent : context form Type) :=
{
  Exp A phi : ent A ent A ;
}.
Arguments FalsityDeductionRules {_} {_} _ _.

Class WeakClass `{funcs_signature, preds_signature, falsity_flag} (context : Type) (ent : context form Type) (incl : context context Prop) :=
{
  Weak A B phi : ent A incl A B ent B
}.
Arguments WeakClass {_} {_} {_} _ _.

#[global]
Instance prv_DeductionRules `{funcs_signature, preds_signature, falsity_flag, peirce} : DeductionRules (list form) prv cons (@List.map form form) (@In form) :=
{|
  II := FullND.II ;
  IE := FullND.IE ;
  AllI := FullND.AllI ;
  AllE := FullND.AllE ;
  ExI := @FullND.ExI _ _ _ _;
  ExE := FullND.ExE ;
  Ctx := FullND.Ctx ;
  CI := FullND.CI ;
  CE1 := FullND.CE1 ;
  CE2 := FullND.CE2 ;
  DI1 := FullND.DI1 ;
  DI2 := FullND.DI2 ;
  DE := FullND.DE ;
|}.

#[global]
Instance prv_ClassicalDeductionRules `{funcs_signature, preds_signature, falsity_flag} : ClassicalDeductionRules (list form) (@prv _ _ _ class) :=
{|
  Pc := FullND.Pc
|}.

#[global]
Instance prv_FalsityDeductionRules `{funcs_signature, preds_signature, peirce} : FalsityDeductionRules (list form) (@prv _ _ falsity_on _) :=
{|
  Exp := FullND.Exp
|}.

#[global]
Instance prv_WeakClass `{funcs_signature, preds_signature, falsity_flag, peirce} : WeakClass (list form) prv (@List.incl form) :=
{|
  Weak := FullNDFacts.Weak
|}.


#[global]
Instance eq_dec_full_operators : eq_dec binop. unfold dec; decide equality. Qed.
#[global]
Instance eq_dec_full_logic_quant : eq_dec quantop. unfold dec; decide equality. Qed.

#[global]
Instance tprv_DeductionRules `{funcs_signature, preds_signature, falsity_flag, peirce, eq_dec syms, eq_dec preds} : DeductionRules theory tprv ( a b extend b a) mapT ( a b in_theory b a) :=
{|
  II := Theories.T_II ;
  IE := Theories.T_IE ;
  AllI := Theories.T_AllI ;
  AllE := Theories.T_AllE ;
  ExI := Theories.T_ExI ;
  ExE := Theories.T_ExE ;
  Ctx := Theories.T_Ctx ;
  CI := Theories.T_CI ;
  CE1 := Theories.T_CE1 ;
  CE2 := Theories.T_CE2 ;
  DI1 := Theories.T_DI1 ;
  DI2 := Theories.T_DI2 ;
  DE := Theories.T_DE ;
|}.

#[global]
Instance tprv_ClassicalDeductionRules `{funcs_signature, preds_signature, falsity_flag} : ClassicalDeductionRules theory (@tprv _ _ _ class) :=
{|
  Pc := Theories.T_Pc
|}.

#[global]
Instance tprv_FalsityDeductionRules `{funcs_signature, preds_signature, peirce} : FalsityDeductionRules theory (@tprv _ _ falsity_on _) :=
{|
  Exp := Theories.T_Exp
|}.

#[global]
Instance tprv_WeakClass `{funcs_signature, preds_signature, falsity_flag, peirce} : WeakClass theory tprv subset_T :=
{|
  Weak := Theories.WeakT
|}.


Definition digit_to_string n := match n with
  | 0 "0" | 1 "1" | 2 "2" | 3 "3" | 4 "4" | 5 "5"
  | 6 "6" | 7 "7" | 8 "8" | 9 "9" | _ "_"
end.
Fixpoint nat_to_string' fuel n := match fuel with
  | 0 "OUT OF FUEL"
  | S fuel' match n with
    | 0 ""
    | _ nat_to_string' fuel' (Nat.div n 10) digit_to_string (Nat.modulo n 10)
    end
end.
Definition nat_to_string n := match n with 0 "0" | _ nat_to_string' 100 n end.

Ltac lookup' n C name :=
  match C with
  | ccons name _ _ constr:(Some n)
  | ccons _ _ ?C' lookup' (S n) C' name
  | tcons name _ _ constr:(Some n)
  | tcons _ _ ?T' lookup' (S n) T' name
  | _ None
  end.
Ltac lookup := lookup' 0.

Ltac nth A n :=
  match n with
  | 0 match A with ?t :: _ t | extend _ ?t t | ccons _ ?t _ t | tcons _ ?t _ t end
  | S ?n' match A with _ :: ?A' nth A' n' | extend ?A' _ nth A' n' | ccons _ _ ?A' nth A' n' | tcons _ _ ?T' nth T' n' end
  end.

Ltac remove A n :=
  match n with
  | 0 match A with _ :: ?A' A' | extend ?A' _ A' | ccons _ _ ?A' A' | tcons _ _ ?A' A' end
  | S ?n' match A with
    | ?t :: ?A' let A'' := remove A' n' in constr:(t::A'')
    | extend ?A' ?t let A'' := remove A' n' in constr:(extend t A'')
    | ccons ?s ?t ?A' let A'' := remove A' n' in constr:(ccons s t A'')
    | tcons ?s ?t ?A' let A'' := remove A' n' in constr:(tcons s t A'')
    end
  end.

Ltac replace_ltac A n :=
  match n with
  | 0 match A with _ :: ?A' constr:(::A') | extend ?A' _ constr:(extend A' ) | ccons ?s _ ?A' constr:(ccons s A') | tcons ?s _ ?A' constr:(tcons s A') end
  | S ?n' match A with
    | ?t :: ?A' let A'' := replace_ltac A' n' in constr:(t::A'')
    | extend ?A' ?t let A'' := replace_ltac A' n' in constr:(extend t A'')
    | ccons ?s ?t ?A' let A'' := replace_ltac A' n' in constr:(ccons s t A'')
    | tcons ?s ?t ?A' let A'' := replace_ltac A' n' in constr:(tcons s t A'')
    end
  end.

Ltac map_ltac A f :=
  match A with
  | nil constr:(nil)
  | cnil constr:(cnil)
  | tnil constr:(tnil)
  | @Vector.nil ?a constr:(@Vector.nil a)
  | cblackbox ?A' A
  | tblackbox ?A' A
  | cons ?x ?A' let x' := f x in let A'' := map_ltac A' f in constr:(cons x' A'')
  | ccons ?s ?x ?A' let x' := f x in let A'' := map_ltac A' f in constr:(ccons s x' A'')
  | tcons ?s ?x ?A' let x' := f x in let A'' := map_ltac A' f in constr:(tcons s x' A'')
  | @Vector.cons _ ?x _ ?A' let x' := f x in let A'' := map_ltac A' f in constr:(@Vector.cons _ x' _ A'')
  end.

Ltac new_name' n base C :=
  let name := match n with
    | 0 base
    | S ?n' let s := eval cbn in (nat_to_string n') in eval cbn in (base s)
  end in
  match lookup C name with
  | @None name
  | @Some _ _ new_name' (S n) base C
  end.
Ltac new_name base C := new_name' 0 base C.

Ltac create_context' A :=
  match A with
  | ?::?A'
    let x := create_context' A' in match x with (?c, ?n)
      match n with
        | 0 constr:((ccons "H" c, S n))
        | S ?n' let s' := eval cbn in ("H" nat_to_string n') in constr:((ccons s' c, S n))
      end
    end
  | extend ?T' ?
    let x := create_context' T' in match x with (?c, ?n)
      match n with
        | 0 constr:((tcons "H" c, S n))
        | S ?n' let s' := eval cbn in ("H" nat_to_string n') in constr:((tcons s' c, S n))
      end
    end
  | nil constr:((cnil, 0))
  | _
    
    match type of A with
    | list form constr:((cblackbox A, 0))
    | theory constr:((tblackbox A, 0))
    | form Prop constr:((tblackbox A, 0))
    end
  end.
Ltac create_context A := let x := create_context' A in match x with (?c, _) c end.


Ltac solve_list_incl :=
  try (repeat (try apply incl_refl; apply incl_tl)); firstorder.


Inductive ident_name := Ident : (unit unit) ident_name.

Ltac to_ident_name id :=
  eval cbv in (ltac:(clear; apply Ident; intros id; assumption) : ident_name).

Notation "x" := (Ident ( x x)) (at level 1, only printing).

Definition named_quant {fsig psig ops ff} op (x : ident_name) phi := @quant fsig psig ops ff op .
Definition named_var {fsig} n (x : ident_name) := @var fsig n.
Arguments named_var {_ _} _.

Ltac assert_is_constant_nat n :=
  match n with
    | 0 0
    | S ?n assert_is_constant_nat n
  end.

Ltac annotate_term f t :=
  match t with
    | context C[ var ?n ]
        let _ := assert_is_constant_nat n in
        let ident_name := eval cbn in (f n) in
        let t' := context C[ @named_var _ n ident_name ] in
        annotate_term f t'
    | _ t
  end.

Ltac annotate_form' f idx :=
  match with
  | falsity constr:(falsity)
  | atom ?P ?v
      let map_fun := annotate_term f in
      let v' := map_ltac v map_fun in
      constr:(atom P v')
  | bin ?op ? ?
      let := annotate_form' f idx in
      let := annotate_form' f idx in
      constr:(bin op )
  | quant ?op ?
      let name := eval cbn in ("x" nat_to_string idx) in
      let id := string_to_ident name in
      
      match with
      | _
          let _ := eval cbv in ltac:(assert (id := 0); clear id; exact 0) in
          let ident_name := to_ident_name id in
          let f' := constr:( n match n with 0 ident_name | S n' f n' end) in
          let := annotate_form' f' (S idx) in
          constr:(named_quant op ident_name )
      | _ annotate_form' f (S idx)
      end
  
  | _
      match with
      | context C[ var ?n ]
          let _ := assert_is_constant_nat n in
          let ident_name := eval cbn in (f n) in
          let := context C[ @named_var _ n ident_name ] in
          annotate_form' f idx
      | _
      end
  end.

Ltac add_binder_names :=
  match goal with
  | [ |- @pm _ _ _ ?p ?C ? ]
    let f := constr:( (n : ) Ident ( _unknown _unknown)) in
    let annotate_form := annotate_form' f 0 in
    let := annotate_form in
    let C' := map_ltac C annotate_form in
    change (@pm _ _ _ p C' )
  | [ |- @tpm _ _ _ ?p ?C ? ]
    let f := constr:( (n : ) Ident ( _unknown _unknown)) in
    let annotate_form := annotate_form' f 0 in
    let := annotate_form in
    let C' := map_ltac C annotate_form in
    change (@tpm _ _ _ p C' )
  end.
Ltac update_binder_names := unfold named_quant; unfold named_var; add_binder_names.


Notation "" := cnil (only printing).
Notation "A" := (cblackbox A) (at level 1, only printing, format " A").
Notation "C name : phi" := (ccons name C)
  (at level 1, C at level 200, at level 200,
  left associativity, format "C '//' name : '[' phi ']'", only printing).

Notation "" := tnil (only printing).
Notation "A" := (tblackbox A) (at level 1, only printing, format " A").
Notation "C name : phi" := (tcons name C)
  (at level 1, C at level 200, at level 200,
  left associativity, format "C '//' name : '[' phi ']'", only printing).

Notation "∀ x .. y , phi" := (named_quant All x ( .. (named_quant All y ) .. )) (at level 50, only printing,
  format "'[' '∀' '/ ' x .. y , '/ ' phi ']'").
Notation "∃ x .. y , phi" := (named_quant Ex x ( .. (named_quant Ex y ) .. )) (at level 50, only printing,
  format "'[' '∃' '/ ' x .. y , '/ ' phi ']'").

Notation "x" := (named_var (Ident ( x x))) (at level 1, only printing).

Notation "C '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' phi" :=
  (pm C )
  (at level 1, left associativity,
  format " C '//' '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' '//' phi", only printing).

Notation "T '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' phi" :=
  (tpm T )
  (at level 1, left associativity,
  format " T '//' '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' '//' phi", only printing).

Ltac fstart :=
  match goal with
  | [ |- @prv _ _ _ ?p ?A ? ] let C := create_context A in change (@pm _ _ _ p C )
  | [ |- @tprv _ _ _ ?p ?T ? ] let C := create_context T in change (@tpm _ _ _ p C )
  end;
  add_binder_names.
Ltac fstop :=
  match goal with
  | [ |- @pm _ _ _ ?p ?C ? ] change (@prv _ _ _ p C )
  | [ |- @tpm _ _ _ ?p ?C ? ] change (@tprv _ _ _ p C )
  end;
  unfold pm in *; unfold cnil; unfold ccons;unfold cblackbox;
  unfold tpm in *; unfold tnil; unfold tcons;unfold tblackbox;
  unfold named_quant; unfold named_var.


Ltac make_compatible tac :=
  match goal with
  | [ |- prv ?A _ ] tac A
  | [ |- tprv ?T _ ] tac T
  | [ |- @pm _ _ _ ?p ?C _ ]
      fstop;
      tac C;
      match goal with
      | [ |- pm _ _ ?G ] change (@pm _ _ _ p C G)
      | [ |- prv _ ?G ] change (@pm _ _ _ p C G)
      | _ idtac
      end;
      try update_binder_names
  | [ |- @tpm _ _ ?p ?C _ ]
      fstop;
      tac C;
      match goal with
      | [ |- tprv _ ?G ] change (@tpm _ _ p C G)
      | _ idtac
      end;
      try update_binder_names
  end.

Ltac make_compatible_light tac :=
  match goal with
  | [ |- prv ?A _ ] tac A
  | [ |- tprv ?T _ ] tac T
  | [ |- @pm _ _ _ ?p ?C _ ]
      fstop;
      tac C;
      match goal with
      | [ |- pm _ _ ?G ] change (@pm _ _ _ p C G)
      | [ |- prv _ ?G ] change (@pm _ _ _ p C G)
      | _ idtac
      end
  | [ |- @tpm _ _ ?p ?C _ ]
      fstop;
      tac C;
      match goal with
      | [ |- tprv _ ?G ] change (@tpm _ _ p C G)
      | _ idtac
      end
  end.

Ltac assert_compat' H :=
  match goal with
  | [ |- ?C _ ] assert (@prv _ _ _ C ) as H
  | [ |- ?C _ ] assert (@tprv _ _ _ C ) as H
  | [ |- @pm _ _ _ _ ?C _ ] assert (@pm _ _ _ _ C ) as H
  | [ |- @tpm _ _ _ _ ?C _ ] assert (@tpm _ _ _ _ C ) as H
  end.
Tactic Notation "assert_compat" constr() := let H := fresh in assert_compat' H.
Tactic Notation "assert_compat" constr() "as" ident(H) := assert_compat' H.
Tactic Notation "assert_compat" constr() "by" tactic(tac) := let H := fresh in assert_compat' H; [tac|].
Tactic Notation "assert_compat" constr() "as" ident(H) "by" tactic(tac) := assert_compat' H; [tac|].

Ltac enough_compat' H :=
  match goal with
  | [ |- ?C _ ] enough (@prv _ _ _ C ) as H
  | [ |- ?C _ ] enough (@tprv _ _ _ C ) as H
  | [ |- @pm _ _ _ _ ?C _ ] enough (@pm _ _ _ _ C ) as H
  | [ |- @tpm _ _ _ _ ?C _ ] enough (@tpm _ _ _ _ C ) as H
  end.
Tactic Notation "enough_compat" constr() := let H := fresh in enough_compat' H.
Tactic Notation "enough_compat" constr() "as" ident(H) := enough_compat' H.
Tactic Notation "enough_compat" constr() "by" tactic(tac) := let H := fresh in enough_compat' H; [tac|].
Tactic Notation "enough_compat" constr() "as" ident(H) "by" tactic(tac) := enough_compat' H; [tac|].

Ltac apply_compat' :=
  match goal with
  | [ |- _ _ ] apply
  | [ |- _ _ ] apply
  end.
Ltac apply_compat_in H :=
  match goal with
  | [ |- _ _ ] apply in H
  | [ |- _ _ ] apply in H
  end.
Tactic Notation "apply_compat" constr() constr() := apply_compat' .
Tactic Notation "apply_compat" constr() constr() "in" hyp(H) := apply_compat_in H.

Ltac get_context_goal :=
  match goal with
  | [ |- ?C _ ] C
  | [ |- ?C _ ] C
  | [ |- pm ?C _ ] C
  | [ |- tpm ?C _ ] C
  end.
Ltac get_context_hyp H :=
  match type of H with
  | ?C _ C
  | ?C _ C
  | pm ?C _ C
  | tpm ?C _ C
  end.
Tactic Notation "get_context" := get_context_goal.
Tactic Notation "get_context" hyp(H) := get_context_hyp H.

Ltac get_form_goal :=
  match goal with
  | [ |- _ ? ]
  | [ |- _ ? ]
  | [ |- pm _ ? ]
  | [ |- tpm _ ? ]
  end.
Ltac get_form_hyp H :=
  match type of H with
  | _ ?
  | _ ?
  | pm _ ?
  | tpm _ ?
  end.
Tactic Notation "get_form" := get_form_goal.
Tactic Notation "get_form" hyp(H) := get_form_hyp H.


#[global]
Hint Rewrite @up_term : subst.
#[global]
Hint Rewrite @subst_term_shift : subst.
#[global]
Hint Rewrite @up_form : subst.
#[global]
Hint Rewrite @subst_shift : subst.

Ltac simpl_subst_hyp H :=
  cbn in H;
  repeat match type of H with
  | context C[ x $(S x)] let H' := context C[] in change H' in H
  | context C[S >> var] let H' := context C[] in change H' in H
  end;
  try rewrite !up_term in H;
  try rewrite !subst_term_shift in H;
  try rewrite !up_form in H;
  try rewrite !subst_shift in H;
  
  unfold ">>";
  
  repeat match goal with
  | [ |- context C[ x $(S x)] ] let H' := context C[] in change H' in H
  end;
  
  custom_fold;
  custom_simpl.

Ltac simpl_subst_goal :=
  cbn;
  repeat match goal with
  | [ |- context C[ x $(S x)] ] let G := context C[] in change G
  | [ |- context C[S >> var] ] let G := context C[] in change G
  end;
  try rewrite !up_term;
  try rewrite !subst_term_shift;
  try rewrite !up_form;
  try rewrite !subst_shift;
  
  unfold ">>";
  
  repeat match goal with
  | [ |- context C[ x $(S x)] ] let G := context C[] in change G
  end;
  
  custom_fold;
  custom_simpl.

Tactic Notation "simpl_subst" hyp(H) := (simpl_subst_hyp H).
Tactic Notation "simpl_subst" := (simpl_subst_goal).

Ltac eval_mapT M :=
  match M with
  | mapT ?f (extend ?T ?a) let T' := eval_mapT (mapT f T) in constr:(extend T' (f a))
  | mapT ?f (tcons ?s ?a ?T) let T' := eval_mapT (mapT f T) in constr:(tcons s (f a) T')
  | mapT ?f (tblackbox ?T) constr:(tblackbox (mapT f T))
  | _ M
  end.

Lemma mapT_step `{s1 : funcs_signature, s2 : preds_signature, ff : falsity_flag, p : peirce} f a T1 T2 :
  subset_T (mapT f ) subset_T (extend (f a)) (mapT f (extend a)).
Proof.
  intros H . destruct as [|].
  - destruct (H ) as [ [ ]]. exists . split. now left. assumption.
  - exists a. split. now right. auto.
Qed.


Ltac simpl_context_mapT :=
  match goal with
  | [ |- tprv ?T ? ]
      let T' := eval_mapT T in
      let X := fresh in
      enough (tprv T' ) as X; [
        eapply Weak; [now apply X | repeat apply mapT_step; apply subset_refl ]
      |]
  | [ |- tpm ?T ? ]
      let T' := eval_mapT T in
      let X := fresh in
      enough (tpm T' ) as X; [
        eapply Weak; [now apply X | repeat apply mapT_step; apply subset_refl ]
      |]
  end.


Ltac ctx := make_compatible ltac:( _ apply Ctx; firstorder).

Ltac fexfalso := make_compatible ltac:( _ apply Exp).
Ltac fsplit := make_compatible ltac:( _ apply CI).
Ltac fleft := make_compatible ltac:( _ apply DI1).
Ltac fright := make_compatible ltac:( _ apply DI2).


Section IntroPattern.
  Import Ascii.

  Inductive intro_pattern :=
    | patId : string intro_pattern
    | patAnd : intro_pattern intro_pattern intro_pattern
    | patOr : intro_pattern intro_pattern intro_pattern
    | patRewrite : intro_pattern
    | patRewriteBack : intro_pattern.

  Fixpoint read_name s := match s with
  | String "]" s' ("", String "]" s')
  | String " " s' ("", String " " s')
  | String "|" s' ("", String "|" s')
  | String c s' let (a, s'') := read_name s' in (String c a, s'')
  | EmptyString ("", EmptyString)
  end.

  Fixpoint parse_intro_pattern' s fuel := match fuel with
  | 0 (None, s)
  | S fuel'
    match s with
    | String ("[") s'
        match parse_intro_pattern' s' fuel' with
        | (Some , String "|" s'') match parse_intro_pattern' s'' fuel' with
                                      | (Some , String "]" s''') (Some (patOr ), s''')
                                      | _ (None, "")
                                      end
        | (Some , String " " s'') match parse_intro_pattern' s'' fuel' with
                                      | (Some , String "]" s''') (Some (patAnd ), s''')
                                      | _ (None, "")
                                      end
        | _ (None, "")
        end
    | String ("]") s' (Some (patId "?"), String "]" s')
    | String " " s' (Some (patId "?"), String " " s')
    | String "|" s' (Some (patId "?"), String "|" s')
    | String "-" (String ">" s') (Some patRewrite, s')
    | String "<" (String "-" s') (Some patRewriteBack, s')
    | EmptyString (None, EmptyString)
    | s let (a, s') := read_name s in (Some (patId a), s')
    end
  end.
  Definition parse_intro_pattern s := fst (parse_intro_pattern' s 100).

End IntroPattern.

Ltac create_pattern T :=
  match T with
  | ?t ?s constr:(patAnd (patId "?") (patId "?"))
  | ?t ?s constr:(patOr (patId "?") (patId "?"))
  | ?t constr:(patAnd (patId "?") (patId "?"))
  | named_quant Ex _ ?t constr:(patAnd (patId "?") (patId "?"))
  | _ constr:(patId "?")
  end.

Ltac create_deep_pattern T :=
  match T with
  | ?t ?s
    let := create_deep_pattern t in
    let := create_deep_pattern s in
    constr:(patAnd )
  | ?t ?s
    let := create_deep_pattern t in
    let := create_deep_pattern s in
    constr:(patOr )
  | ?t
    let := constr:(patId "?") in
    let := create_deep_pattern t in
    constr:(patAnd )
  | named_quant Ex _ ?t
    let := constr:(patId "?") in
    let := create_deep_pattern t in
    constr:(patAnd )
  | _ constr:(patId "?")
  end.

Section Fintro.
  Context {Σ_funcs : funcs_signature}.
  Context {Σ_preds : preds_signature}.
  Context {ff : falsity_flag}.
  Variable p : peirce.

  Lemma intro_and_destruct A s t G :
    A (s t G) A (s t G).
  Proof.
    intros. now apply switch_conj_imp.
  Qed.


  Lemma intro_or_destruct A s t G :
    A (s G) A (t G) A (s t G).
  Proof.
    intros Hs Ht. apply II. eapply DE. ctx.
    eapply Weak in Hs. eapply IE. apply Hs. ctx. firstorder.
    eapply Weak in Ht. eapply IE. apply Ht. ctx. firstorder.
  Qed.


  Context {eq_dec_Funcs : eq_dec syms}.
  Context {eq_dec_Preds : eq_dec preds}.

  Lemma intro_and_destruct_T T s t G :
    T (s t G) T (s t G).
  Proof.
    intros. apply II. apply (IE ( := t)). apply (IE ( := s)).
    eapply Weak. apply H. firstorder.
    eapply CE1, Ctx; firstorder.
    eapply CE2, Ctx; firstorder.
  Qed.


  Lemma intro_or_destruct_T T s t G :
    T (s G) T (t G) T (s t G).
  Proof.
    intros Hs Ht. apply II. eapply DE. ctx.
    eapply Weak in Hs. eapply IE. apply Hs. ctx. firstorder.
    eapply Weak in Ht. eapply IE. apply Ht. ctx. firstorder.
  Qed.


  Lemma subst_zero phi x :
    $0 = x = [ n match n with 0 x | S n $(S n) end].
  Proof.
    intros. symmetry. apply subst_id. intros [|]; cbn. now rewrite H. reflexivity.
  Qed.


  Lemma subst_zero_term t x :
    $0 = x t`[ n match n with 0 x | S n $(S n) end] = t.
  Proof.
    intros. apply subst_term_id. intros [|]; cbn. now rewrite H. reflexivity.
  Qed.


End Fintro.

Ltac hypname_from_pattern C id :=
  match id with
  | "?" new_name "H" C
  | _ match lookup C id with
    | @None id
    | @Some _ _ let msg := eval cbn in ("Identifier already used: " id) in fail 7 msg
    end
  end.

Ltac varname_from_pat pat :=
  match pat with
  | patId "?" fresh "x"
  | patId ?id string_to_ident id
  end.

Ltac fintro_ident x :=
  let H := fresh "H" in
  match goal with
  | [ |- _ ?t ]
    apply AllI;
    edestruct nameless_equiv_all as [x H];
    apply H; clear H;
    simpl_subst
  | [ |- @pm _ _ _ ?p ?C (named_quant All _ ?t) ]
    apply AllI;
    edestruct nameless_equiv_all as [x H];
    apply H; clear H;
    simpl_subst;
    match goal with [ |- prv _ ?t'] change (@pm _ _ _ p C t') end;
    update_binder_names
  | [ |- @pm _ _ _ ?p ?C (quant All ?t) ]
    apply AllI;
    edestruct nameless_equiv_all as [x H];
    apply H; clear H;
    simpl_subst;
    match goal with [ |- prv _ ?t'] change (@pm _ _ _ p C t') end;
    update_binder_names
  | [ |- _ ?t ]
    let E := fresh "E" in
    apply AllI;
    assert ( x, $0 = x) as [x E] by (now exists ($0));
    rewrite (subst_zero t x E);
    simpl_context_mapT;
    simpl_subst;
    repeat (try rewrite subst_zero_term; [| apply E]);
    clear E
  | [ |- @tpm _ _ _ ?p ?C (named_quant All _ ?t) ]
    let E := fresh "E" in
    apply AllI;
    assert ( x, $0 = x) as [x E] by (now exists ($0));
    rewrite (subst_zero t x E);
    simpl_context_mapT;
    simpl_subst;
    repeat (try rewrite subst_zero_term; [| apply E]);
    clear E;
    update_binder_names
  | [ |- @tpm _ _ _ ?p ?C (quant All ?t) ]
    let E := fresh "E" in
    apply AllI;
    assert ( x, $0 = x) as [x E] by (now exists ($0));
    rewrite (subst_zero t x E);
    simpl_context_mapT;
    simpl_subst;
    repeat (try rewrite subst_zero_term; [| apply E]);
    clear E;
    update_binder_names
  | _
    
    progress custom_unfold; simpl_subst; try update_binder_names;
    custom_unfold;
    fintro_ident x;
    custom_fold
  end.

Ltac frewrite' T A back := contxt idtac.

Ltac fintro_pat' pat :=
  match pat with
  | patAnd ? ?
      make_compatible_light ltac:( C
        apply II; eapply ExE; [ apply Ctx; now left |
          let x := varname_from_pat in
          let H := fresh "H" in
          edestruct nameless_equiv_ex as [x H];
          apply H; clear H; cbn; simpl_subst; apply imps;
          eapply (@Weak _ _ _ _ _ _ _ C); [| now apply incl_tl] ]
      );
      fintro_pat'
  | patAnd ? ?
      make_compatible_light ltac:( _
        match goal with
        | [ |- prv _ _ ] apply intro_and_destruct
        | [ |- tprv _ _ ] apply intro_and_destruct_T
        end
      );
      fintro_pat' ; fintro_pat'
  | patOr ? ?
      make_compatible_light ltac:( _
        match goal with
        | [ |- prv _ _ ] apply intro_or_destruct
        | [ |- tprv _ _ ] apply intro_or_destruct_T
        end
      );
      [fintro_pat' | fintro_pat' ]
  | patId ?id
      match goal with
      | [ |- ?A ?t ] let x := varname_from_pat pat in fintro_ident x
      | [ |- ?A ?t ] let x := varname_from_pat pat in fintro_ident x
      | [ |- ?A (?s ?t) ] apply II
      | [ |- ?A (?s ?t) ] apply II
      
      | [ |- @pm _ _ _ ?p ?C (named_quant All _ ?t) ] let x := varname_from_pat pat in fintro_ident x
      | [ |- @pm _ _ _ ?p ?C (quant All ?t) ] let x := varname_from_pat pat in fintro_ident x
      | [ |- @tpm _ _ _ ?p ?C (named_quant All _ ?t) ] let x := varname_from_pat pat in fintro_ident x
      | [ |- @tpm _ _ _ ?p ?C (quant All ?t) ] let x := varname_from_pat pat in fintro_ident x
      | [ |- @pm _ _ _ ?p ?C (?s ?t) ] apply II; let name := hypname_from_pattern C id in change (@pm _ _ _ p (ccons name s C) t)
      | [ |- @tpm _ _ _ ?p ?C (?s ?t) ] apply II; let name := hypname_from_pattern C id in change (@tpm _ _ _ p (tcons name s C) t)
      | _
        
        progress custom_unfold; simpl_subst; try update_binder_names;
        custom_unfold;
        fintro_pat' pat;
        custom_fold
      end
  | patRewrite fintro_pat' constr:(patId "?"); make_compatible ltac:(frewrite' 0 ([] : list form) false)
  | patRewriteBack fintro_pat' constr:(patId "?"); make_compatible ltac:(frewrite' 0 ([] : list form) true)
  end.

Ltac fintro_pat intro_pat :=
  let pat := match intro_pat with
    | "..." match get_form_goal with ?t _ create_deep_pattern t end
    | _ match eval cbn in (parse_intro_pattern intro_pat) with
            | Some ?p p
            | None let msg := eval cbn in ("Invalid intro pattern: " intro_pat) in fail 2 msg
            | ?q idtac "AAA" q
            end
  end in fintro_pat' pat.

Tactic Notation "fintro" := fintro_pat constr:("?").
Tactic Notation "fintro" constr(H) := fintro_pat H.
Tactic Notation "fintro" ident(x) := fintro_ident x.

Tactic Notation "fintros" := repeat fintro.

Tactic Notation "fintros" constr() := fintro_pat .
Tactic Notation "fintros" ident() := fintro_ident .

Tactic Notation "fintros" constr() constr() := fintro_pat ; fintro_pat .
Tactic Notation "fintros" ident() constr() := fintro_ident ; fintro_pat .
Tactic Notation "fintros" constr() ident() := fintro_pat ; fintro_ident .
Tactic Notation "fintros" ident() ident() := fintro_ident ; fintro_ident .

Tactic Notation "fintros" constr() constr() constr() := fintro_pat ; fintro_pat ; fintro_pat .
Tactic Notation "fintros" ident() constr() constr() := fintro_ident ; fintro_pat ; fintro_pat .
Tactic Notation "fintros" constr() ident() constr() := fintro_pat ; fintro_ident ; fintro_pat .
Tactic Notation "fintros" constr() constr() ident() := fintro_pat ; fintro_pat ; fintro_ident .
Tactic Notation "fintros" ident() ident() constr() := fintro_ident ; fintro_ident ; fintro_pat .
Tactic Notation "fintros" constr() ident() ident() := fintro_pat ; fintro_ident ; fintro_ident .
Tactic Notation "fintros" ident() ident() ident() := fintro_ident ; fintro_ident ; fintro_ident .

Tactic Notation "fintros" constr() constr() constr() constr() := fintro_pat ; fintro_pat ; fintro_pat ; fintro_pat .
Tactic Notation "fintros" constr() constr() constr() constr() constr() := fintro_pat ; fintro_pat ; fintro_pat ; fintro_pat ; fintro_pat .


Ltac is_hyp H := match type of H with ?t match type of t with Prop idtac end end.

Ltac turn_into_hypothesis T H contxt :=
  tryif is_hyp T
  then assert (H := T)
  else match goal with
  | [ |- @prv _ _ _ ?p ?C _ ]
      match type of T with
      | form assert (@prv _ _ _ p C T) as H by ctx
      | let T' := nth C T in assert (@prv _ _ _ p C T') as H by ctx
      | string match lookup contxt T with
        | @None let msg := eval cbn in ("Unknown identifier: " T) in fail 4 msg
        | @Some _ ?n let T' := nth C n in assert (@prv _ _ _ p C T') as H by ctx
        end
      end
  | [ |- @tprv _ _ _ ?p ?C _ ]
      match type of T with
      | form assert (@tprv _ _ _ p C T) as H by ctx
      | let T' := nth C T in assert (@tprv _ _ _ p C T') as H by ctx
      | string match lookup contxt T with
        | @None let msg := eval cbn in ("Unknown identifier: " T) in fail 4 msg
        | @Some _ ?n let T' := nth C n in assert (@tprv _ _ _ p C T') as H by ctx
        end
      end
  end.

Section ContextUtil.
  Context {Σ_funcs : funcs_signature}.
  Context {Σ_preds : preds_signature}.
  Context {ff : falsity_flag}.
  Variable p : peirce.

  Fixpoint replace C n (phi : form) :=
    match n, C with
      | 0, _::C :: C
      | S n, x::C x :: replace C n
      | _, _ C
    end.

  Lemma replace_incl C n phi :
    incl (replace C n ) (::C).
  Proof.
    revert C. induction n; destruct C; cbn; firstorder.
  Qed.

End ContextUtil.

Ltac fix_context_names C_named C_raw :=
  match C_named with
  | cnil constr:(cnil)
  | tnil constr:(tnil)
  | tblackbox _ match C_raw with tblackbox ?C C_raw | _ constr:(tblackbox C_raw) end
  | cblackbox _ match C_raw with cblackbox ?C C_raw | _ constr:(cblackbox C_raw) end
  | ccons ?s _ ?A match C_raw with ?x::?B let B' := fix_context_names A B in constr:(ccons s x B') end
  | tcons ?s _ ?A match C_raw with extend ?x ?B let B' := fix_context_names A B in constr:(tcons s x B') end
  | _ C_raw
  end.

Ltac replace_context T_old H_new :=
  let C := get_context_goal in
  let := get_form_hyp H_new in
  let := get_form_goal in
  let X := fresh in
  (enough_compat ( ) as X by eapply (IE X); apply H_new);
  let C' := match type of T_old with
    | constr:(replace C T_old )
    
    | string match lookup C T_old with
      | @None let msg := eval cbn in ("Unknown identifier: " T_old) in fail 4 msg
      | @Some _ ?n constr:(replace C n )
      end
  end in
  fintro; apply (Weak (A := C')); [ | apply replace_incl];
  let C' := eval cbn [replace ccons tcons cnil tnil] in C' in
  match goal with
  | [ |- @pm _ _ _ ?p _ _ ] let C' := fix_context_names C C' in change (@pm _ _ _ p C' )
  | [ |- @tpm _ _ _ ?p _ _ ] let C' := fix_context_names C C' in change (@tpm _ _ _ p C' )
  | _ idtac
  end.


Ltac fspecialize_list H A :=
  match A with
  | [] simpl_subst H
  | ?x::?A'
      
      tryif (
        let H' := fresh "H" in
        make_compatible ltac:( C turn_into_hypothesis x H' C);
        apply ( H IE H H') in H;
        clear H'
      ) then idtac
      else (
        
        let x' := fresh "x" in
        eapply (AllE ?[x']) in H;
        instantiate (x' := x)
      );
      fspecialize_list H A'
  end.

Ltac fspecialize_context T A :=
  
  match A with
  | [] idtac
  | _
      let H := fresh "H" in
      make_compatible_light ltac:( C turn_into_hypothesis T H C);
      fspecialize_list H A;
      replace_context T H;
      try update_binder_names;
      try simpl_subst;
      clear H
  end.

Ltac fspecialize' T A := tryif is_hyp T then fspecialize_list T A else fspecialize_context T A.

Tactic Notation "fspecialize" "(" constr(H) constr() ")" := fspecialize' H constr:([]).
Tactic Notation "fspecialize" "(" constr(H) constr() constr() ")" := fspecialize' H constr:([; ]).
Tactic Notation "fspecialize" "(" constr(H) constr() constr() constr() ")" := fspecialize' H constr:([;;]).

Tactic Notation "fspecialize" constr(H) "with" constr() := fspecialize' H constr:([]).
Tactic Notation "fspecialize" constr(H) "with" constr() constr() := fspecialize' H constr:([;]).
Tactic Notation "fspecialize" constr(H) "with" constr() constr() constr() := fspecialize' H constr:([;;]).


Section Fapply.
  Context {Σ_funcs : funcs_signature}.
  Context {Σ_preds : preds_signature}.
  Context {ff : falsity_flag}.
  Variable p : peirce.

  Lemma fapply_equiv_l A phi psi :
    A ( ) A A .
  Proof.
    intros. apply (IE ( := )). eapply CE1. apply H. apply .
  Qed.


  Lemma fapply_equiv_r A phi psi :
    A ( ) A A .
  Proof.
    intros. apply (IE ( := )). eapply CE2. apply H. apply .
  Qed.


  Context {eq_dec_Funcs : eq_dec syms}.
  Context {eq_dec_Preds : eq_dec preds}.

  Lemma fapply_equiv_l_T A phi psi :
    A ( ) A A .
  Proof.
    intros. apply (IE ( := )). eapply CE1. apply H. apply .
  Qed.


  Lemma fapply_equiv_r_T A phi psi :
    A ( ) A A .
  Proof.
    intros. apply (IE ( := )). eapply CE2. apply H. apply .
  Qed.

End Fapply.


Ltac fapply_without_quant H :=
  tryif exact H then idtac else
  let Hs := fresh "Hs" in
  let Ht := fresh "Ht" in
  match get_form_hyp H with
  | ?s ?t
    match goal with
    | [ |- @prv _ _ _ ?p ?A _ ]
        enough (@prv _ _ _ p A s) as Hs;
        [ assert (@prv _ _ _ p A t) as Ht;
          [ eapply (IE H Hs) | fapply_without_quant Ht; clear Hs; clear Ht ]
        | ]
    | [ |- @tprv _ _ _ ?p ?A _ ]
        enough (@tprv _ _ _ p A s) as Hs;
        [ assert (@tprv _ _ _ p A t) as Ht;
          [ apply (IE H Hs) | fapply_without_quant Ht; clear Hs; clear Ht ]
        | ]
    end
  
  
  | _ _
    match goal with
    | [ |- prv _ _] tryif apply (fapply_equiv_l H) then idtac else apply (fapply_equiv_r H)
    | [ |- tprv _ _] tryif apply (fapply_equiv_l_T H) then idtac else apply (fapply_equiv_r_T H)
    end
  
  
  | _ eapply AllE in H; simpl_subst H; fapply_without_quant H

  
  | _
    progress custom_unfold; simpl_subst H;
    custom_unfold;
    fapply_without_quant H;
    custom_fold
  end.

Ltac instantiate_evars H := repeat eassert (H := H _); repeat eapply AllE in H.

Ltac assert_premises H :=
  match type of H with
  | ?A ?B
      let H' := fresh "H" in assert A as H';
      [|specialize (H H'); clear H'; assert_premises H ]
  | _, _ eassert (H := H _); assert_premises H
  | _ idtac
  end.

Ltac feapply' T A := contxt
  let H := fresh "H" in
  turn_into_hypothesis T H contxt;
  
  assert_premises H;
  
  try (
    fspecialize_list H A;
    instantiate_evars H;
    simpl_subst H;
    let C := get_context_goal in
    eapply (Weak (B := C)) in H; [| solve_list_incl];
    fapply_without_quant H;
    
    revgoals;
    custom_fold
  );
  clear H.

Ltac fapply' T A contxt :=
  let H := fresh "H" in
  turn_into_hypothesis T H contxt;
  
  assert_premises H;
  
  try (
    fspecialize_list H A;
    instantiate_evars H;
    simpl_subst H;
    let C := get_context_goal in
    eapply (Weak (B := C)) in H; [| solve_list_incl];
    fapply_without_quant H;
    
    revgoals;
    custom_fold;
    
    
    tryif has_evar ltac:(type of H)
    then fail 3 "Cannot find instance for variable. Try feapply?"
    else clear H
  );
  try clear H.

Tactic Notation "feapply" constr(T) := make_compatible ltac:(feapply' T constr:([] : list form)).
Tactic Notation "feapply" "(" constr(T) constr() ")" := make_compatible ltac:(feapply' T constr:([])).
Tactic Notation "feapply" "(" constr(T) constr() constr() ")" := make_compatible ltac:(feapply' T constr:([;])).
Tactic Notation "feapply" "(" constr(T) constr() constr() constr() ")" := make_compatible ltac:(feapply' T constr:([;;])).

Tactic Notation "fapply" constr(T) := make_compatible ltac:(fapply' T constr:([] : list form)).
Tactic Notation "fapply" "(" constr(T) constr() ")" := make_compatible ltac:(fapply' T constr:([])).
Tactic Notation "fapply" "(" constr(T) constr() constr() ")" := make_compatible ltac:(fapply' T constr:([;])).
Tactic Notation "fapply" "(" constr(T) constr() constr() constr() ")" := make_compatible ltac:(fapply' T constr:([;;])).

Tactic Notation "feapply_light" constr(T) := make_compatible_light ltac:(feapply' T constr:([] : list form)).

Tactic Notation "feapply" "(" constr(T) ")" := make_compatible ltac:(feapply' T constr:([] : list form)).
Tactic Notation "fapply" "(" constr(T) ")" := make_compatible ltac:(fapply' T constr:([] : list form)).


Ltac fapply_in_without_quant_in T_hyp H_imp H_hyp :=
  match get_form_hyp H_imp with
  | ?s ?t
    let H_hyp' := fresh "H_hyp'" in
    tryif assert_compat t as H_hyp' by (feapply_light H_imp; apply H_hyp)
    then (replace_context T_hyp H_hyp'; clear H_hyp')
    else (
      
      let Hs := fresh "Hs" in
      let Ht := fresh "Ht" in
      (enough_compat s as Hs); [
        (assert_compat t as Ht by apply (IE H_imp Hs));
        
        fapply_in_without_quant_in T_hyp Ht H_hyp;
        clear Ht; clear Hs
      | ] )
  
  
  | ?s ?t
    let H_hyp' := fresh "H_hyp'" in
    (tryif assert_compat t as H_hyp' by (feapply_light H_imp; apply H_hyp) then idtac
    else assert_compat s as H_hyp' by (feapply_light H_imp; apply H_hyp));
    replace_context T_hyp H_hyp'; clear H_hyp'
  
  
  | _ instantiate_evars H_imp; fapply_in_without_quant_in T_hyp H_imp H_hyp

  
  | _
    progress custom_unfold; simpl_subst H_imp; simpl_subst H_hyp;
    custom_unfold;
    fapply_in_without_quant_in T_hyp H_imp H_hyp;
    custom_fold
  end.

Ltac feapply_in T_imp A T_hyp :=
  let H_imp := fresh "H_imp" in
  let H_hyp := fresh "H_hyp" in
  make_compatible ltac:( contxt
    turn_into_hypothesis T_imp H_imp contxt;
    turn_into_hypothesis T_hyp H_hyp contxt
  );
  
  assert_premises H_imp;
  
  try (
    let C := get_context_goal in
    fspecialize_list H_imp A;
    instantiate_evars H_imp;
    simpl_subst H_imp;
    eapply (Weak (B := C)) in H_imp; [| solve_list_incl];
    fapply_in_without_quant_in T_hyp H_imp H_hyp;
    
    revgoals;
    simpl_subst;
    try update_binder_names;
    clear H_imp; clear H_hyp
  ).

Ltac fapply_in T_imp A T_hyp :=
  feapply_in T_imp A T_hyp;
  
  
  let C := get_context_goal in
  let := get_form_goal in
  tryif has_evar C then fail 3 "Cannot find instance for variable. Try feapply?"
  else tryif has_evar then fail 3 "Cannot find instance for variable. Try feapply?"
  else idtac.

Tactic Notation "feapply" constr(T_imp) "in" constr(T_hyp) := feapply_in T_imp constr:([] : list form) T_hyp.
Tactic Notation "feapply" "(" constr(T_imp) constr() ")" "in" constr(T_hyp) := feapply_in T_imp constr:([] : list form) T_hyp.
Tactic Notation "feapply" "(" constr(T_imp) constr() constr() ")" "in" constr(T_hyp) := feapply_in T_imp constr:([;] : list form) T_hyp.
Tactic Notation "feapply" "(" constr(T_imp) constr() constr() constr() ")" constr(T_hyp) := feapply_in T_imp constr:([;;] : list form) T_hyp.

Tactic Notation "fapply" constr(T_imp) "in" constr(T_hyp) := fapply_in T_imp constr:([] : list form) T_hyp.
Tactic Notation "fapply" "(" constr(T_imp) constr() ")" "in" constr(T_hyp) := fapply_in T_imp constr:([] : list form) T_hyp.
Tactic Notation "fapply" "(" constr(T_imp) constr() constr() ")" "in" constr(T_hyp) := fapply_in T_imp constr:([;] : list form) T_hyp.
Tactic Notation "fapply" "(" constr(T_imp) constr() constr() constr() ")" constr(T_hyp) := fapply_in T_imp constr:([;;] : list form) T_hyp.


Section Fassert.
  Context {Σ_funcs : funcs_signature}.
  Context {Σ_preds : preds_signature}.
  Context {ff : falsity_flag}.

  Lemma fassert_help `{peirce} A phi psi :
    A A ( ) A .
  Proof.
    intros . eapply IE. exact . exact .
  Qed.


  Context {eq_dec_Funcs : eq_dec syms}.
  Context {eq_dec_Preds : eq_dec preds}.

  Lemma fassert_help_T `{peirce} A phi psi :
    A A ( ) A .
  Proof.
    intros . eapply IE. exact . exact .
  Qed.

End Fassert.

Ltac fassert' := _
  let := fresh "H" in
  let := fresh "H" in
  match goal with
  | [ |- prv ?A ? ]
    assert (A ) as ; [ |
      assert (A ( )) as ; [ clear |
        apply (fassert_help )
      ]
    ]
  | [ |- ?A ? ]
    assert (A ) as ; [ |
      assert (A ( )) as ; [ clear |
        apply (fassert_help_T )
      ]
    ]
  end.

Tactic Notation "fassert" constr() := (make_compatible ltac:(fassert' )); [| fintro].
Tactic Notation "fassert" constr() "as" constr(H) := (make_compatible ltac:(fassert' )); [| fintro_pat H].
Tactic Notation "fassert" constr() "by" tactic(tac) := (make_compatible ltac:(fassert' )); [tac | fintro].
Tactic Notation "fassert" constr() "as" constr(H) "by" tactic(tac) := (make_compatible ltac:(fassert' )); [tac | fintro_pat H].



Section Fdestruct.
  Context {Σ_funcs : funcs_signature}.
  Context {Σ_preds : preds_signature}.
  Context {ff : falsity_flag}.
  Context {p : peirce}.

  Fixpoint remove_n (C : list form) n :=
    match n, C with
    | 0, x::C C
    | S n, x::C x::remove_n C n
    | _, _ C
    end.

  Definition move_to_front C n :=
    match nth_error C n with
      | Some x x :: remove_n C n
      | None C
    end.

  Lemma remove_n_incl C n :
    incl (remove_n C n) C.
  Proof.
    revert C. induction n; destruct C; firstorder.
  Qed.


  Lemma move_to_front_incl C n :
    incl (move_to_front C n) C.
  Proof.
    unfold move_to_front. destruct nth_error eqn:H. 2: easy. intros x [|].
    - eapply nth_error_In, H.
    - eapply remove_n_incl, .
  Qed.


  Lemma fdestruct_discharge_premises T a b c :
    T a T b c T (a b) c.
  Proof.
    intros . fintros. fapply . fapply 0. eapply Weak. apply . firstorder.
  Qed.


  Context {eq_dec_Funcs : eq_dec syms}.
  Context {eq_dec_Preds : eq_dec preds}.

  Lemma fdestruct_discharge_premises_T T a b c :
    T a T (b c) T ((a b) c).
  Proof.
    intros . fintros. fapply . fapply 0. eapply Weak. apply . firstorder.
  Qed.

End Fdestruct.

Ltac frevert T :=
  let C := get_context_goal in
  let n := match type of T with
    | T
    | string match lookup C T with
      | @None let msg := eval cbn in ("Unknown identifier: " T) in fail 4 msg
      | @Some _ ?n n
      end
  end in
  let := nth C n in
  let C' := constr:(move_to_front C n) in
  apply (Weak (A := C')); [| apply move_to_front_incl];
  cbn [move_to_front remove_n nth_error ccons tcons cnil tnil];
  match goal with
  | [ |- prv ?C' ? ] apply imps
  | [ |- tprv ?C' ? ] apply switch_imp_T
  | [ |- pm ?C' ? ]
      apply imps;
      let C' := get_context_goal in
      let := remove C n in
      let C' := fix_context_names C' in
      change (pm C' ( ))
  | [ |- tpm ?C' ? ]
      apply switch_imp_T;
      let C' := get_context_goal in
      let := remove C n in
      let C' := fix_context_names C' in
      change (tpm C' ( ))
  end.

Ltac fdestruct_discharge_premises :=
  try (
    match goal with
    | [ |- prv _ _ ] apply fdestruct_discharge_premises
    | [ |- tprv _ _ ] apply fdestruct_discharge_premises_T
    | [ |- pm _ _ ] make_compatible ltac:( _ apply fdestruct_discharge_premises)
    | [ |- tpm _ _ ] make_compatible ltac:( _ apply fdestruct_discharge_premises_T)
    end;
    [| fdestruct_discharge_premises ]).

Ltac fdestruct' T A pat :=
  tryif is_hyp T then (
    
    let H := fresh "H" in
    let X := fresh "X" in
    assert (H := T);
    let s := get_form_hyp H in
    match goal with
    | [ |- prv ?C ?t ] enough (C (s t)) as X by (feapply X; feapply H)
    | [ |- tprv ?C ?t ] enough (C (s t)) as X by (feapply X; feapply H)
    | [ |- pm ?C ?t ] enough (pm C (s t)) as X by (feapply X; feapply H)
    | [ |- tpm ?C ?t ] enough (tpm C (s t)) as X by (feapply X; feapply H)
    end;
    fintro "?"; fdestruct' 0 A pat; clear H
  )
  else (
    try fspecialize_context T A;
    frevert T;
    
    fdestruct_discharge_premises;
    
    try (let pattern := lazymatch pat with
    | "" match get_form_goal with ?t _ create_pattern t end
    | "..." match get_form_goal with ?t _ create_deep_pattern t end
    | _
      match eval cbn in (parse_intro_pattern pat) with
      | @Some _ ?p p
      | @None let msg := eval cbn in ("Invalid pattern: " pat) in fail 3 msg
      end
    end in
    fintro_pat' pattern;
    try update_binder_names)
  ).

Tactic Notation "fdestruct" constr(T) := fdestruct' T constr:([] : list form) "".
Tactic Notation "fdestruct" "(" constr(T) constr() ")" := fdestruct' T constr:([]) "".
Tactic Notation "fdestruct" "(" constr(T) constr() constr() ")" := fdestruct' T constr:([;]) "".
Tactic Notation "fdestruct" "(" constr(T) constr() constr() constr() ")" := fdestruct' T constr:([;;]) "".

Tactic Notation "fdestruct" constr(T) "as" constr(pat) := fdestruct' T constr:([] : list form) pat.
Tactic Notation "fdestruct" "(" constr(T) constr() ")" "as" constr(pat) := fdestruct' T constr:([]) pat.
Tactic Notation "fdestruct" "(" constr(T) constr() constr() ")" "as" constr(pat) := fdestruct' T constr:([;]) pat.
Tactic Notation "fdestruct" "(" constr(T) constr() constr() constr() ")" "as" constr(pat) := fdestruct' T constr:([;;]) pat.

Tactic Notation "feapply" constr(T_imp) "in" constr(T_hyp) "as" constr(pat) := feapply_in T_imp constr:([] : list form) T_hyp; try fdestruct T_hyp as pat.
Tactic Notation "feapply" "(" constr(T_imp) constr() ")" "in" constr(T_hyp) "as" constr(pat) := feapply_in T_imp constr:([] : list form) T_hyp; try fdestruct T_hyp as pat.
Tactic Notation "feapply" "(" constr(T_imp) constr() constr() ")" "in" constr(T_hyp) "as" constr(pat) := feapply_in T_imp constr:([;] : list form) T_hyp; try fdestruct T_hyp as pat.
Tactic Notation "feapply" "(" constr(T_imp) constr() constr() constr() ")" constr(T_hyp) "as" constr(pat) := feapply_in T_imp constr:([;;] : list form) T_hyp; try fdestruct T_hyp as pat.

Tactic Notation "fapply" constr(T_imp) "in" constr(T_hyp) "as" constr(pat) := fapply_in T_imp constr:([] : list form) T_hyp; try fdestruct T_hyp as pat.
Tactic Notation "fapply" "(" constr(T_imp) constr() ")" "in" constr(T_hyp) "as" constr(pat) := fapply_in T_imp constr:([] : list form) T_hyp; try fdestruct T_hyp as pat.
Tactic Notation "fapply" "(" constr(T_imp) constr() constr() ")" "in" constr(T_hyp) "as" constr(pat) := fapply_in T_imp constr:([;] : list form) T_hyp; try fdestruct T_hyp as pat.
Tactic Notation "fapply" "(" constr(T_imp) constr() constr() constr() ")" constr(T_hyp) "as" constr(pat) := fapply_in T_imp constr:([;;] : list form) T_hyp; try fdestruct T_hyp as pat.


Section Classical.
  Context {Σ_funcs : funcs_signature}.
  Context {Σ_preds : preds_signature}.

  Lemma case_help A phi psi :
    A C ( ( ) ) A C .
  Proof.
    intro H. eapply IE. apply H.
    eapply IE. eapply Pc.
    apply II. apply DI2. apply II.
    eapply IE. apply Ctx. right. now left.
    apply DI1. apply Ctx. now left.
  Qed.


  Lemma contradiction_help A phi :
    A C (( ) ) A C .
  Proof.
    intro H. eapply IE. eapply Pc. apply II.
    apply Exp. eapply IE. eapply Weak. apply H. firstorder.
    apply II. eapply IE. apply Ctx. right. now left.
    apply Ctx. now left.
  Qed.


  Context {eq_dec_Funcs : eq_dec syms}.
  Context {eq_dec_Preds : eq_dec preds}.

  Lemma case_help_T A phi psi :
    A C ( ( ) ) A C .
  Proof.
    intro H. eapply IE. apply H.
    eapply IE. eapply Pc.
    apply II. apply DI2. apply II.
    eapply IE. apply Ctx. firstorder.
    apply DI1. apply Ctx. firstorder.
  Qed.


  Lemma contradiction_help_T A phi :
    A C (( ) ) A C .
  Proof.
    intro H. eapply IE. eapply Pc. apply II.
    apply Exp. eapply IE. eapply Weak. apply H. firstorder.
    apply II. eapply IE. apply Ctx. firstorder.
    apply Ctx. firstorder.
  Qed.


End Classical.

Tactic Notation "fclassical" constr() "as" constr() constr() :=
  make_compatible ltac:( _
    match goal with
    | [ |- prv _ _ ] apply (case_help ( := ))
    | [ |- tprv _ _ ] apply (case_help_T ( := ))
    end
  ); let pat := eval cbn in ("[" "|" "]") in fintro_pat pat.
Tactic Notation "fclassical" constr() "as" constr(H) := fclassical as H H.
Tactic Notation "fclassical" constr() := fclassical as "".

Tactic Notation "fcontradict" "as" constr(H) :=
  make_compatible ltac:( _
    match goal with
    | [ |- _ _ ] apply contradiction_help
    | [ |- _ _ ] apply contradiction_help_T
    end
  ); fintro_pat H.
Tactic Notation "fcontradict" := fcontradict as "?".


Definition cast {X} {x y: X} {p: X Type}
  : x = y p x p y
  := e a match e with eq_refl a end.

Fixpoint {X Y n} (p : X Y Prop) (v1 : Vector.t X n) (v2 : Vector.t Y n) :=
  match in Vector.t _ n return Vector.t Y n Prop with
  | @Vector.nil _ _ True
  | @Vector.cons _ x _ v2 p x (Vector.hd ) p (Vector.tl )
  end .

Class Leibniz (Σ_funcs : funcs_signature) (Σ_preds : preds_signature) (ff : falsity_flag) :=
{
  Eq_pred : preds ;
  eq_binary : 2 = ar_preds Eq_pred ;
  eq s t := @atom Σ_funcs Σ_preds _ _ Eq_pred (cast eq_binary (Vector.cons term s 1 (Vector.cons term t 0 (Vector.nil term)))) ;

  Axioms : list form ;

  eq_refl `{peirce} : Axioms (eq ($0) ($0)) ;
  eq_sym `{peirce} : Axioms (eq ($0) ($1) eq ($1) ($0)) ;

  eq_func_congr `{peirce} : A F v1 v2, incl Axioms A ( t1 t2 A eq ) A eq (func F ) (func F ) ;
  eq_pred_congr `{peirce} : A P v1 v2, incl Axioms A ( t1 t2 A eq ) A atom P A atom P ;
}.

Lemma eq_subst_help `{funcs_signature} f y (v : Vector.t term 2) (e : 2 = y) :
  Vector.map f (cast e v) = cast e (@Vector.map term term f _ v).
Proof.
  destruct e. reflexivity.
Qed.


Lemma refl `{L : Leibniz, p : peirce} A x :
  incl Axioms A A eq x x.
Proof.
  intros H. assert (H_refl := eq_refl). fspecialize (H_refl x).
  rewrite eq_subst_help in H_refl. eapply Weak. apply H_refl. easy.
Qed.


Lemma sym `{L : Leibniz, p : peirce} A x y :
  incl Axioms A A eq x y A eq y x.
Proof.
  intros . assert (H_sym := eq_sym). fspecialize (H_sym y x).
  rewrite ! eq_subst_help in H_sym. simpl_subst H_sym. eapply IE. eapply Weak.
  apply H_sym. easy. easy.
Qed.


Lemma leibniz_term `{L : Leibniz} `{p : peirce} A t s1 s2 :
  incl Axioms A ( n, A eq ( n) ( n)) A eq (t`[]) (t`[]).
Proof.
  intros HA H. induction t; cbn.
  - apply H.
  - apply eq_func_congr. easy. induction v; constructor. apply IH; left.
    apply IHv. intros. apply IH. now right.
Qed.


Lemma leibniz `{L : Leibniz} `{peirce} A phi t t' :
  incl Axioms A A eq t t' A [t..] A [t'..].
Proof.
  enough ( s1 s2, incl Axioms A ->( n, A eq ( n) ( n)) A [] A []) as X.
  { intros. apply X with ( := t..). easy. intros []; cbn. easy. now apply refl. easy. }
  induction ; intros HA .
  - easy.
  - cbn in *. eapply eq_pred_congr. easy. induction ; constructor.
    now apply leibniz_term. apply .
  - cbn in *. destruct ; split; intros .
    + apply CE in . fsplit. now apply ( L A ). now apply ( L A ).
    + apply CE in . fsplit. now apply ( L A ). now apply ( L A ).
    + eapply DE in . apply .
      * fleft. apply ( L _ ). now apply incl_tl. intros. eapply Weak.
        apply . now apply incl_tl. ctx.
      * fright. apply ( L _ ). now apply incl_tl. intros. eapply Weak.
        apply . now apply incl_tl. ctx.
    + eapply DE in . apply .
      * fleft. apply ( L _ ). now apply incl_tl. intros. eapply Weak.
        apply . now apply incl_tl. ctx.
      * fright. apply ( L _ ). now apply incl_tl. intros. eapply Weak.
        apply . now apply incl_tl. ctx.
    + fintros. apply ( L _ ). now apply incl_tl. intros. eapply Weak.
      apply . now apply incl_tl. eapply IE. eapply Weak. apply . now apply incl_tl.
      apply ( L _ ). now apply incl_tl. intros. eapply Weak.
      apply . apply incl_tl, incl_refl. ctx.
    + fintros. apply ( L _ ). now apply incl_tl. intros. eapply Weak.
      apply . now apply incl_tl. eapply IE. eapply Weak. apply . now apply incl_tl.
      apply ( L _ ). now apply incl_tl. intros. eapply Weak.
      apply . apply incl_tl, incl_refl. ctx.
  - destruct q; split; cbn; intros .
    + fintros. rewrite subst_comp. apply (IHphi L _ _ (up >> subst_term x..)). easy.
      intros []; cbn. now apply refl. simpl_subst. simpl_subst. apply sym. easy. apply .
      rewrite subst_comp. now apply AllE.
    + fintros. rewrite subst_comp. apply (IHphi L _ _ (up >> subst_term x..)). easy.
      intros []; cbn. now apply refl. simpl_subst. simpl_subst. apply .
      rewrite subst_comp. now apply AllE.
    + fdestruct . apply (ExI (t := x)). rewrite ! subst_comp.
      apply (IHphi L _ _ (up >> subst_term x..)). now apply incl_tl. 2: ctx.
      intros []; cbn. apply refl; now apply incl_tl. simpl_subst. simpl_subst. apply sym. now apply incl_tl.
      eapply Weak. apply . now apply incl_tl.
    + fdestruct . apply (ExI (t := x)). rewrite ! subst_comp.
      apply (IHphi L _ _ (up >> subst_term x..)). now apply incl_tl. 2: ctx.
      intros []; cbn. apply refl; now apply incl_tl. simpl_subst. simpl_subst.
      eapply Weak. apply . now apply incl_tl.
Qed.


Section FrewriteEquiv.
  Context {Σ_funcs : funcs_signature}.
  Context {Σ_preds : preds_signature}.
  Context {ff : falsity_flag}.
  Context {p : peirce}.

  Lemma frewrite_equiv_bin_l A op phi psi theta :
    A ( ) A (bin op bin op ).
  Proof.
    intros E. fstart. destruct op; fsplit.
    - fintros "[P T]". fsplit. fapply E. ctx. ctx.
    - fintros "[P T]". fsplit. fapply E. ctx. ctx.
    - fintros "[P|T]". fleft. fapply E. ctx. fright. ctx.
    - fintros "[P|T]". fleft. fapply E. ctx. fright. ctx.
    - fintros "H" "P". fapply "H". fapply E. ctx.
    - fintros "H" "P". fapply "H". fapply E. ctx.
  Qed.


  Lemma frewrite_equiv_bin_r A op phi psi theta :
    A ( ) A (bin op bin op ).
  Proof.
    intros E. fstart. destruct op; fsplit.
    - fintros "[P T]". fsplit. ctx. fapply E. ctx.
    - fintros "[P T]". fsplit. ctx. fapply E. ctx.
    - fintros "[P|T]". fleft. ctx. fright. fapply E. ctx.
    - fintros "[P|T]". fleft. ctx. fright. fapply E. ctx.
    - fintros "H" "P". fapply E. fapply "H". ctx.
    - fintros "H" "P". fapply E. fapply "H". ctx.
  Qed.


  Lemma frewrite_equiv_bin_lr A op phi psi theta chi :
    A ( ) A ( chi) A (bin op bin op chi).
  Proof.
    intros . fstart. destruct op; fsplit.
    - fintros "[P T]". fsplit. fapply . ctx. fapply . ctx.
    - fintros "[P C]". fsplit. fapply . ctx. fapply . ctx.
    - fintros "[P|T]". fleft. fapply . ctx. fright. fapply . ctx.
    - fintros "[P|C]". fleft. fapply . ctx. fright. fapply . ctx.
    - fintros "H" "P". fapply . fapply "H". fapply . ctx.
    - fintros "H" "P". fapply . fapply "H". fapply . ctx.
  Qed.


  Lemma frewrite_equiv_quant A op phi psi :
    A ( ) A (quant op [] quant op []).
  Proof.
    intros E. fstart. destruct op; fsplit.
    - fintros "H" x. fapply E. fapply ("H" $0).     - fintros "H" x. fapply E. fapply ("H" $0).
    - fintros "[x H]". apply ExI with (t := x); simpl_subst. fapply E. ctx.
    - fintros "[x H]". apply ExI with (t := x); simpl_subst. fapply E. ctx.
  Qed.


  Lemma frewrite_equiv_switch A phi psi :
    A ( ) A ( ).
  Proof.
    intros E. fdestruct E. fsplit; ctx.
  Qed.


  Context {eq_dec_Funcs : EqDec syms}.
  Context {eq_dec_Preds : EqDec preds}.

  Lemma frewrite_equiv_bin_l_T T op phi psi theta :
    T ( ) T (bin op bin op ).
  Proof.
    intros [A [ ]]. exists A. split. easy. now apply frewrite_equiv_bin_l.
  Qed.


  Lemma frewrite_equiv_bin_r_T T op phi psi theta :
    T ( ) T (bin op bin op ).
  Proof.
    intros [A [ ]]. exists A. split. easy. now apply frewrite_equiv_bin_r.
  Qed.


  Lemma frewrite_equiv_bin_lr_T T op phi psi theta chi :
    T ( ) T ( chi) T (bin op bin op chi).
  Proof.
    intros [A [ ]] [B [ ]]. exists (List.app A B). split.
    now apply contains_app. apply frewrite_equiv_bin_lr.
    eapply Weak. apply . now apply incl_appl.
    eapply Weak. apply . now apply incl_appr.
  Qed.


  Lemma frewrite_equiv_quant_T T op phi psi :
    T ( ) T (quant op [] quant op []).
  Proof.
    intros [A [ ]]. exists A. split. easy. now apply frewrite_equiv_quant.
  Qed.


  Lemma frewrite_equiv_switch_T T phi psi :
    T ( ) T ( ).
  Proof.
    intros [A [ ]]. exists A. split. easy. now apply frewrite_equiv_switch.
  Qed.


End FrewriteEquiv.

Ltac contains f := match with f idtac | context P [ f ] idtac end.

Ltac frewrite_equiv_solve H :=
  match get_form_goal with
  | apply H
  | bin ?op ?l ?r _ (
      tryif contains l
      then (tryif contains r
        then apply_compat frewrite_equiv_bin_lr frewrite_equiv_bin_lr_T
        else apply_compat frewrite_equiv_bin_l frewrite_equiv_bin_l_T)
      else apply_compat frewrite_equiv_bin_r frewrite_equiv_bin_r_T
    );
    frewrite_equiv_solve H
  | quant _ _ _ _
    apply_compat frewrite_equiv_quant frewrite_equiv_quant_T;
    frewrite_equiv_solve H
  end.

Ltac frewrite_replace_all t s :=
  match with
  | context C[t] let := context C[s] in frewrite_replace_all t s
  | _
  end.

Fixpoint up_n `{funcs_signature} n sigma := match n with
| 0
| S n' up (up_n n' )
end.

Ltac shift_n n t :=
  match n with
  | 0 t
  | S ?n' shift_n n' (t`[])
  end.

Ltac vector_map_ltac v f :=
  match v with
  | Vector.nil ?t constr:(Vector.nil t)
  | @Vector.cons _ ?x _ ?v'
    let x' := f x in
    let v'' := vector_map_ltac v' f in
    constr:(@Vector.cons _ x' _ v'')
  end.

Ltac add_shifts' n t G :=
  let f := add_shifts' n t in
  let t_shifted := shift_n n t in
  match G with
  
  | t_shifted constr:(($n)`[up_n n t..])
  | $(?m) constr:(($m)`[up_n n ]`[up_n n t..])
  | func ?fu ?vec let vec' := vector_map_ltac vec f in constr:(func fu vec')
  
  | falsity constr:(falsity[up_n n ][up_n n t..])
  | atom ?P ?vec let vec' := vector_map_ltac vec f in constr:(atom P vec')
  | bin ?op ?u ?v let u' := f u in let v' := f v in constr:(bin op u' v')
  | quant ?op ?u let u' := add_shifts' (S n) t u in constr:(quant op u')
  
  | ?u match type of u with
      | form constr:(u[up_n n ][up_n n t..])
      | term constr:(u`[up_n n ]`[up_n n t..])
      end
  end.
Ltac add_shifts := add_shifts' 0.

Ltac remove_shifts G t :=
  match G with
  | context C[ ?s[up_n ?n ][up_n ?n t..] ] let G' := context C[ s[up_n n ] ] in remove_shifts G' t
  | context C[ ?s`[up_n ?n ]`[up_n ?n t..] ] let G' := context C[ s`[up_n n ] ] in remove_shifts G' t
  | context C[ ($ ?n)`[up_n ?n t..] ] let G' := context C[ $n ] in remove_shifts G' t
  | _ G
  end.

Ltac repeat_n n tac := match n with 0 idtac | S ?n' tac; repeat_n n' tac end.

Ltac frewrite' T A back ::= contxt
  let H := fresh "H" in
  turn_into_hypothesis T H contxt;
  fspecialize_list H A;
  instantiate_evars H;
  simpl_subst H;

  
  custom_unfold;

  
  repeat match goal with
  | [ |- context C[ x $(S x)] ] let G := context C[] in change G
  end;

  match get_form_hyp H with
  
  | ?_phi ?_psi
    let := match back with true _psi | false _phi end in
    let := match back with true _phi | false _psi end in
    match back with true apply_compat frewrite_equiv_switch frewrite_equiv_switch_T in H | _ idtac end;

    let G := get_form_goal in
    let G' := frewrite_replace_all G in
    let E := fresh "E" in
    assert_compat (G G') as E;
    [ frewrite_equiv_solve H |];
    feapply E;
    clear E

  
  | atom ?p (@Vector.cons _ ?_t _ (@Vector.cons _ ?_t' _ (Vector.nil term)))
    
    assert (p = Eq_pred) as _ by reflexivity;

    let t := match back with true _t' | false _t end in
    let t' := match back with true _t | false _t' end in
    
    
    let C := get_context_goal in
    let G := get_form_goal in
    let X := fresh in
    let G' := add_shifts t G in
    match goal with
    | [ |- _ _ ] enough (C G') as X
    | [ |- _ _ ] enough (C G') as X
    end;
    [
      repeat match type of X with context K[ ?u`[up_n ?n ]`[up_n ?n t..] ]
        let R := fresh in
        
        assert (u`[up_n n ]`[up_n n t..] = u) as R; [
          rewrite subst_term_comp; apply subst_term_id;
          let a := fresh in intros a;
          (repeat_n n ltac:(try destruct a)); reflexivity |];
        rewrite R in X
      end;
      apply X
    |];
    
    
    match goal with
    | [ |- ?U ?G ] let G' := remove_shifts G t in change (U G'[t..])
    | [ |- ?U ?G ] let G' := remove_shifts G t in change (U G'[t..])
    end;

    
    let t'' := fresh "t" in
    match goal with
    | [ |- prv _ _ ] eapply leibniz
    | [ |- tprv _ _ ] fail "Rewrite not supported under theories"
    end;
    [
      
      try (repeat (try apply incl_refl; apply incl_tl));
      
      firstorder
    |
      match back with
      | false
          apply sym;
          [ try (repeat (try apply incl_refl; apply incl_tl)); firstorder
          | fapply H ]
      | true apply H
      end
    | ];
    
    
    cbn -[up_n];
    
    
    

    
    repeat match goal with [ |- context C[up_n ?n ?a] ]
      let G' := context C[ ($a)[up_n n ] ] in change G'
    end;

    
    repeat match goal with
    | [ |- context C[up_n ?n (up ?s)]] let G' := context C[up_n (S n) s] in change G'
    | [ |- context C[up ?s]] let G' := context C[up_n 1 s] in change G'
    end;

    
    repeat match goal with [ |- context K[ ?u `[up_n ?n ]`[up_n ?n t'..] ]]
      let R := fresh in
      
      assert (u`[up_n n ]`[up_n n t'..] = u) as R; [
        rewrite subst_term_comp; apply subst_term_id;
        let a := fresh in intros a;
        (repeat_n n ltac:(try destruct a)); reflexivity |];
      rewrite ! R;
      clear R
    end;

    
    cbn; try rewrite !subst_shift; try rewrite !subst_term_shift;

    
    custom_fold;
    simpl_subst
  end;
  clear H.

Tactic Notation "frewrite" constr(T) := make_compatible ltac:(frewrite' T constr:([] : list form) constr:(false)).
Tactic Notation "frewrite" "(" constr(T) constr() ")" := make_compatible ltac:(frewrite' T constr:([]) constr:(false)).
Tactic Notation "frewrite" "(" constr(T) constr() constr() ")" := make_compatible ltac:(frewrite' T constr:([;]) constr:(false)).
Tactic Notation "frewrite" "(" constr(T) constr() constr() constr() ")" := make_compatible ltac:(frewrite' T constr:([;;]) constr:(false)).

Tactic Notation "frewrite" "" constr(T) := make_compatible ltac:(frewrite' T constr:([] : list form) constr:(true)).
Tactic Notation "frewrite" "" "(" constr(T) constr() ")" := make_compatible ltac:(frewrite' T constr:([]) constr:(true)).
Tactic Notation "frewrite" "" "(" constr(T) constr() constr() ")" := make_compatible ltac:(frewrite' T constr:([;]) constr:(true)).
Tactic Notation "frewrite" "" "(" constr(T) constr() constr() constr() ")" := make_compatible ltac:(frewrite' T constr:([;;]) constr:(true)).

Ltac fexists x := make_compatible ltac:( _
  apply (ExI (t := x));
  simpl_subst).