Require Import HoTT.

(* perhaps too much is imported here *)
From GCTT
     Require Import equiv_lemmas sigma_lemmas quotients finite_types finite.permutations monoidal_1type.
From GCTT Require Import fintype_monoidal group_complete_category.

(** Would have been better to define this stuff before fintype_monoidal, but but.*)
Require Import monoidal_category.
Section Iso_Fin.
  Definition iso_fin : PreCategory.
  Proof.
    srapply (Build_PreCategory (fun A B : FinType => A <~> B)).
    - intros. cbn. reflexivity.
    - intros A B C. cbn. intros f g.
      exact (f oE g).
    - intros A B C D. cbn. intros f g h.
      apply ecompose_ee_e.
    - intros A B. cbn. intro f.
      apply ecompose_1e.
    - intros A B. cbn. intro f.
      apply ecompose_e1.
  Defined.

  Definition isgroupoid_isofin {A B : iso_fin} (f : morphism iso_fin A B)
    : IsIsomorphism f.
  Proof.
    simpl in f.
    apply (Build_IsIsomorphism _ A B  f (equiv_inverse f)); apply path_equiv; apply path_arrow.
    - apply (eissect f).
    - apply (eisretr f).
  Defined.

  Definition iso_fin_to_pg_fin : Functor iso_fin (Core.groupoid_category FinType).
  Proof.
    srapply (Build_Functor iso_fin (Core.groupoid_category FinType) idmap).
    - intros A B. cbn. apply path_FinType.
    - intros A B C. intros f g. hnf.
      apply path_FinType_compose.
    - intro A. hnf. apply path_FinType_1.
  Defined.

  Definition isfullyfaithful_iso_to_pgfin : forall (A B : iso_fin),
      IsEquiv (morphism_of iso_fin_to_pg_fin (s := A) (d := B)).
  Proof.
    simpl. intros A B. exact _.
  Defined.

  Definition iso_fin_sum : Functor (iso_fin * iso_fin) iso_fin.
  Proof.
    srapply @Build_Functor.
    - intros [A B]. exact (sum_FinType A B).
    - intros [A1 B1] [A2 B2]. simpl.
      intros [alpha betta]. exact (alpha +E betta).
    - cbn. intros [A1 B1] [A2 B2] [A3 B3]. cbn.
      intros [alpha1 betta1] [alpha2 betta2]. cbn.
      apply path_equiv. apply path_arrow. intros [i | j]; reflexivity.
    - cbn. intros [A B]. simpl. unfold reflexive_equiv.
      apply path_equiv. apply path_arrow. intros [i | j]; reflexivity.
  Defined.

  Definition moncat_isofin : Symmetric_Monoidal_Category.
  Proof.
    srefine (Build_Symmetric_Monoidal_Category
               (Build_Cat_Magma _ iso_fin_sum) FinType_id
               (fun A B C => equiv_sum_assoc A B C)
               _ _
               (fun A => sum_empty_l A)
               _ _
               (fun A => sum_empty_r A)
               _ _
               (fun A B => equiv_sum_symm A B ) _ _ _ _ _ _ _); cbn.
    - intros. apply isgroupoid_isofin.
    - intros A B C A' B' C'. intros f g h.
      apply path_equiv; apply path_arrow.
      intro x. destruct x as [[i | j] | k]; reflexivity.
    - intro A. apply isgroupoid_isofin.
    - intros A B f.
      apply path_equiv; apply path_arrow.
      intro x. destruct x as [[] | i]. reflexivity.
    - intro A. apply isgroupoid_isofin.
    - intros A B f.
      apply path_equiv; apply path_arrow.
      intro x. destruct x as [i | []]. reflexivity.
    - intros. apply isgroupoid_isofin.
    - intros A B A' B' f g.
      apply path_equiv; apply path_arrow.
      intro x. destruct x as [i | i]; reflexivity.
    - intros A B.
      apply path_equiv; apply path_arrow.
      intro x. destruct x as [i | i]; reflexivity.
    - intros A B.
      apply path_equiv; apply path_arrow.
      intro x. destruct x as [[[] | i] | i]; reflexivity.
    - intros A B.
      apply path_equiv; apply path_arrow.
      intro x. destruct x as [[i | []] | i]; reflexivity.
    - intros A B C D.
      apply path_equiv; apply path_arrow.
      intro x. destruct x as [[[i | i] | i] | i]; reflexivity.
    - intros A B C.
      apply path_equiv; apply path_arrow.
      intro x. destruct x as [[i | i] | i]; reflexivity.
  Defined.

End Iso_Fin.

Section Group_Complete_Iso_Fin.
  Definition group_complete_isofin := group_completion_moncat moncat_isofin.

  Definition sum_group_complete_isofin :
    Functor (group_complete_isofin * group_complete_isofin) group_complete_isofin.
  Proof.
    srapply @Build_Functor; cbn.
    - intros [[A1 B1] [A2 B2]].
      exact ((sum_FinType A1 A2, sum_FinType B1 B2)).
    - intros [[A1 B1] [A2 B2]] [[A3 B3] [A4 B4]]. simpl.
      intros [f1 f2]. revert f1 f2. simpl.
      srapply @set_quotient_rec2'.
      + intros f1 f2. simpl. apply class_of.
        apply (sum_group_completion_1' moncat_isofin f1 f2).
      + simpl. intros f f' g H.
        apply related_classes_eq.
        apply (welldef_sum_group_completion_1 moncat_isofin f f' g g H).
        apply refl_monact_relation.
      + simpl. intros f g g' H.
        apply related_classes_eq.
        srefine (welldef_sum_group_completion_1 moncat_isofin f f g g' _ H).
        apply refl_monact_relation.
    - simpl. intros [[a1 a2] [b1 b2]] [[c1 c2] [d1 d2]] [[e1 e2] [f1 f2]].
      simpl. intros [alpha betta] [gamma dela]. simpl.
      revert alpha.
      apply set_quotient_ind_prop.
      { intro alpha. exact _. }
      intro alpha. revert betta.
      apply set_quotient_ind_prop.
      { intro betta. exact _. }
      intro betta. revert gamma.
      apply set_quotient_ind_prop.
      { intro gamma. exact _. }
      intro gamma. revert dela.
      apply set_quotient_ind_prop.
      { intro dela. exact _. }
      intro dela. simpl.
      apply related_classes_eq.
      destruct alpha as [s alpha].
      destruct betta as [t betta].
      destruct gamma as [u gamma].
      destruct dela as [v dela].
      srapply @exist.
      { simpl. apply (Build_Isomorphic (isiso_twist_middle moncat_isofin _ _ _ _ )). }
      cbn.
      destruct alpha as [alpha1 alpha2]. destruct betta as [betta1 betta2].
      destruct gamma as [gamma1 gamma2]. destruct dela as [delta1 delta2]. simpl in *.
      simpl.
      apply path_prod; simpl.
      (** Here comes the coherence  *)
      { apply path_equiv. apply path_arrow. intro x.
        destruct x as [[[i|i] | [i|i]] | [i|i]]; reflexivity. }
      { apply path_equiv. apply path_arrow. intro x.
        destruct x as [[[i|i] | [i|i]] | [i|i]]; reflexivity. }
    - intros [[A1 A2] [B1 B2]]. simpl.
      apply related_classes_eq.
      unfold monact_relation. srapply @exist.
      { simpl.
        apply (Build_Isomorphic (isiso_moncat_lid moncat_isofin FinType_id)). }
      simpl.
      apply path_prod; simpl.
      (** This is probably not difficult to show from naturality and coherence triangles, but we use the hamme*)
      { apply path_equiv; apply path_arrow. intro x.
        destruct x as [[[] | []] | [i | i]]; reflexivity. }
      { apply path_equiv; apply path_arrow. intro x.
        destruct x as [[[] | []] | [i | i]]; reflexivity. }
  Defined.

  Definition FinFin_to_GrpCompl : Functor (iso_fin * iso_fin) group_complete_isofin.
  Proof.
    apply cat_to_actcat.
  Defined.

  Definition assoc_group_complete_isofin (A1 A2 A3 : group_complete_isofin)
    : morphism _ (sum_group_complete_isofin (sum_group_complete_isofin (A1, A2), A3))
               (sum_group_complete_isofin (A1, sum_group_complete_isofin (A2, A3))).
  Proof.
    srefine (FinFin_to_GrpCompl _1 _). cbn.
    (* simpl. apply class_of. *)
    (* exists (moncat_id _). *)
    simpl. (* destruct A1 as [A1 B1]. destruct A2 as [A2 B2]. destruct A3 as [A3 B3]. simpl. *)
    exact (equiv_sum_assoc _ _ _ , equiv_sum_assoc _ _ _ ).
  Defined.

  Definition lid_group_complete_isofin (A : group_complete_isofin)
    : morphism _ (sum_group_complete_isofin ((FinType_id, FinType_id), A)) A.
  Proof.
    srefine (FinFin_to_GrpCompl _1 _). cbn.
    exact (sum_empty_l _ , sum_empty_l _).
  Defined.

  Definition rid_group_complete_isofin (A : group_complete_isofin)
    : morphism _ (sum_group_complete_isofin (A, (FinType_id, FinType_id))) A.
  Proof.
    srefine (FinFin_to_GrpCompl _1 _). cbn.
    exact (sum_empty_r _ , sum_empty_r _).
  Defined.

  Definition sym_group_complete_isofin (A B : group_complete_isofin)
    : morphism _ (sum_group_complete_isofin (A, B)) (sum_group_complete_isofin (B, A)).
  Proof.
    srefine (FinFin_to_GrpCompl _1 _). cbn.
    exact (equiv_sum_symm _ _, equiv_sum_symm _ _).
  Defined.

  (* Move result to other place *)
  (** The symmetric monoidal structure of the category of finite types and isomorphisms.  *)
  (** Should ideally restructure somewhat since now this takes a LONG time...  *)
  Definition moncat_group_complete_isofin : Symmetric_Monoidal_Category.
  Proof.
    srefine (Build_Symmetric_Monoidal_Category
               (Build_Cat_Magma _ sum_group_complete_isofin) (FinType_id ,FinType_id)
               assoc_group_complete_isofin _ _
               lid_group_complete_isofin _ _
               rid_group_complete_isofin _ _
               sym_group_complete_isofin _ _
               _ _(* (fun A => sum_empty_r A) *)
               _ _
               _ ).
    - intros A B C. unfold assoc_group_complete_isofin.
      apply (iso_functor FinFin_to_GrpCompl).
      apply prod_isisomorphism; apply isgroupoid_isofin.
    - intros A B C A' B' C'. simpl in A, B, C, A', B', C'.
      intros f g h. revert f.
      apply set_quotient_ind_prop.
      { intro x. exact _. }
      intro f.
      revert g.
      apply set_quotient_ind_prop.
      { intro x. exact _. }
      intro g. revert h.
      apply set_quotient_ind_prop.
      { intro x. exact _. }
      intro h.
      destruct f as [s f]. destruct g as [t g]. destruct h as [u h]. simpl in *.
      apply related_classes_eq.
      srapply @exist.
      { simpl.
        (* Cheat a little since all morphisms in iso_fin are isomorphisms *)
        srefine (Build_Isomorphic (isgroupoid_isofin _)).
        refine (moncat_sym (moncat_isofin) FinType_id (sum_FinType s (sum_FinType t u)) o _).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        srefine (identity _ +^ _). apply moncat_assoc. }
      hnf. destruct f as [f1 f2]. destruct g as [g1 g2]. destruct h as [h1 h2]. hnf.
      destruct A as [A1 A2]. destruct B as [B1 B2]. destruct C as [C1 C2].
      destruct A' as [A1' A2']. destruct B' as [B1' B2']. destruct C' as [C1' C2'].
      unfold monoidal_action_cat_compose'. simpl.
      apply path_prod; cbn.
      (* here comes the coherence *)
      { apply path_equiv. apply path_arrow.
        intro x. destruct x as [[[] | i] | i]; repeat destruct i as [i | i]; reflexivity. }
      { apply path_equiv. apply path_arrow.
        intro x. destruct x as [[[] | i] | i]; repeat destruct i as [i | i]; reflexivity. }
    - intro A. unfold lid_group_complete_isofin.
      apply (iso_functor FinFin_to_GrpCompl).
      apply prod_isisomorphism; apply isgroupoid_isofin.
    - intros A A'. simpl in A, A'.
      apply set_quotient_ind_prop.
      {intro x. exact _. }
      intro f. destruct f as [s [f1 f2]]. simpl in f1, f2.
      destruct A as [A1 A2]. destruct A' as [A1' A2']. simpl.
      apply related_classes_eq.
      srapply @exist.
      { simpl.
        (* Cheat a little since all morphisms in iso_fin are isomorphisms *)
        srefine (Build_Isomorphic (isgroupoid_isofin _)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        srefine (_ o moncat_lid moncat_isofin _).
        apply moncat_sym. }
      hnf. unfold monoidal_action_cat_compose'. simpl.
      (* coherence: *)
      apply path_prod; simpl;
        apply path_equiv; apply path_arrow; intro i;
          repeat try destruct i as [i | i]; try destruct i as []; reflexivity.
    - intro A.
      apply (iso_functor FinFin_to_GrpCompl).
      apply prod_isisomorphism; apply isgroupoid_isofin.
    - intros A A'. simpl in A, A'.
      apply set_quotient_ind_prop.
      { intro x. exact _. }
      intros [s [f1 f2]]. simpl in f1, f2.
      destruct A as [A1 A2]. destruct A' as [A1' A2']. simpl in *.
      apply related_classes_eq.
      srapply @exist.
      { simpl.
        srefine (Build_Isomorphic (isgroupoid_isofin _)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        srefine (moncat_lid moncat_isofin _). }
      (* coherence *)
      apply path_prod; simpl;
        apply path_equiv; apply path_arrow; intro i;
          repeat try destruct i as [i | i]; try destruct i as []; reflexivity.
    - intros A B.
      apply (iso_functor FinFin_to_GrpCompl).
      apply prod_isisomorphism; apply isgroupoid_isofin.
    - intros [A1 A2] [B1 B2] [A1' A2'] [B1' B2'].
      intros f g. revert f.
      apply set_quotient_ind_prop.
      { intro x. exact _. } intro f.
      revert g.
      apply set_quotient_ind_prop.
      { intro x. exact _. } intro g.
      destruct f as [s f]. destruct g as [t g]. simpl in *.
      apply related_classes_eq.
      srapply @exist.
      { simpl. srefine (Build_Isomorphic (isgroupoid_isofin _)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        srefine (moncat_sym moncat_isofin _ _ o _).
        srefine (1 +^ _). apply moncat_sym. }
      (* coherence *)
      apply path_prod; simpl;
        apply path_equiv; apply path_arrow; intro i;
          repeat try destruct i as [i | i]; try destruct i as []; reflexivity.
    - intros [A1 A2] [B1 B2]. simpl.
      apply related_classes_eq.
      srapply @exist.
      { simpl. srefine (Build_Isomorphic (isgroupoid_isofin _)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        apply (moncat_lid moncat_isofin). }
      (* coherence *)
      apply path_prod; simpl;
        apply path_equiv; apply path_arrow; intro i;
          repeat try destruct i as [i | i]; try destruct i as []; reflexivity.
    - intros [A1 A2] [B1 B2]. simpl.
      apply related_classes_eq.
      srapply @exist.
      { reflexivity. }
      (* coherence *)
      apply path_prod; simpl;
        apply path_equiv; apply path_arrow; intro i;
          repeat try destruct i as [i | i]; try destruct i as []; reflexivity.
    - intros [A1 A2] [B1 B2]. simpl.
      apply related_classes_eq.
      srapply @exist.
      { simpl.
        srefine (Build_Isomorphic (isgroupoid_isofin _)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        apply (moncat_rid moncat_isofin). }
      (* coherence *)
      apply path_prod; simpl;
        apply path_equiv; apply path_arrow; intro i;
          repeat try destruct i as [i | i]; try destruct i as []; reflexivity.
    - intros [A1 A2] [B1 B2] [C1 C2] [D1 D2]. simpl.
      apply related_classes_eq.
      srapply @exist.
      { simpl.  apply symmetry.
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        srefine (Build_Isomorphic (isgroupoid_isofin _)).
        srefine (_ +^ (moncat_lid moncat_isofin FinType_id) ).
        srefine (_ o moncat_rid moncat_isofin _ ).
        srefine (moncat_rid moncat_isofin _ ). }
      (* coherence *)
      apply path_prod; simpl;
        apply path_equiv; apply path_arrow; intro i;
          repeat try destruct i as [i | i]; try destruct i as []; reflexivity.
    - intros [A1 A2] [B1 B2] [C1 C2]. simpl.
      apply related_classes_eq.
      srapply @exist.
      { simpl. apply symmetry.
        srefine (Build_Isomorphic (isgroupoid_isofin _)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        change (sum_FinType ?a ?b) with (@binary_op moncat_isofin (a, b)).
        srefine (_ +^ (moncat_lid moncat_isofin FinType_id) ).
        exact ((moncat_lid moncat_isofin FinType_id) +^ 1). }
      (* coherence *)
      apply path_prod; simpl;
        apply path_equiv; apply path_arrow; intro i;
          repeat try destruct i as [i | i]; try destruct i as []; reflexivity.
  Defined.

End Group_Complete_Iso_Fin.


Load LFindLoad.
From lfind Require Import LFind.
From QuickChick Require Import QuickChick.
From adtind Require Import goal33.

Derive Show for natural.

              Derive Arbitrary for natural.

              Instance Dec_Eq_natural : Dec_Eq natural.

              Proof. dec_eq. Qed.

Lemma conj32synthconj1 : forall  (lv0 : natural) (lv1 : natural), (@eq natural (plus (plus (mult lv0 lv1) lv1) (Succ lv0)) (plus Zero (mult (Succ lv0) (Succ lv1)))).
Admitted.
QuickChick conj32synthconj1.


Load LFindLoad.
From lfind Require Import LFind.
From QuickChick Require Import QuickChick.
From adtind Require Import goal33.

Derive Show for natural.

              Derive Arbitrary for natural.

              Instance Dec_Eq_natural : Dec_Eq natural.

              Proof. dec_eq. Qed.

Lemma conj6synthconj2 : forall  (lv0 : natural) (lv1 : natural) (lv2 : natural) (lv3 : natural), (@eq natural (mult lv0 (Succ Zero)) (plus (plus (mult lv1 lv2) lv2) lv3)).
Admitted.
QuickChick conj6synthconj2.


Set Implicit Arguments.
Require Import Metalib.Metatheory.
Require Import Program.Equality.
Require Export Decidability.
Require Export Coq.micromega.Lia.

Ltac inv H := inversion H; subst.

Module Algo.

(* This is the Algo typing for
   Iso + Fsub, without struct unfold rule
*)

Inductive exposure : env -> typ -> typ -> Prop :=
  | exposure_nat: forall E,
      exposure E typ_nat typ_nat
  | exposure_top : forall E,
      exposure E typ_top typ_top
  | exposure_trans_tvar : forall U E T X,
      binds X (bind_sub U) E ->
      exposure E U T ->
      exposure E (typ_fvar X) T
  | exposure_arrow: forall E A1 A2,
      exposure E (typ_arrow A1 A2) (typ_arrow A1 A2)
  | exposure_all : forall E S T,
      exposure E (typ_all S T) (typ_all S T)
  | exposure_rec: forall E A,
      exposure E (typ_mu A) (typ_mu A)
  | exposure_label: forall E X A,
      exposure E (typ_label X A) (typ_label X A)
.

Inductive typing : env -> exp -> typ -> Prop :=
  | typing_nat: forall G,
    wf_env G ->
    typing G (exp_nat) (typ_nat)
  | typing_var : forall E x T,
      wf_env E ->
      binds x (bind_typ T) E ->
      typing E (exp_fvar x) T
  | typing_abs : forall L E V e1 T1,
      (forall x : atom, x `notin` L ->
        typing (x ~ bind_typ V ++ E) (open_ee e1 x) T1) ->
      typing E (exp_abs V e1) (typ_arrow V T1)
  | typing_app : forall T11 T12 E e1 e2 T1 T2,
      typing E e1 T1 ->
      exposure E T1 (typ_arrow T11 T12) ->
      typing E e2 T2 ->
      sub E T2 T11 ->
      typing E (exp_app e1 e2) T12
  | typing_tabs : forall L E V e1 T1,
      (forall X : atom, X `notin` L ->
        typing (X ~ bind_sub V ++ E) (open_te e1 X) (open_tt T1 X)) ->
      typing E (exp_tabs V e1) (typ_all V T1)
  | typing_tapp : forall E e1 T1 T11 T12 T2,
      typing E e1 T1 ->
      exposure E T1 (typ_all T11 T12) ->
      sub E T2 T11 ->
      typing E (exp_tapp e1 T2) (open_tt T12 T2)
  | typing_fold : forall G A T e ,
     typing G e T ->
     (* exposure G T (open_tt A  (typ_mu A))    -> *)
     sub G T (open_tt A (typ_mu A)) ->
     WF G (typ_mu A) ->
     typing G (exp_fold (typ_mu A) e) (typ_mu A)
 | typing_unfold : forall G A' T e,
     typing G e A' ->
     sub G A'(typ_mu T) ->
     typing G (exp_unfold (typ_mu T) e)  (open_tt T (typ_mu T))
     (* typing G e A -> sub G A (typ_mu T) ->
     typing G (exp_unfold A e)  (open_tt T A) *)
.

Hint Constructors exposure.

Lemma exposure_sound: forall E A B,
  wf_env E -> WF E A ->
  exposure E A B -> sub E A B.
Proof with auto.
  intros.
  induction H1;try solve [apply Reflexivity;auto]...
  - eapply sa_trans_tvar with (U:=U)...
    apply IHexposure... apply WF_from_binds_typ with (x:=X)...
Qed.

Definition not_var_typ U :=
  match U with
  | typ_fvar _ => False
  | _ => True
  end.

Lemma exposure_promote: forall E S T U,
  WF E T ->
  exposure E S T -> sub E S U -> not_var_typ U ->
  sub E T U.
Proof with auto.
  intros. generalize dependent T.
  induction H1;intros...
  - inv H1...
  - inv H2...
  - inv H3. apply IHsub...
    apply sub_regular in H1. destruct_hypos.
    pose proof binds_uniq _ _ _ H1 H H5.
    subst...
  - inv H0...
  - inv H4...
    eapply sa_all with (L:=L)...
  - inv H5. eapply sa_rec with (L:=L)...
  - inv H0...
Qed.

Lemma exposure_weakening: forall E S T X U,
  exposure E S T ->
  exposure (X ~ bind_sub U ++ E) S T.
Proof with auto.
  intros. induction H...
  eapply exposure_trans_tvar with (U:=U0)...
Qed.

Lemma exposure_weakening_bind: forall E S T a,
  exposure E S T ->
  exposure (a :: E) S T.
Proof with auto.
  intros. induction H...
  rewrite_env ([ a ] ++ E)...
  eapply exposure_trans_tvar with (U:=U)...
Qed.

Lemma exposure_weakening_bind2: forall E1 E2 S T a,
  exposure (E1 ++ E2) S T ->
  exposure (E1 ++ a ++ E2) S T.
Proof with auto.
  intros. dependent induction H...
  eapply exposure_trans_tvar with (U:=U)...
Qed.

Lemma exposure_ex: forall E A,
  wf_env E -> WF E A -> exists B,
  exposure E A B /\ not_var_typ B /\ WF E B.
Proof with auto.
  intros. revert A H0.
  induction E;intros...
  {
    dependent induction H0.
    (* + dependent induction H0. *)
    - exists typ_top;simpl...
    - exists typ_nat;simpl...
    - inv H0.

    - exists (typ_arrow A B).
      split;simpl...
    - exists (typ_all T1 T2).
      repeat split;simpl...
      apply WF_all with (L:=L)...
    - exists (typ_mu A).
      repeat split;simpl...
      apply WF_rec with (L:=L)...
    - exists (typ_label X A).
      repeat split;simpl...
  }

  {
    dependent induction H0.
    (* + dependent induction H0. *)
    - exists typ_top;simpl...
    - exists typ_nat;simpl...
    - inv H0.
      + inv H. dependent induction H5.
        * exists typ_top. repeat split;simpl...
          apply exposure_trans_tvar with (U:=typ_top)...
        * exists typ_nat. repeat split;simpl...
          apply exposure_trans_tvar with (U:=typ_nat)...
        * pose proof WF_from_binds_typ _ _ H4 H1.
          destruct (IHE H4 _ H2). destruct_hypos.
          exists x. repeat split;simpl...
          { apply exposure_trans_tvar with (U:= X0)...
            apply exposure_trans_tvar with (U:= U)...
            rewrite_env (X ~ bind_sub X0 ++ E).
            apply exposure_weakening...
          }
          { rewrite_env (nil ++ X ~ bind_sub X0 ++ E).
            eapply WF_weakening... }
        * inv H. exists (typ_arrow A B).
          repeat split;simpl...
          { apply exposure_trans_tvar with (U:=(typ_arrow A B))... }
          { rewrite_env (nil ++ X ~ bind_sub (typ_arrow A B) ++ E).
            eapply WF_weakening... }
        * inv H. exists (typ_all T1 T2).
          repeat split;simpl...
          { apply exposure_trans_tvar with (U:=(typ_all T1 T2))... }
          { rewrite_env (nil ++ X ~ bind_sub (typ_all T1 T2) ++ E).
            eapply WF_weakening... }
        * inv H. exists (typ_mu A).
          repeat split;simpl...
          { apply exposure_trans_tvar with (U:=(typ_mu A))... }
          { rewrite_env (nil ++ X ~ bind_sub (typ_mu A) ++ E).
            eapply WF_weakening... }
        * inv H. exists (typ_label X0 A).
          repeat split;simpl...
          { apply exposure_trans_tvar with (U:= (typ_label X0 A))... }
          { rewrite_env (nil ++ X ~ bind_sub  (typ_label X0 A) ++ E).
            eapply WF_weakening... }
      + assert (wf_env E). { inv H... }
        assert (WF E U).
        { eapply WF_from_binds_typ... apply H1. }
        destruct (IHE H2 _ H3) as [S ]. destruct_hypos.
        exists S. repeat split;simpl...
        { apply exposure_trans_tvar with (U:= U)...
          apply exposure_weakening_bind...
        }
        { rewrite_env (nil ++ [a] ++ E).
          apply WF_weakening... }
    - exists (typ_arrow A B).
      split;simpl...
    - exists (typ_all T1 T2).
      repeat split;simpl...
      apply WF_all with (L:=L)...
    - exists (typ_mu A).
      repeat split;simpl...
      apply WF_rec with (L:=L)...
    - exists (typ_label X A).
      repeat split;simpl...
  }
Qed.

Lemma exposure_WF: forall E A B,
  wf_env E -> WF E A -> exposure E A B ->
  WF E B.
Proof.
  intros.
  pose proof exposure_sound H H0 H1.
  apply sub_regular in H2.
  destruct_hypos. auto.
Qed.

Lemma typing_regular : forall E e T,
  typing E e T ->
  wf_env E /\ expr e /\ WF E T.
Proof with auto.
  intros.
  induction H;destruct_hypos...
  -
    repeat split...
    apply wf_typ_from_binds_typ with (x:=x)...
  -
    pick fresh Y.
    assert (wf_env (Y ~ bind_typ V ++ E)).
    specialize_x_and_L Y L...
    destruct_hypos...
    dependent destruction H1...
    repeat split...
    +
      apply expr_abs with (L:=L)...
      apply WF_type with (E:=E) ...
      intros.
      apply H0...
    +
      constructor...
      specialize_x_and_L Y L...
      destruct_hypos.
      add_nil.
      apply wf_typ_strengthening with (x:=Y) (U:=V)...
  -
    pose proof exposure_WF H3 H8 H0.
    dependent destruction H9...
  -
    pick fresh Y.
    assert (wf_env (Y ~ bind_sub V ++ E)).
    specialize_x_and_L Y L...
    destruct_hypos...
    dependent destruction H1...
    repeat split...
    +
      apply expr_tabs with (L:=L)...
      apply WF_type with (E:=E) ...
      intros.
      apply H0...
    +
      apply WF_all with (L:=L)...
      intros.
      eapply H0...
  -
    pose proof exposure_WF H2 H4 H0.
    dependent destruction H5.
    repeat split...
    constructor...
    get_type...
    pick fresh X.
    rewrite subst_tt_intro with (X:=X)...
    rewrite_env (map (subst_tb X  T2) nil ++ E).
    apply subst_tb_wf with (Q:=bind_sub T11)...
    apply H6...
    get_well_form...
  -
    repeat split...
    constructor...
    apply WF_type with (E:=G)...
  -
    repeat split...
    constructor...
    apply WF_type with (E:=G)... { get_well_form... }
    apply sub_regular in H0. destruct_hypos...
    apply wf_open_rec...
Qed.

Lemma exposure_typ_strengthening: forall E1 E2 S T X U,
  exposure (E1 ++ X ~ bind_typ U ++ E2) S T ->
  exposure (E1 ++ E2) S T.
Proof with auto.
  intros. dependent induction H...
  eapply exposure_trans_tvar with (U:=U0)...
  2:{ apply IHexposure with (X0:=X) (U1:=U)... }
  apply binds_app_iff in H. destruct H.
  { apply binds_app_iff. left... }
  apply binds_app_iff in H. destruct H.
  { inv H. inv H1. inv H1. }
  apply binds_app_iff. right...
Qed.

Lemma typing_replacing: forall E1 E2 e T U X Y,
  typing (E1++ X ~ bind_typ U ++E2) e T ->
  X <> Y ->
  wf_env ( E1 ++ Y ~ bind_typ U ++ E2) ->
  typing (E1 ++ Y ~ bind_typ U ++E2) (subst_ee X Y e) T.
Proof with auto.
  intros.
  generalize dependent Y.
  dependent induction H;intros.
  - constructor. auto.
  - simpl. destruct (x == X)...
    { subst. apply typing_var...
      assert (binds X (bind_typ U) (E1 ++ X ~ bind_typ U ++ E2))...
      pose proof binds_unique _ _ _ _ _ H0 H3.
      rewrite H4... apply uniq_from_wf_env... }
    { apply typing_var with (T:=T)... analyze_binds H0. }
  - apply typing_abs with (L:=L \u {{ X }}
      \u dom (E1 ++ Y ~ bind_typ U ++ E2) )...
    intros.
    specialize_x_and_L x L.
    rewrite subst_ee_open_ee_var...
    rewrite_env ((x~bind_typ V ++ E1) ++ Y ~ bind_typ U ++ E2).
    apply H0...
    rewrite_env (x~bind_typ V ++ (E1 ++ Y ~ bind_typ U ++ E2)).
    constructor...
    { apply typing_regular in H. destruct_hypos.
      inv H... rewrite_env (E1 ++ X ~ bind_typ U ++ E2) in H10.
      apply wf_typ_strengthening in H10.
      apply WF_weakening... }
  - apply typing_app with (T11:=T11) (T2:=T2) (T1:=T1)...
    + apply exposure_typ_strengthening in H0.
      apply exposure_weakening_bind2...
    + apply Sub_weakening... apply Sub_typ_strengthening in H2...
  - simpl. apply typing_tabs with (L:=L \u {{ X }}
  \u dom (E1 ++ Y ~ bind_typ U ++ E2) ).
    intros.
    specialize_x_and_L X0 L.
    rewrite subst_ee_open_te_var...
    rewrite_env ((X0~bind_sub V ++ E1) ++ Y ~ bind_typ U ++ E2).
    apply H0...
    rewrite_env (X0~bind_sub V ++ (E1 ++ Y ~ bind_typ U ++ E2)).
    constructor...
    { apply typing_regular in H. destruct_hypos.
      inv H... rewrite_env (E1 ++ X ~ bind_typ U ++ E2) in H10.
      apply wf_typ_strengthening in H10.
      apply WF_weakening... }
  - apply typing_tapp with (T11:=T11) (T2:=T2) (T1:=T1)...
    + apply exposure_typ_strengthening in H0.
      apply exposure_weakening_bind2...
    + apply Sub_weakening... apply Sub_typ_strengthening in H1...
  - apply typing_fold with (T:= T)...
    + apply Sub_weakening... apply Sub_typ_strengthening in H0...
    + apply WF_weakening... apply wf_typ_strengthening in H1...
  - apply typing_unfold with (A':= A')...
    + apply Sub_weakening... apply Sub_typ_strengthening in H0...
Qed.

Lemma uniq_map_app_r:
  forall (A : Type) (E F : list (atom * A)) (f : A -> A),
  uniq (map f F ++ E) -> uniq (F ++ E).
Proof.
  intros.
  apply uniq_app_iff in H. apply uniq_app_iff.
  destruct_hypos. repeat split;auto.
  { apply uniq_map_1 in H. auto. }
  { apply disjoint_map_1 in H1. auto. }
Qed.

Lemma binds_typ_replacing_subst_tb: forall E X Y T x,
  binds x (bind_typ T) E ->
  binds x (bind_typ (subst_tt X Y T)) ((map (subst_tb X Y) E)).
Proof.
  intros. induction E.
  - inv H.
  - destruct H.
    + subst. left. reflexivity.
    + right. apply IHE. auto.
Qed.

Lemma binds_sub_replacing_subst_tb: forall E X Y T x,
  binds x (bind_sub T) E ->
  binds x (bind_sub (subst_tt X Y T)) ((map (subst_tb X Y) E)).
Proof.
  intros. induction E.
  - inv H.
  - destruct H.
    + subst. left. reflexivity.
    + right. apply IHE. auto.
Qed.

Lemma binds_sub_replacing: forall E1 E2 U X Y x T,
    binds  x (bind_sub T) (E1++ X ~ bind_sub U ++E2) ->
    X <> Y -> x <> X ->
    wf_env ( E1 ++ X ~ bind_sub U ++ E2) ->
    binds  x (bind_sub (subst_tt X Y T)) (map (subst_tb X Y) E1 ++ Y ~ bind_sub U ++E2).
Proof with auto.
  intros.
  apply binds_app_iff in H. apply binds_app_iff.
  destruct H.
  - left. apply binds_sub_replacing_subst_tb...
  - right. inv H. { inv H3... exfalso... }
    apply wf_env_cons in H2. inv H2.
    pose proof WF_from_binds_typ _ _ H7 H3.
    apply WF_imply_dom in H4.
    rewrite <- subst_tt_fresh...
Qed.

Lemma exposure_replacing: forall E1 E2 A B U X Y,
    exposure (E1++ X ~ bind_sub U ++E2) A B ->
    wf_env ((E1++ X ~ bind_sub U ++E2) ) ->
    X <> Y ->
    wf_env (map (subst_tb X Y)  E1 ++ Y ~ bind_sub U ++ E2) ->
    exposure (map (subst_tb X Y) E1 ++ Y ~ bind_sub U ++E2) (subst_tt X Y A) (subst_tt X Y B).
Proof with auto.
  intros.
  generalize dependent Y.
  dependent induction H;intros;simpl;try solve [rewrite_alist (map (subst_tb X Y) E1 ++ [(Y, bind_sub U)] ++ E2);constructor;auto;apply WF_replacing;auto]...
  -
    destruct (X0==X)...
    + subst. assert (binds X (bind_sub U) (E1 ++ X ~ bind_sub U ++ E2) )...
      pose proof binds_uniq _ _ _ H1 H H4. subst.
      apply exposure_trans_tvar with (U:=subst_tt X Y U)...
      * rewrite <- subst_tt_fresh...
        apply wf_env_cons in H1. inv H1.
        apply WF_imply_dom in H9...
      * apply IHexposure...
    + apply exposure_trans_tvar with (U:=subst_tt X Y U0)...
      2:{ apply IHexposure... }
      apply binds_sub_replacing...
Qed.

Lemma typing_replacing2: forall E1 E2 e T U X Y,
  typing (E1++ X ~ bind_sub U ++E2) e T ->
  X <> Y ->
  wf_env ( map (subst_tb X Y) E1 ++ Y ~ bind_sub U ++ E2) ->
  typing (map (subst_tb X Y) E1 ++ Y ~ bind_sub U ++E2) (subst_te X Y e) (subst_tt X Y T).
Proof with auto.
  intros.
  generalize dependent Y.
  dependent induction H;intros.
  - constructor. auto.
  - destruct (x == X)...
    { subst. apply typing_var...
      assert (binds X (bind_sub U) (E1 ++ X ~ bind_sub U ++ E2))...
      pose proof binds_unique _ _ _ _ _ H0 H3.
      apply uniq_from_wf_env in H.
      specialize (H4 H). inv H4.
    }
    { apply typing_var... apply binds_app_iff in H0. apply binds_app_iff.
      destruct H0.
      - left. apply binds_typ_replacing_subst_tb...
      - right. destruct H0. { inv H0... }
        apply wf_env_cons in H. inv H.
        pose proof wf_typ_from_binds_typ _ _ H6 H0.
        apply WF_imply_dom in H3.
        rewrite <- subst_tt_fresh...
    }
  - simpl. apply typing_abs with (L:=L \u {{ X }} \u
      dom (map (subst_tb X Y) E1 ++ Y ~ bind_sub U ++ E2)
     )...
    intros.
    specialize_x_and_L x L.
    rewrite subst_te_open_ee_var...
    rewrite_env (map (subst_tb X Y) (x ~ bind_typ V ++ E1) ++ Y ~ bind_sub U ++ E2).
    apply H0...
    rewrite_env (x ~ bind_typ (subst_tt X Y V) ++ (map (subst_tb X Y) E1 ++ Y ~ bind_sub U ++ E2)).
    constructor...
    { apply typing_regular in H. destruct_hypos.
      inv H... rewrite_env (E1 ++ (X ~ bind_sub U) ++ E2) in H10.
      apply WF_replacing... }
  - simpl. apply typing_app with (T11:= subst_tt X Y T11) (T2:=subst_tt X Y T2) (T1:=subst_tt X Y T1)...
    + apply IHtyping1...
    +
      eapply exposure_replacing with (Y:=Y) in H0...
      apply typing_regular in H... destruct_hypos...
    + apply IHtyping2...
    + apply sub_replacing...
  - simpl. apply typing_tabs with (L:=L \u {{ X }}
  \u dom (E1 ++ Y ~ bind_typ U ++ E2) ).
    intros.
    specialize_x_and_L X0 L.
    rewrite subst_te_open_te_var...
    rewrite subst_tt_open_tt_var...
    rewrite_env ( map (subst_tb X Y) (X0~bind_sub V ++ E1) ++ Y ~ bind_sub U ++ E2).
    apply H0...
    rewrite_env (X0~bind_sub (subst_tt X Y V) ++ (map (subst_tb X Y) E1 ++ Y ~ bind_sub U ++ E2)).
    constructor...
    { apply typing_regular in H. destruct_hypos.
      inv H... rewrite_env(E1 ++ (X ~ bind_sub U) ++ E2) in H10.
      apply WF_replacing... }
  - simpl. rewrite subst_tt_open_tt...
    apply typing_tapp with (T11:=subst_tt X Y T11) (T2:=subst_tt X Y T2) (T1:=subst_tt X Y  T1)...
    + apply IHtyping...
    + eapply exposure_replacing with (Y:=Y) in H0...
      apply typing_regular in H... destruct_hypos...
    + apply sub_replacing...
  - simpl. apply typing_fold with (T:= subst_tt X Y T)...
    + apply IHtyping...
    + apply sub_replacing with (Y:=Y) in H0...
      rewrite subst_tt_open_tt in H0...
    + apply WF_replacing with (Y:=Y) in H1...
  - simpl. rewrite subst_tt_open_tt... apply typing_unfold with (A':= subst_tt X Y A')...
    + apply IHtyping...
    + apply sub_replacing with (Y:=Y) in H0...
Qed.

Lemma typing_replacing_var: forall E2 A U X Y T,
    typing (X ~ bind_typ U ++E2) (open_ee A X) T ->
    X \notin fv_ee A \u  {{Y}} ->
    wf_env (Y ~ bind_typ U ++ E2) ->
    typing (Y ~ bind_typ U ++E2) (open_ee A Y) T.
Proof with auto.
  intros.
  rewrite_env (nil ++ Y ~ bind_typ U ++ E2).
  rewrite subst_ee_intro with (x:=X)...
  apply typing_replacing...
Qed.

End Algo.

Hint Constructors Algo.typing.

Lemma sub_inv_arrow: forall E A T1 T2, sub E A (typ_arrow T1 T2) ->
  (exists X:atom, A = X) \/
  (exists S1 S2, A = typ_arrow S1 S2 /\ sub E T1 S1 /\ sub E S2 T2).
Proof.
  intros.
  dependent induction H.
  - left. exists X. auto.
  - right. exists A1, A2. auto.
Qed.

Lemma sub_inv_all: forall E A T1 T2, sub E A (typ_all T1 T2) ->
  (exists X:atom, A = X) \/
  (exists S2 L, A = typ_all T1 S2 /\
   forall X, X \notin L -> sub (X ~ bind_sub T1 ++ E)
    (open_tt S2 X) (open_tt T2 X)).
Proof.
  intros.
  dependent induction H.
  - left. exists X. auto.
  - right. exists T0, L. split; auto.
Qed.

Lemma open_close_reverse_rec: forall T X X0,
  (* (X \notin fv_tt T \u {{X0}}) ->  *)
  (* (X0 \notin fv_tt T) -> *)
  forall k, WFD T k ->
  subst_tt X X0 T = open_tt_rec k X0 (close_tt_rec k X T).
   (* subst_tt X0 X T = close_tt_rec k X (open_tt_rec k (typ_fvar X0) T). *)
Proof with auto.
intros T.
induction T;intros;simpl in *;try solve [f_equal;auto]...
- inv H. destruct (k == n);try lia...
- destruct (a == X)... simpl. destruct (k == k); try lia...
- inv H. rewrite IHT1 with (k:=k)...
  rewrite IHT2 with (k:=k)...
- inv H. rewrite IHT1 with (k:=k)...
  rewrite IHT2 with (k:=S k)...
- inv H. rewrite IHT with (k:=S k)...
- inv H. rewrite IHT with (k:=k)...
Qed.

Lemma open_close_reverse: forall T (X0 X:atom),
  type T ->
  (* (X \notin fv_tt T \u {{ X0 }}) ->  *)
   subst_tt X X0 T = open_tt (close_tt T X) X0.
Proof with auto.
intros. unfold open_tt, close_tt.
rewrite <- open_close_reverse_rec...
apply close_type_wfd...
Qed.

Theorem minimum_typing: forall E e T, typing E e T ->
exists S, Algo.typing E e S /\ sub E S T.
Proof with auto.
  intros.
  induction H.
  -
    exists typ_nat...
  -
    exists T. split... apply Reflexivity...
    eapply wf_typ_from_binds_typ with (x:=x)...
  -
    pick_fresh X. specialize_x_and_L X L.
    destruct H0 as [T2 [? ?]].
    assert (Hwf: wf_env E).
    { apply typing_regular in H.
      destruct_hypos. inv H... }
    exists (typ_arrow V T2). split...
    { apply Algo.typing_abs with (L:=L \u {{X}} \u dom E).
      intros.
      apply Algo.typing_replacing_var with (X:=X)...
      apply typing_regular in H... destruct_hypos.
      inv H...
    }
    { eapply sa_arrow.
      { apply Reflexivity...
        apply typing_regular in H.
        destruct_hypos. inv H... }
      rewrite_env (nil ++ X ~ bind_typ V ++ E) in H1.
      apply Sub_typ_strengthening in H1... }
  -
    destruct IHtyping1 as [Tarr' [? ?]].
    destruct IHtyping2 as [T2' [? ?]].
    pose proof sub_regular H2. destruct_hypos.
    destruct (Algo.exposure_ex H5 H6) as [N1 [? [? ?]]].
    epose proof Algo.exposure_promote H10 H8 H2 I.
    destruct (sub_inv_arrow H11).
    { destruct H12. subst. inv H9.  }
    destruct H12 as [N11 [N12 ?]]. destruct_hypos.
    exists N12. split...
    eapply Algo.typing_app with (T11:=N11) (T1:=Tarr') (T2:=T2')...
    { subst... }
    { eapply sub_transitivity with (Q:=T1)... }
  -
    pick_fresh X. specialize_x_and_L X L.
    destruct H0 as [S ]. destruct_hypos.
    assert (Hwf: wf_env E).
    { apply typing_regular in H.
      destruct_hypos. inv H... }
    exists (typ_all V (close_tt S X)). split...
    { apply Algo.typing_tabs with (L:=L \u {{X}} \u dom E).
      intros.
      rewrite <- open_close_reverse...
      2:{ apply Algo.typing_regular in H0. destruct_hypos...
          apply WF_type in H4... }
      replace (open_te e1 X0) with (subst_te X X0 (open_te e1 X)).
      2:{ rewrite <- subst_te_intro... }
      rewrite_env (map (subst_tb X X0) nil ++ X0 ~ bind_sub V ++ E).
      apply Algo.typing_replacing2...
      { constructor...
        apply sub_regular in H1. destruct_hypos.
        inv H1...
      }
    }
    { eapply sa_all with (L:=L \u {{X}} \u dom E).
      { apply typing_regular in H.
        destruct_hypos. inv H... }
      intros.
      rewrite <- open_close_reverse...
      2:{ apply Algo.typing_regular in H0. destruct_hypos...
          apply WF_type in H4... }
      replace (open_tt T1 X0) with (subst_tt X X0 (open_tt T1 X)).
      2:{ rewrite <- subst_tt_intro... }
      rewrite_env (map (subst_tb X X0) nil ++ X0 ~ bind_sub V ++ E).
      apply sub_replacing...
      { constructor...
        apply sub_regular in H1. destruct_hypos.
        inv H1...
      }
    }
  -
    destruct IHtyping as [Tall' ?]. destruct_hypos.
    pose proof sub_regular H2. destruct_hypos.
    destruct (Algo.exposure_ex H3 H4) as [N1 [? [? ?]]].
    epose proof Algo.exposure_promote H8 H6 H2 I. simpl in H9.
    destruct (sub_inv_all H9).
    { destruct H10. subst. inv H7.  }
    destruct H10 as [N11 [L ]]. destruct_hypos.
    exists (open_tt N11 T). split...
    2:{
      pick_fresh X0.
      rewrite_env (map (subst_tb X0 T) nil ++ E).
      replace (open_tt N11 T) with (subst_tt X0 T (open_tt N11 X0)).
      2:{ rewrite <- subst_tt_intro... }
      replace (open_tt T2 T) with (subst_tt X0 T (open_tt T2 X0)).
      2:{ rewrite <- subst_tt_intro... }
      apply sub_through_subst_tt with (Q:=T1)...
      apply H11...
    }
    eapply Algo.typing_tapp with (T11:=T1) (T1:=Tall')...
    { subst... }
  -
    destruct IHtyping as [S]. destruct_hypos.
    pose proof sub_regular H2. destruct_hypos.
    (* destruct (Algo.exposure_ex H3 H4) as [N ]. destruct_hypos.
    epose proof Algo.exposure_promote H8 H6 H2. *)
    exists (typ_mu A).
    split...
    { eapply Algo.typing_fold with (T:=S)... }
    { apply Reflexivity... }
  -
    destruct IHtyping as [S]. destruct_hypos.

    pose proof sub_regular H1. destruct_hypos.
    (* destruct (Algo.exposure_ex H3 H4) as [N ]. destruct_hypos.
    epose proof Algo.exposure_promote H8 H6 H2. *)
    exists (open_tt T (typ_mu T)).
    split...
    {
      eapply Algo.typing_unfold. apply H0. auto.  }
    { apply Reflexivity...
      apply wf_open_rec... }
  -
    destruct IHtyping as [S']. destruct_hypos.
    exists S'. split...
    apply sub_transitivity with (Q:=S)...
Qed.

Theorem typing_algo_sound: forall E e T, Algo.typing E e T ->
  typing E e T.
Proof with auto.
  intros.
  induction H...
  - apply typing_abs with (L:=L)...
  - eapply typing_app with (T1:=T11).
    + apply typing_sub with (S:=T1)...
      apply Algo.typing_regular in H. destruct_hypos.
      apply Algo.exposure_sound...
    + apply typing_sub with (S:=T2)...
  - apply typing_tabs with (L:=L)...
  - apply typing_tapp with (T1:=T11)...
    + apply typing_sub with (S:=T1)...
      apply Algo.typing_regular in H. destruct_hypos.
      apply Algo.exposure_sound...
  - apply typing_fold...
    + apply typing_sub with (S:=T)...
  - apply typing_unfold...
    + apply typing_sub with (S:=A')...
Qed.


(** * ImpCEvalFun: Evaluation Function for Imp *)

(* $Date: 2012-02-22 04:00:00 -0500 (Wed, 22 Feb 2012) $ *)

(* #################################### *)
(** ** Evaluation Function *)

Require Import Imp.

(** Here's a first try at an evaluation function for commands,
    omitting [WHILE]. *)

Fixpoint ceval_step1 (st : state) (c : com) : state :=
  match c with
    | SKIP =>
        st
    | l ::= a1 =>
        update st l (aeval st a1)
    | c1 ; c2 =>
        let st' := ceval_step1 st c1 in
        ceval_step1 st' c2
    | IFB b THEN c1 ELSE c2 FI =>
        if (beval st b)
          then ceval_step1 st c1
          else ceval_step1 st c2
    | WHILE b1 DO c1 END =>
        st  (* bogus *)
  end.

(** In a traditional functional programming language like ML or
    Haskell we could write the WHILE case as follows:
<<
    | WHILE b1 DO c1 END =>
        if (beval st b1)
          then ceval_step1 st (c1; WHILE b1 DO c1 END)
          else st
>>
    Coq doesn't accept such a definition ([Error: Cannot guess
    decreasing argument of fix]) because the function we want to
    define is not guaranteed to terminate. Indeed, the changed
    [ceval_step1] function applied to the [loop] program above would
    never terminate. Since Coq is not just a functional programming
    language, but also a consistent logic, any potentially
    non-terminating function needs to be rejected. Here is an
    invalid(!) Coq program showing what would go wrong if Coq allowed
    non-terminating recursive functions:
<<
     Fixpoint loop_false (n : nat) : False := loop_false n.
>>
    That is, propositions like [False] would become
    provable (e.g. [loop_false 0] would be a proof of [False]), which
    would be a disaster for Coq's logical consistency.

    Thus, because it doesn't terminate on all inputs, the full version
    of [ceval_step1] cannot be be written in Coq -- at least not
    without one additional trick... *)

(** Second try, using an extra numeric argument as a "step index" to
    ensure that evaluation always terminates. *)

Fixpoint ceval_step2 (st : state) (c : com) (i : nat) : state :=
  match i with
  | O => empty_state
  | S i' =>
    match c with
      | SKIP =>
          st
      | l ::= a1 =>
          update st l (aeval st a1)
      | c1 ; c2 =>
          let st' := ceval_step2 st c1 i' in
          ceval_step2 st' c2 i'
      | IFB b THEN c1 ELSE c2 FI =>
          if (beval st b)
            then ceval_step2 st c1 i'
            else ceval_step2 st c2 i'
      | WHILE b1 DO c1 END =>
          if (beval st b1)
          then let st' := ceval_step2 st c1 i' in
               ceval_step2 st' c i'
          else st
    end
  end.

(** _Note_: It is tempting to think that the index [i] here is
    counting the "number of steps of evaluation."  But if you look
    closely you'll see that this is not the case: for example, in the
    rule for sequencing, the same [i] is passed to both recursive
    calls.  Understanding the exact way that [i] is treated will be
    important in the proof of [ceval__ceval_step], which is given as
    an exercise below. *)

(** Third try, returning an [option state] instead of just a [state]
    so that we can distinguish between normal and abnormal
    termination. *)

Fixpoint ceval_step3 (st : state) (c : com) (i : nat)
                    : option state :=
  match i with
  | O => None
  | S i' =>
    match c with
      | SKIP =>
          Some st
      | l ::= a1 =>
          Some (update st l (aeval st a1))
      | c1 ; c2 =>
          match (ceval_step3 st c1 i') with
          | Some st' => ceval_step3 st' c2 i'
          | None => None
          end
      | IFB b THEN c1 ELSE c2 FI =>
          if (beval st b)
            then ceval_step3 st c1 i'
            else ceval_step3 st c2 i'
      | WHILE b1 DO c1 END =>
          if (beval st b1)
          then match (ceval_step3 st c1 i') with
               | Some st' => ceval_step3 st' c i'
               | None => None
               end
          else Some st
    end
  end.

(** We can improve the readability of this definition by introducing a
    bit of auxiliary notation to hide the "plumbing" involved in
    repeatedly matching against optional states. *)

Notation "'LETOPT' x <== e1 'IN' e2"
   := (match e1 with
         | Some x => e2
         | None => None
       end)
   (right associativity, at level 60).

Fixpoint ceval_step (st : state) (c : com) (i : nat)
                    : option state :=
  match i with
  | O => None
  | S i' =>
    match c with
      | SKIP =>
          Some st
      | l ::= a1 =>
          Some (update st l (aeval st a1))
      | c1 ; c2 =>
          LETOPT st' <== ceval_step st c1 i' IN
          ceval_step st' c2 i'
      | IFB b THEN c1 ELSE c2 FI =>
          if (beval st b)
            then ceval_step st c1 i'
            else ceval_step st c2 i'
      | WHILE b1 DO c1 END =>
          if (beval st b1)
          then LETOPT st' <== ceval_step st c1 i' IN
               ceval_step st' c i'
          else Some st
    end
  end.

Definition test_ceval (st:state) (c:com) :=
  match ceval_step st c 500 with
  | None    => None
  | Some st => Some (st X, st Y, st Z)
  end.

(* Eval compute in
     (test_ceval empty_state
         (X ::= ANum 2;
          IFB BLe (AId X) (ANum 1)
            THEN Y ::= ANum 3
            ELSE Z ::= ANum 4
          FI)).
   ====>
      Some (2, 0, 4)   *)

(** **** Exercise: 2 stars, recommended (pup_to_n) *)
(** Write an Imp program that sums the numbers from [1] to
   [X] (inclusive: [1 + 2 + ... + X]) in the variable [Y].  Make sure
   your solution satisfies the test that follows. *)

Definition pup_to_n : com :=
  (* FILL IN HERE *) admit.

(*
Example pup_to_n_1 :
  test_ceval (update empty_state X 5) pup_to_n
  = Some (0, 15, 0).
Proof. reflexivity. Qed.
*)
(** [] *)

(** **** Exercise: 2 stars, optional (peven) *)
(** Write a [While] program that sets [Z] to [0] if [X] is even and
    sets [Z] to [1] otherwise.  Use [ceval_test] to test your
    program. *)

(* FILL IN HERE *)
(** [] *)

(* ################################################################ *)
(** ** Equivalence of Relational and Step-Indexed Evaluation *)

(** As with arithmetic and boolean expressions, we'd hope that
    the two alternative definitions of evaluation actually boil down
    to the same thing.  This section shows that this is the case.
    Make sure you understand the statements of the theorems and can
    follow the structure of the proofs. *)

Theorem ceval_step__ceval: forall c st st',
      (exists i, ceval_step st c i = Some st') ->
      c / st || st'.
Proof.
  intros c st st' H.
  inversion H as [i E].
  clear H.
  generalize dependent st'.
  generalize dependent st.
  generalize dependent c.
  induction i as [| i' ].

  Case "i = 0 -- contradictory".
    intros c st st' H. inversion H.

  Case "i = S i'".
    intros c st st' H.
    com_cases (destruct c) SCase;
           simpl in H; inversion H; subst; clear H.
      SCase "SKIP". apply E_Skip.
      SCase "::=". apply E_Ass. reflexivity.

      SCase ";".
        remember (ceval_step st c1 i') as r1. destruct r1.
        SSCase "Evaluation of r1 terminates normally".
          apply E_Seq with s.
            apply IHi'. rewrite Heqr1. reflexivity.
            apply IHi'. simpl in H1. assumption.
        SSCase "Otherwise -- contradiction".
          inversion H1.

      SCase "IFB".
        remember (beval st b) as r. destruct r.
        SSCase "r = true".
          apply E_IfTrue. rewrite Heqr. reflexivity.
          apply IHi'. assumption.
        SSCase "r = false".
          apply E_IfFalse. rewrite Heqr. reflexivity.
          apply IHi'. assumption.

      SCase "WHILE". remember (beval st b) as r. destruct r.
        SSCase "r = true".
          remember (ceval_step st c i') as r1. destruct r1.
          SSSCase "r1 = Some s".
            apply E_WhileLoop with s. rewrite Heqr. reflexivity.
            apply IHi'. rewrite Heqr1. reflexivity.
            apply IHi'. simpl in H1. assumption.
          SSSCase "r1 = None".
            inversion H1.
        SSCase "r = false".
          inversion H1.
          apply E_WhileEnd.
          rewrite Heqr. subst. reflexivity.  Qed.

(** **** Exercise: 4 stars (ceval_step__ceval_inf) *)
(** Write an informal proof of [ceval_step__ceval], following the
    usual template.  (The template for case analysis on an inductively
    defined value should look the same as for induction, except that
    there is no induction hypothesis.)  Make your proof communicate
    the main ideas to a human reader; do not simply transcribe the
    steps of the formal proof.

(* FILL IN HERE *)
[]
*)

Theorem ceval_step_more: forall i1 i2 st st' c,
  i1 <= i2 ->
  ceval_step st c i1 = Some st' ->
  ceval_step st c i2 = Some st'.
Proof.
induction i1 as [|i1']; intros i2 st st' c Hle Hceval.
  Case "i1 = 0".
    simpl in Hceval. inversion Hceval.
  Case "i1 = S i1'".
    destruct i2 as [|i2']. inversion Hle.
    assert (Hle': i1' <= i2') by omega.
    com_cases (destruct c) SCase.
    SCase "SKIP".
      simpl in Hceval. inversion Hceval.
      reflexivity.
    SCase "::=".
      simpl in Hceval. inversion Hceval.
      reflexivity.
    SCase ";".
      simpl in Hceval. simpl.
      remember (ceval_step st c1 i1') as st1'o.
      destruct st1'o.
      SSCase "st1'o = Some".
        symmetry in Heqst1'o.
        apply (IHi1' i2') in Heqst1'o; try assumption.
        rewrite Heqst1'o. simpl. simpl in Hceval.
        apply (IHi1' i2') in Hceval; try assumption.
      SSCase "st1'o = None".
        inversion Hceval.

    SCase "IFB".
      simpl in Hceval. simpl.
      remember (beval st b) as bval.
      destruct bval; apply (IHi1' i2') in Hceval; assumption.

    SCase "WHILE".
      simpl in Hceval. simpl.
      destruct (beval st b); try assumption.
      remember (ceval_step st c i1') as st1'o.
      destruct st1'o.
      SSCase "st1'o = Some".
        symmetry in Heqst1'o.
        apply (IHi1' i2') in Heqst1'o; try assumption.
        rewrite -> Heqst1'o. simpl. simpl in Hceval.
        apply (IHi1' i2') in Hceval; try assumption.
      SSCase "i1'o = None".
        simpl in Hceval. inversion Hceval.  Qed.

(** **** Exercise: 3 stars, recommended (ceval__ceval_step) *)
(** Finish the following proof.  You'll need [ceval_step_more] in a
    few places, as well as some basic facts about [<=] and [plus]. *)

Theorem ceval__ceval_step: forall c st st',
      c / st || st' ->
      exists i, ceval_step st c i = Some st'.
Proof.
  intros c st st' Hce.
  ceval_cases (induction Hce) Case.
  (* FILL IN HERE *) Admitted.
(** [] *)

Theorem ceval_and_ceval_step_coincide: forall c st st',
      c / st || st'
  <-> exists i, ceval_step st c i = Some st'.
Proof.
  intros c st st'.
  split. apply ceval__ceval_step. apply ceval_step__ceval.
Qed.

(* ####################################################### *)
(** ** Determinism of Evaluation (Simpler Proof) *)

(** Here's a slicker proof showing that the evaluation relation is
    deterministic, using the fact that the relational and step-indexed
    definition of evaluation are the same. *)

Theorem ceval_deterministic' : forall c st st1 st2,
     c / st || st1  ->
     c / st || st2 ->
     st1 = st2.
Proof.
  intros c st st1 st2 He1 He2.
  apply ceval__ceval_step in He1.
  apply ceval__ceval_step in He2.
  inversion He1 as [i1 E1].
  inversion He2 as [i2 E2].
  apply ceval_step_more with (i2 := i1 + i2) in E1.
  apply ceval_step_more with (i2 := i1 + i2) in E2.
  rewrite E1 in E2. inversion E2. reflexivity.
  omega. omega.  Qed.


(*
abhishek@brixpro:~/parametricity/reflective-paramcoq/test-suite/iso$ ./coqid.sh IWTS
*)

Require Import SquiggleEq.terms.

Require Import ReflParam.common.
Require Import ReflParam.templateCoqMisc.
Require Import String.
Require Import List.
Require Import Template.Ast.
Require Import SquiggleEq.terms.
Require Import ReflParam.paramDirect ReflParam.indType.
Require Import SquiggleEq.substitution.
Require Import ReflParam.PiTypeR.
Import ListNotations.
Open Scope string_scope.

Require Import ReflParam.PIWNew.

Require Import Template.Template.

Inductive IWT (I A : Set) (B : A -> Set) (AI : A -> I)
(BI : forall (a : A), B a -> I) : forall (i:I), Set :=
    iwt : forall (a : A) (lim: forall b : B a, IWT I A B AI BI (BI a b)),
     IWT I A B AI BI (AI a).

Run TemplateProgram (genParamIndAll [] "Top.IWTS.IWT").

Print Assumptions Top_IWTS_IWT_pmtcty_RR0tot12.
Print Assumptions Top_IWTS_IWT_pmtcty_RR0one12.

Run TemplateProgram (mkIndEnv "indTransEnv" ["Top.IWTS.IWT"]).
Require Import ReflParam.Trecord.
Print Top_IWTS_IWT_pmtcty_RR0_constr_0_tot.
Run TemplateProgram (genWrappers indTransEnv).

(* Set version *)
Definition IWTind :=
fun (I A : Set) (B : A -> Set) (AI : A -> I) (BI : forall a : A, B a -> I)
  (P : forall i : I, IWT I A B AI BI i -> Set)
  (f : forall (a : A) (lim : forall b : B a, IWT I A B AI BI (BI a b)),
       (forall b : B a, P (BI a b) (lim b)) -> P (AI a) (iwt I A B AI BI a lim)) =>
fix F (i : I) (i0 : IWT I A B AI BI i) {struct i0} : P i i0 :=
  match i0 as i2 in (IWT _ _ _ _ _ i1) return (P i1 i2) with
  | iwt _ _ _ _ _ a lim => f a lim (fun b : B a => F (BI a b) (lim b))
  end.

Run TemplateProgram (genParam indTransEnv IsoRel true "Top.IWTS.IWTind").

Print Top_IWTS_IWTind_pmtcty_RR.

Section Comp.
(*
Now we want to verify that the IsoRel translation (before unused-var analysis) preserves
reductions.
*)

Variables (I A : Set) (B : A -> Set) (AI : A -> I)  (BI : forall (a : A), B a -> I)
  (P : forall i : I, IWT I A B AI BI i -> Set)
  (f : forall (a : A) (lim : forall b : B a, IWT I A B AI BI (BI a b)),
       (forall b : B a, P (BI a b) (lim b)) -> P (AI a) (iwt I A B AI BI a lim))
  (a:A)
  (lim: forall b : B a, IWT I A B AI BI (BI a b)).

Definition LHS := IWTind _ _ _ _ _ _ f _ (iwt _ _ _ _ _ a lim).
Definition RHS := f a lim (fun b : B a => IWTind _ _ _ _ _ _ f (BI a b) (lim b)).

(** To show that the equality holds definitionally, the proof must be reflexivity. *)
Lemma iotaRed  :
  LHS  = RHS.
Proof using. reflexivity. Qed.

(** Below, we check that this iota reduction is preserved after IsoRel translation
*)

End Comp.

(** perform the IsoRel translation of LHS and RHS to respectively generate
Top_IWTS_LHS_pmtcty_RR and Top_IWTS_RHS_pmtcty_RR
*)
Run TemplateProgram (genParam indTransEnv IsoRel true "Top.IWTS.LHS").
Run TemplateProgram (genParam indTransEnv IsoRel true "Top.IWTS.RHS").

Check (eq_refl: Top_IWTS_LHS_pmtcty_RR = Top_IWTS_RHS_pmtcty_RR).
Lemma IsoRelPreservesIotaDefinitionallyDirect:
Top_IWTS_LHS_pmtcty_RR = Top_IWTS_RHS_pmtcty_RR.
Proof.
reflexivity.
Qed.


(* Copyright (c) 2014, Robert Dockins *)

Require Import String.
Require Import List.

Require Import basics.
Require Import preord.
Require Import categories.
Require Import sets.
Require Import finsets.
Require Import esets.
Require Import effective.
Require Import directed.
Require Import plotkin.
Require Import joinable.
Require Import approx_rels.
Require Import cpo.
Require Import profinite.
Require Import discrete.

(** * Soundness, adequacy and strong normalization for simply-typed SKI with booleans.

    As a demonstration of the system in action, here is a proof
    of soundness and adequacy for a simply-typed SKI calculus.
    The adequacy proof goes via a standard logical-relations argument.
    As a corollary of the main logical-relations lemma, we achieve
    strong normalization for the calculus.
  *)

(**  We have arrow types and a single base type of booleans. *)
Inductive ty :=
  | ty_bool
  | ty_arrow : ty -> ty -> ty.

Delimit Scope ty_scope with ty.
Notation "2" := ty_bool : ty_scope.
Notation "x ⇒ y" := (ty_arrow (x)%ty (y)%ty) : ty_scope.
Bind Scope ty_scope with ty.

Delimit Scope ski_scope with ski.
Open Scope ski_scope.

(**  Terms are boolean constants, the standard combinators S, K and I,
     and an IF/THEN/ELSE combinator; and applications.
  *)
Inductive term : ty -> Type :=

  | tbool : forall b:bool,
                term 2

  | tapp : forall σ₁ σ₂,
                term (σ₁ ⇒ σ₂) ->
                term σ₁ ->
                term σ₂

  | tI : forall σ,
                term (σ ⇒ σ)

  | tK : forall σ₁ σ₂,
                term (σ₁ ⇒ σ₂ ⇒ σ₁)

  | tS : forall σ₁ σ₂ σ₃,
                term ((σ₁ ⇒ σ₂ ⇒ σ₃) ⇒ (σ₁ ⇒ σ₂) ⇒ σ₁ ⇒ σ₃)

  | tIF : forall σ,
                term (2 ⇒ σ ⇒ σ ⇒ σ).

Arguments tapp [_ _] _ _.
Notation "x • y" := (tapp x y)
  (at level 52, left associativity, format "x • y") : ski_scope.

(**  The operational semantics is given in a big-step style, with the specification
     of redexes split out into a separate relation.
  *)

Inductive redex : forall σ₁ σ₂, term (σ₁ ⇒ σ₂) -> term σ₁ -> term σ₂ -> Prop :=
  | redex_I : forall σ x,
                  redex _ _ (tI σ) x x
  | redex_K : forall σ₁ σ₂ x y,
                  redex σ₂ σ₁ (tK σ₁ σ₂ • y) x y
  | redex_S : forall σ₁ σ₂ σ₃ f g x,
                  redex _ _ (tS σ₁ σ₂ σ₃ • f • g)
                            x
                            ((f•x)•(g•x))
  | redex_IFtrue : forall σ th el,
                  redex _ _ (tIF σ • tbool true • th) el th
  | redex_IFfalse : forall σ th el,
                  redex _ _ (tIF σ • tbool false • th) el el.

Inductive eval : forall τ, term τ -> term τ -> Prop :=
  | ebool : forall b, eval 2 (tbool b) (tbool b)
  | eI   : forall σ, eval _ (tI σ) (tI _)
  | eK   : forall σ₁ σ₂, eval _ (tK σ₁ σ₂) (tK _ _)
  | eS   : forall σ₁ σ₂ σ₃, eval _ (tS σ₁ σ₂ σ₃) (tS _ _ _)
  | eIF  : forall σ , eval _ (tIF σ) (tIF σ)
  | eapp1 : forall σ₁ σ₂ m₁ m₂ n₁ n₂ r z,
             eval (σ₁ ⇒ σ₂) m₁ n₁ ->
             eval σ₁ m₂ n₂ ->
             redex σ₁ σ₂ n₁ n₂ r ->
             eval σ₂ r z ->
             eval σ₂ (m₁ • m₂) z
  | eapp2 : forall σ₁ σ₂ m₁ m₂ n₁ n₂,
             eval (σ₁ ⇒ σ₂) m₁ n₁ ->
             eval σ₁ m₂ n₂ ->
             ~(exists r, redex σ₁ σ₂ n₁ n₂ r) ->
             eval σ₂ (m₁ • m₂) (n₁ • n₂).

(**  Syntactic types have decicable equality, which
     implies injectivity for dependent pairs with
     (syntactic) types as the type being depended upon.
  *)
Lemma inj_pair2_ty : forall (F:ty -> Type) τ x y,
  existT F τ x = existT F τ y -> x = y.
Proof.
  intros.
  apply Eqdep_dec.inj_pair2_eq_dec in H. auto.
  decide equality.
Qed.

Ltac inj_ty :=
  repeat match goal with
           [ H : existT _ _ _ = existT _ _ _ |- _ ] =>
             apply inj_pair2_ty in H
           end.

Ltac inv H :=
  inversion H; subst; inj_ty; repeat subst.

(** Values are terms that evaluate to themselves.
  *)
Definition value σ (t:term σ) := eval _ t t.
Arguments value [σ] t.

(**  Here are some basic techincal results
     about the operational semantics.
  *)
Lemma eval_value τ x y :
  eval τ x y -> value y.
Proof.
  intro H. induction H.
  apply ebool.
  apply eI.
  apply eK.
  apply eS.
  apply eIF.
  auto.
  apply eapp2; auto.
Qed.

Lemma redex_eq τ₁ τ₂ x y z1 z2 :
  redex τ₁ τ₂ x y z1 ->
  redex τ₁ τ₂ x y z2 ->
  z1 = z2.
Proof.
  intros; inv H; inv H; inv H0; auto.
Qed.

Lemma eval_eq τ x y1 y2 :
  eval τ x y1 -> eval τ x y2 -> y1 = y2.
Proof.
  intro H. revert y2.
  induction H.

  intros. inv H. auto.
  intros. inv H. auto.
  intros. inv H. auto.
  intros. inv H. auto.
  intros. inv H. auto.

  intros. inv H3.
  apply IHeval1 in H9.
  apply IHeval2 in H10.
  subst n₁0 n₂0.
  assert (r = r0).
  eapply redex_eq; eauto.
  subst r0.
  apply IHeval3; auto.
  apply IHeval1 in H9.
  apply IHeval2 in H10.
  subst n₁0 n₂0.
  elim H11; eauto.

  intros. inv H2.
  apply IHeval1 in H8.
  apply IHeval2 in H9.
  subst n₁0 n₂0.
  elim H1. eauto.
  f_equal; auto.
Qed.

Lemma eval_trans τ x y z :
  eval τ x y -> eval τ y z -> eval τ x z.
Proof.
  intros.
  replace z with y; auto.
  eapply eval_eq with y; auto.
  eapply eval_value; eauto.
Qed.

Lemma eval_app_congruence σ₁ σ₂ : forall x x' y y' z,
  (forall q, eval _ x q -> eval _ x' q) ->
  (forall q, eval _ y q -> eval _ y' q) ->
  eval _ (@tapp σ₁ σ₂ x y) z ->
  eval _ (@tapp σ₁ σ₂ x' y') z.
Proof.
  intros.
  inv H1.
  apply H in H7.
  apply H0 in H8.
  eapply eapp1; eauto.
  apply eapp2; auto.
Qed.

(**  Types are interpreted as unpointed domains, using the discrete domain
     of booleans and the exponential in PLT.
  *)
Fixpoint tydom (τ:ty) : PLT :=
  match τ with
  | 2%ty => disc finbool
  | (τ₁ ⇒ τ₂)%ty => tydom τ₁ ⇒ tydom τ₂
  end.

(**  The denotation of terms is given by a simple fixpoint on term structure.
     The denotation of each combinator is a straightforward interpretation of the
     usual lambda term into the operations of a cartesian closed category.
  *)

Fixpoint denote (τ:ty) (m:term τ) : 1 → tydom τ :=
  match m in term τ return 1 → tydom τ with
  | tbool b => disc_elem b

  | tapp m₁ m₂ => apply ∘ 〈〚m₁〛, 〚m₂〛〉

  | tI σ => Λ(π₂)

  | tK σ₁ σ₂ => Λ(Λ(π₂ ∘ π₁))

  | tS σ₁ σ₂ σ₃ => Λ(Λ(Λ(
                    apply ∘ 〈 apply ∘ 〈 π₂ ∘ π₁ ∘ π₁, π₂ 〉
                            , apply ∘ 〈 π₂ ∘ π₁, π₂ 〉
                            〉
                   )))

  | tIF σ => Λ (disc_cases (fun b:bool =>
                 if b then Λ(Λ(π₂ ∘ π₁))
                      else Λ(Λ(π₂))
             ))
  end

 where "〚 m 〛" := (denote _ m) : ski_scope.

(**  Every redex preserves the meaning of terms. *)
Lemma redex_soundness : forall σ₁ σ₂ x y z,
  redex σ₁ σ₂ x y z ->
  apply ∘ 〈〚x〛,〚y〛〉 ≈ 〚z〛.
Proof.
  intros. case H; simpl.
  intros.

  rewrite PLT.curry_apply2.
  rewrite PLT.pair_commute2. auto.
  intros.
  rewrite PLT.curry_apply2.
  rewrite PLT.curry_apply3.
  rewrite <- (cat_assoc (PLT.PLT false) _ _ _ _ PLT.pi2).
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute2.
  auto.
  intros.
  rewrite PLT.curry_apply2.
  rewrite PLT.curry_apply3.
  rewrite PLT.curry_apply3.
  rewrite <- (cat_assoc (PLT.PLT false)).
  apply cat_respects. auto.
  rewrite (PLT.pair_compose_commute false).
  apply PLT.pair_eq.
  rewrite <- (cat_assoc (PLT.PLT false)).
  apply cat_respects. auto.
  rewrite (PLT.pair_compose_commute false).
  apply PLT.pair_eq.
  repeat rewrite <- (cat_assoc (PLT.PLT false)).
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute2. auto.
  rewrite PLT.pair_commute2. auto.
  rewrite <- (cat_assoc (PLT.PLT false)).
  apply cat_respects. auto.
  rewrite (PLT.pair_compose_commute false).
  apply PLT.pair_eq.
  repeat rewrite <- (cat_assoc (PLT.PLT false)).
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute2. auto.
  rewrite PLT.pair_commute2. auto.

  intros.
  rewrite PLT.curry_apply2.
  rewrite disc_cases_elem.
  rewrite PLT.curry_apply3.
  rewrite PLT.curry_apply3.
  rewrite <- (cat_assoc (PLT.PLT false)).
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute2.
  auto.

  intros.
  rewrite PLT.curry_apply2.
  rewrite disc_cases_elem.
  rewrite PLT.curry_apply3.
  rewrite PLT.curry_apply3.
  rewrite PLT.pair_commute2.
  auto.
Qed.

(**  Evaluation preserves the denotation of terms. *)
Theorem soundness : forall τ (m z:term τ),
  eval τ m z -> 〚m〛 ≈ 〚z〛.
Proof.
  intros. induction H; simpl; auto.
  rewrite IHeval1.
  rewrite IHeval2.
  rewrite <- IHeval3.
  apply redex_soundness. auto.
  rewrite IHeval1.
  rewrite IHeval2.
  auto.
Qed.

(** * The logical relations lemma

    Now we define the logical relation.  It is defined by induction
    on the structure of types, in a standard way.
  *)
Fixpoint LR (τ:ty) : term τ -> (1 → tydom τ) -> Prop :=
  match τ as τ' return term τ' -> (1 → tydom τ') -> Prop
  with
  | ty_bool => fun m h =>
        exists b:bool, m = tbool b /\ h ≈ disc_elem b
  | ty_arrow σ₁ σ₂ => fun m h =>
        forall n h',
          LR σ₁ n h' -> eval σ₁ n n ->
          exists z,
            eval _ (m•n) z /\
            LR σ₂ z (apply ∘ 〈h, h'〉)
  end.

Lemma LR_equiv τ : forall m h h',
  h ≈ h' -> LR τ m h -> LR τ m h'.
Proof.
  induction τ; simpl. intros.
  destruct H0 as [b [??]]. exists b; split; auto.
  rewrite <- H; auto.
  simpl; intros.
  destruct (H0 n h'0 H1 H2) as [z [??]].
  exists z; split; auto.
  revert H4. apply IHτ2.
  apply cat_respects; auto.
  apply PLT.pair_eq; auto.
Qed.

(**  Now we need a host of auxilary definitions to state
     the main lemmas regarding the logical relation.  These
     definitions allow us to apply an arbitrary number of
     arguments to a syntactic term and to the denotation of terms.
  *)
Fixpoint lrtys (ts:list ty) (z:ty) : ty :=
  match ts with
  | nil => z
  | t::ts' => (t ⇒ (lrtys ts' z))%ty
  end.

Fixpoint lrsyn (ts:list ty) : Type :=
  match ts with
  | nil => unit
  | t::ts' => prod (lrsyn ts') (term t)
  end.

Fixpoint lrsem (ts:list ty) : Type :=
  match ts with
  | nil => unit
  | t::ts' => prod (lrsem ts') (1 → tydom t)
  end.

Fixpoint lrhyps (ls:list ty) : lrsyn ls -> lrsem ls -> Prop :=
  match ls with
  | nil => fun _ _ => True
  | t::ts => fun xs ys =>
      eval _ (snd xs) (snd xs) /\
      LR t (snd xs) (snd ys) /\
      lrhyps ts (fst xs) (fst ys)
  end.

Fixpoint lrapp (ls:list ty) z : lrsyn ls -> term (lrtys ls z) -> term z :=
  match ls as ls' return lrsyn ls' -> term (lrtys ls' z) -> term z with
  | nil => fun _ m => m
  | t::ts => fun xs m => lrapp ts _ (fst xs) (m • (snd xs))
  end.

Fixpoint lrsemapp (ls:list ty) z :
  lrsem ls -> (1 → tydom (lrtys ls z)) -> (1 → tydom z) :=
  match ls as ls' return
    lrsem ls' -> (1 → tydom (lrtys ls' z)) -> (1 → tydom z)
  with
  | nil => fun _ h => h
  | t::ts => fun ys h => lrsemapp ts _ (fst ys) (apply ∘ 〈h, snd ys〉)
  end.

Lemma eval_lrapp_congruence ls : forall xs τ m m' z,
  (forall q, eval _ m q -> eval _ m' q) ->
  eval τ (lrapp ls τ xs m) z ->
  eval τ (lrapp ls τ xs m') z.
Proof.
  induction ls; simpl; intros.
  apply H. auto.
  fold lrtys in *.

  revert H0. apply IHls.
  intros.
  inv H0.
  apply H in H6.
  eapply eapp1; eauto.
  eapply eapp2; eauto.
Qed.

Lemma lrsemapp_equiv ls : forall τ ys h h',
  h ≈ h' -> lrsemapp ls τ ys h ≈ lrsemapp ls τ ys h'.
Proof.
  induction ls; simpl; intros; auto.
  apply IHls.
  apply cat_respects; auto.
  apply PLT.pair_eq; auto.
Qed.

(**  This fact is important in the base cases of the fundamental lemma; it allows
     unwind a stack of applications.
  *)
Lemma LR_under_apply ls :
   forall (τ : ty) (m z0 : term (lrtys ls τ)) (xs : lrsyn ls)
     (ys : lrsem ls) (h : 1 → (tydom (lrtys ls τ))),
   eval (lrtys ls τ) m z0 ->
   lrhyps ls xs ys ->
   LR (lrtys ls τ) z0 h ->
   exists z : term τ,
     eval τ (lrapp ls τ xs m) z /\ LR τ z (lrsemapp ls τ ys h).
Proof.
  induction ls; simpl; intros.
  exists z0. split; auto.
  destruct xs as [xs x].
  destruct ys as [ys y]. simpl in *.
  destruct H0 as [?[??]].
  destruct (H1 x y) as [z1 [??]]; auto.

  generalize (IHls τ (tapp z0 x) z1 xs ys (apply ∘ 〈h,y〉)
     H4 H3 H5).
  intros [q [??]].
  exists q; split; auto.
  revert H6.
  apply eval_lrapp_congruence. intro.
  apply eval_app_congruence; auto.
  fold lrtys. intros.
  apply eval_trans with z0; auto.
Qed.

(**  Now we prove that each of the base combinators stands
     in the logical relation with their denotations.
  *)
Lemma LR_I σ : LR _ (tI σ) 〚tI σ〛.
Proof.
  simpl. intros.
  exists n. split.
  eapply eapp1.
  apply eI. apply H0.
  apply redex_I. auto.
  revert H. apply LR_equiv. rewrite PLT.curry_apply2.
  rewrite PLT.pair_commute2; auto.
Qed.

Lemma LR_K σ₁ σ₂ : LR _ (tK σ₁ σ₂) 〚tK σ₁ σ₂〛.
Proof.
  simpl. intros.

  exists (tapp (tK _ _) n). split.
  apply eapp2. apply eK. auto.
  intros [? Hr]. inv Hr.
  intros.
  exists n. split.
  eapply eapp1.
  apply eapp2. apply eK. eauto.
  intros [? Hr]. inv Hr. eauto.
  apply redex_K. auto.
  revert H.
  apply LR_equiv.
  rewrite PLT.curry_apply2.
  rewrite PLT.curry_apply3.
  rewrite <- (cat_assoc PLT).
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute2.
  auto.
Qed.

Lemma LR_S σ₁ σ₂ σ₃ : LR _ (tS σ₁ σ₂ σ₃) 〚tS σ₁ σ₂ σ₃〛.
Proof.
  simpl; intros.
  exists (tapp (tS _ _ _) n). split.
  apply eapp2. apply eS. auto.
  intros [? Hr]; inversion Hr.
  intros.
  exists (tapp (tapp (tS _ _ _) n) n0). split.
  apply eapp2. apply eapp2. apply eS. auto.
  intros [? Hr]; inversion Hr. auto.
  intros [? Hr]; inversion Hr. intros.

  assert (
    (PLT.app
        ∘ PLT.pair
            (PLT.app
             ∘ PLT.pair
                 (PLT.app
                  ∘ PLT.pair
                      (PLT.curry
                         (PLT.curry
                            (PLT.curry
                               (PLT.app
                                ∘ PLT.pair
                                    (PLT.app
                                     ∘ PLT.pair (PLT.pi2 ∘ PLT.pi1 ∘ PLT.pi1)
                                         PLT.pi2)
                                    (PLT.app
                                     ∘ PLT.pair (PLT.pi2 ∘ PLT.pi1) PLT.pi2)))))
                      h') h'0) h'1)
     ≈
     PLT.app ∘ PLT.pair
       (PLT.app ∘ PLT.pair h' h'1)
       (PLT.app ∘ PLT.pair h'0 h'1)).
  clear.
  rewrite PLT.curry_apply2.
  rewrite PLT.curry_apply3.
  rewrite PLT.curry_apply3.
  rewrite <- (cat_assoc PLT).
  apply cat_respects. auto.
  rewrite <- (cat_assoc PLT).
  rewrite (PLT.pair_compose_commute false).
  apply PLT.pair_eq.
  rewrite <- (cat_assoc PLT).
  rewrite (PLT.pair_compose_commute false).
  apply cat_respects. auto.
  apply PLT.pair_eq.
  rewrite <- (cat_assoc PLT).
  rewrite <- (cat_assoc PLT).
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute2.
  auto.
  rewrite PLT.pair_commute2.
  auto.
  rewrite <- (cat_assoc PLT).
  apply cat_respects; auto.
  rewrite (PLT.pair_compose_commute false).
  apply PLT.pair_eq; auto.
  rewrite <- (cat_assoc PLT).
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute2.
  auto.
  rewrite PLT.pair_commute2.
  auto.

  destruct (H1 n1 h'1) as [z0 [??]]; auto; clear H1.
  destruct (H n1 h'1) as [z1 [??]]; auto; clear H.
  destruct (H8 z0 (PLT.app ∘ PLT.pair h'0 h'1)) as [z2 [??]]; auto; clear H8.
  eapply eval_value; eauto.
  exists z2. split.
  eapply eapp1.
  apply eapp2. apply eapp2. apply eS. eauto.
  intros [? Hr]; inv Hr. eauto.
  intros [? Hr]; inv Hr. eauto.
  apply redex_S.
  revert H.
  apply eval_app_congruence.
  intros. apply eval_trans with z1; auto.
  intros. apply eval_trans with z0; auto.
  revert H9. apply LR_equiv. auto.
Qed.

Lemma LR_IF σ : LR _ (tIF σ) 〚tIF σ〛.
Proof.
  simpl; intros.
  destruct H as [b [??]]. subst n.
  exists (tapp (tIF σ) (tbool b)).
  split. apply eapp2. apply eIF. apply ebool.
  intros [r Hr]; inversion Hr.
  intros.
  exists (tapp (tapp (tIF σ) (tbool b)) n).
  split.
  apply eapp2. apply eapp2.
  apply eIF. apply ebool.
  intros [r Hr]; inversion Hr. eauto.
  intros [r Hr]; inversion Hr. eauto.
  intros.
  destruct b.
  exists n. split.
  eapply eapp1.
  apply eapp2. apply eapp2. apply eIF. apply ebool.
  intros [r Hr]; inversion Hr. eauto.
  intros [r Hr]; inversion Hr. eauto.
  econstructor. auto.
  revert H. apply LR_equiv.
  rewrite PLT.curry_apply2. rewrite H1.
  rewrite disc_cases_elem.
  rewrite PLT.curry_apply3.
  rewrite PLT.curry_apply3.
  repeat rewrite <- (cat_assoc PLT).
  rewrite PLT.pair_commute1.
  rewrite PLT.pair_commute2.
  auto.

  exists n0. split.
  eapply eapp1.
  apply eapp2. apply eapp2. apply eIF. apply ebool.
  intros [r Hr]; inversion Hr. eauto.
  intros [r Hr]; inversion Hr. eauto.
  econstructor. auto.
  revert H3. apply LR_equiv.
  rewrite PLT.curry_apply2. rewrite H1.
  rewrite disc_cases_elem.
  rewrite PLT.curry_apply3.
  rewrite PLT.curry_apply3.
  rewrite PLT.pair_commute2.
  auto.
Qed.

(**  The fundamental lemma states that every term stands in the logical relation
     with its denotation when applied related term/denotation pairs.
  *)
Lemma fundamental_lemma : forall σ (n:term σ) ls τ m xs ys
  (Hσ : σ = lrtys ls τ),
  eq_rect σ term n (lrtys ls τ) Hσ = m ->
  lrhyps ls xs ys ->
  exists z,
    eval _ (lrapp ls τ xs m) z /\
    LR τ z (lrsemapp ls τ ys 〚m〛).
Proof.
  induction n; intros.

  (* bool case *)
  destruct ls; simpl in *. subst τ.
  simpl in H. subst m.
  exists (tbool b).
  split. apply ebool.
  simpl.
  exists b. split; auto.
  inv Hσ.

  (* application case *)
  subst σ₂. simpl in H. subst m.

  destruct (IHn2 nil σ₁ n2 tt tt (Logic.refl_equal _) (Logic.refl_equal _) I)
    as [q2[??]]; auto.
  destruct (IHn1 (σ₁::ls) _ n1 (xs, q2) (ys, denote σ₁ q2)
    (Logic.refl_equal _) (Logic.refl_equal _)) as [q1 [??]].
  simpl; intuition. eapply eval_value; eauto.
  revert H1. apply LR_equiv.
  simpl. simpl in H. apply soundness; auto.
  exists q1. split.
  revert H2.
  simpl. apply eval_lrapp_congruence.
  intro q. apply eval_app_congruence; intros; auto.
  apply eval_trans with q2; auto.
  revert H3.
  apply LR_equiv. simpl.
  apply lrsemapp_equiv.
  simpl. apply cat_respects; auto.
  apply PLT.pair_eq; auto.
  symmetry.
  apply soundness; auto.

  (* I case *)
  cut (exists z : term (σ ⇒ σ),
     eval (σ ⇒ σ) (tI σ) z /\ LR (σ ⇒ σ) z (denote _ (tI _))).
  revert H.
  generalize (tI σ).
  generalize Hσ.
  rewrite Hσ. intro H.
  replace H with (refl_equal (lrtys ls τ)). simpl. intros.
  subst t. clear H Hσ.
  destruct H2 as [z0 [??]].
  eapply LR_under_apply; eauto.
  apply Eqdep_dec.UIP_dec. decide equality.

  exists (tI σ). split. apply eI. apply LR_I.

  (* K case *)
  cut (exists z,
     eval _ (tK σ₁ σ₂) z /\ LR _ z (denote _ (tK _ _))).
  revert H.
  generalize (tK σ₁ σ₂).
  generalize Hσ.
  rewrite Hσ. intro H.
  replace H with (refl_equal (lrtys ls τ)). simpl. intros.
  subst t. clear H Hσ.
  destruct H2 as [z0 [??]].
  eapply LR_under_apply; eauto.
  apply Eqdep_dec.UIP_dec. decide equality.

  exists (tK _ _). split. apply eK. apply LR_K.

  (* S case *)
  cut (exists z,
     eval _ (tS σ₁ σ₂ σ₃) z /\ LR _ z (denote _ (tS _ _ _))).
  revert H.
  generalize (tS σ₁ σ₂ σ₃).
  generalize Hσ.
  rewrite Hσ. intro H.
  replace H with (refl_equal (lrtys ls τ)). simpl. intros.
  subst t. clear H Hσ.
  destruct H2 as [z0 [??]].
  eapply LR_under_apply; eauto.
  apply Eqdep_dec.UIP_dec. decide equality.

  exists (tS _ _ _).
 split. simpl. apply eS. apply LR_S.

  (* IF case *)
  cut (exists z,
     eval _ (tIF σ) z /\ LR _ z (denote _ (tIF _))).
  revert H.
  generalize (tIF σ).
  generalize Hσ.
  rewrite Hσ. intro H.
  replace H with (refl_equal (lrtys ls τ)). simpl. intros.
  subst t. clear H Hσ.
  destruct H2 as [z0 [??]].
  eapply LR_under_apply; eauto.
  apply Eqdep_dec.UIP_dec. decide equality.

  exists (tIF σ). split. apply eIF. apply LR_IF.
Qed.

(**  A simpified form of the fundamental lemma that follows
     from the inductively-strong one above.
  *)
Lemma fundamental_lemma' : forall τ (m:term τ),
  exists z, eval τ m z /\ LR τ z 〚 m 〛.
Proof.
  intros.
  apply (fundamental_lemma _ m nil _ m tt tt
    (Logic.refl_equal _) (Logic.refl_equal _) I).
Qed.

(** * Contextual equivalance and the adequacy theorem.

    Now we define contextual equivalance.  Contexts here are
    given in "inside-out" form, which makes the induction in the
    adequacy proof significantly easier.
  *)
Inductive context τ : ty -> Type :=
  | cxt_top : context τ τ
  | cxt_appl : forall σ₁ σ₂,
                    term σ₁ ->
                    context τ σ₂ ->
                    context τ (σ₁ ⇒ σ₂)
  | cxt_appr : forall σ₁ σ₂,
                    term (σ₁ ⇒ σ₂) ->
                    context τ σ₂ ->
                    context τ σ₁.

Fixpoint plug τ σ (C:context τ σ) : term σ -> term τ :=
  match C in context _ σ return term σ -> term τ with
  | cxt_top _ => fun x => x
  | cxt_appl _ σ₁ σ₂ t C' => fun x => plug τ _ C' (tapp x t)
  | cxt_appr _ σ₁ σ₂ t C' => fun x => plug τ _ C' (tapp t x)
  end.

Definition cxt_eq τ σ (m n:term σ):=
  forall (C:context τ σ) (z:term τ),
    eval τ (plug τ σ C m) z <-> eval τ (plug τ σ C n) z.

(**  Adequacy means that terms with equivalant denotations
     are contextually equivalant in any boolean context.
  *)
Theorem adequacy : forall τ (m n:term τ),
  〚m〛 ≈ 〚n〛 -> cxt_eq 2 τ m n.
Proof.
  intros. intro.
  revert n m H.
  induction C.

  simpl; intros.
  destruct (fundamental_lemma' _ m) as [zm [??]]. simpl in *.
  destruct (fundamental_lemma' _ n) as [zn [??]]. simpl in *.
  destruct H1 as [bm [??]].
  destruct H3 as [bn [??]].
  subst zm zn.
  rewrite H in H4.
  rewrite H4 in H5.
  assert (bm = bn).
  apply disc_elem_inj in H5. auto.
  subst bn.
  split; intro.
  assert (z = (tbool bm)).
  eapply eval_eq; eauto.
  subst z. auto.
  assert (z = (tbool bm)).
  eapply eval_eq; eauto.
  subst z. auto.

  simpl. intros.
  apply IHC. simpl.
  apply cat_respects; auto.
  apply PLT.pair_eq; auto.

  simpl; intros.
  apply IHC. simpl.
  apply cat_respects; auto.
  apply PLT.pair_eq; auto.
Qed.

(**  As a corollary of the fundamental lemma, we learn that
     the calculus is strongly normalizing.
  *)
Corollary normalizing : forall τ (m:term τ), exists z, eval τ m z.
Proof.
  intros.
  generalize (fundamental_lemma' τ m).
  simpl. intros [z [??]]. exists z; auto.
Qed.

(** These should print "Closed under the global context", meaning these
    theorems hold without the use of any axioms.
  *)
Print Assumptions adequacy.
Print Assumptions normalizing.