(****************************************************************
  Copyright © 2010-2024  Amy Felty

  This file is part of "Two-Level Hybrid in Coq"

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <https://www.gnu.org/licenses/>.
  ***************************************************************)
(****************************************************************
   File: TypingSimple.v

   original: January 2014
   Apr 2024: Current Coq Version: V8.18.0

   1. Type unicity (the R and G approach coincide since there is only
      one context)
   2. Adequacy (using R approach)

  ***************************************************************)

From HybridSys Require Export sl.

Section encoding.

(****************************************************************
   Types
  ****************************************************************)

Inductive tp: Set :=
   num : tp
 | arr : tp -> tp -> tp.

(****************************************************************
   Constants for Lambda Terms
  ****************************************************************)

Inductive Econ: Set := Cabs: tp -> Econ | Capp: Econ.
Definition uexp : Set := expr Econ.

Definition Var : var -> uexp := (VAR Econ).
Definition Bnd : bnd -> uexp := (VAR Econ).
Definition lam : tp -> (uexp -> uexp) -> uexp :=
  fun t:tp => fun f:uexp->uexp =>
  (APP (CON (Cabs t)) (lambda f)).
Definition app : uexp -> uexp -> uexp :=
  fun e1:uexp => fun e2:uexp => (APP (APP (CON Capp) e1) e2).

(****************************************************************
   Some Properties of Constructors
  ****************************************************************)

Hint Resolve level_CON level_VAR level_BND level_APP level_ABS : hybrid.
Hint Resolve proper_APP abstr_proper : hybrid.
Hint Unfold proper: hybrid.
Hint Rewrite ext_eq_eta : hybrid.

Lemma proper_Var: forall x:var, (proper (Var x)).
Proof.
unfold Var; auto with hybrid.
Qed.

Lemma proper_lam: forall (t:tp) (E:uexp -> uexp),
  abstr E -> proper (lam t (fun x => E x)).
Proof.
unfold lam; auto with hybrid.
Qed.

Lemma proper_app: forall (E1 E2:uexp),
  proper E1 -> proper E2 -> proper (app E1 E2).
Proof.
unfold app; auto with hybrid.
Qed.

Hint Resolve proper_Var : hybrid.

(****************************************************************
   The atm type and instantiation of oo.
  ****************************************************************)
Inductive atm : Set :=
   oft : uexp -> tp -> atm
 | term : uexp -> atm.

Definition oo_ := oo atm Econ.
Definition atom_ : atm -> oo_ := atom Econ.
Definition T_ : oo_ := T atm Econ.

Hint Unfold oo_ atom_ T_: hybrid.

(****************************************************************
   Contexts with Unique Variables
  ****************************************************************)

Definition nvA (a:atm) : var
  := match a with
       oft e t => (newvar e)
     | term e => (newvar e)
     end.

Lemma nvA_oft : forall (e:uexp) (t:tp),
  nvA (oft e t) = (newvar e).
Proof.
simpl; auto.
Qed.

Lemma nvA_term : forall (e:uexp), nvA (term e) = (newvar e).
Proof.
simpl; auto.
Qed.

Fixpoint nvC (l:list atm) {struct l} : var
  := match l with
       nil => 0
     | (a::l') => max (nvA a) (nvC l')
     end.

Lemma nvC_cons: forall (a:atm) (l:list atm),
  nvC (a::l) = max (nvA a) (nvC l).
Proof.
simpl; auto.
Qed.

Lemma greater_head_nvC : forall (v:var) (t:tp) (l:list atm),
  (nvC (oft (Var v) t::l)) > v.
Proof.
intros v t l; simpl.
assert (h:(exists n:nat, (nvC l)=n)).
{ exists (nvC l); auto. }
elim h; clear h; intros n h; rewrite h; clear h.
case n; simpl; auto.
intro n0; rewrite Nat.succ_max_distr.
generalize (Nat.max_dec (S v) (S n0)); intros [h | h].
- rewrite h; lia.
- generalize (PeanoNat.Nat.max_spec_le (S v) (S n0)).
rewrite h; lia.
Qed.

Lemma greater_nvC: forall (v:var) (t:tp) (l:list atm),
  (In (oft (Var v) t) l) -> (nvC l > v).
Proof.
induction l; try contradiction; auto.
(* cons case *)
generalize a; clear a; induction a; try contradiction; auto.
- generalize u; clear u; induction u.
  (* CON case *)
  + intro h; specialize in_inv with (1:=h); clear h; intros [h | h];
      try discriminate h.
    simpl; apply IHl; auto.
  (* VAR case *)
  + intro h; specialize in_inv with (1:=h); clear h; intros [h | h].
    * unfold Var; injection h; intros; subst; clear h.
      apply greater_head_nvC; auto.
    * generalize (IHl h); clear IHl h; intro h.
      rewrite -> nvC_cons.
      rewrite -> nvA_oft.
      unfold newvar.
      generalize (PeanoNat.Nat.max_dec (S v0) (nvC l)); intros [h1 | h1].
      -- generalize (PeanoNat.Nat.max_spec_le (S v0) (nvC l)).
         rewrite h1; lia.
      -- rewrite h1; auto.
  (* BND case *)
  + intro h; specialize in_inv with (1:=h); clear h; intros [h | h];
      try discriminate h.
    simpl; apply IHl; auto.
  (* APP case *)
  + intro h; specialize in_inv with (1:=h); clear h; intros [h | h];
      try discriminate h.
    simpl.
    generalize (IHl h); intro h1.
    generalize (PeanoNat.Nat.max_spec_le (max (newvar u1) (newvar u2)) (nvC l));
      intro h2.
    lia.
  (* ABS case *)
  + intro h; specialize in_inv with (1:=h); clear h; intros [h | h];
      try discriminate h.
    simpl.
    generalize (IHl h); intro h1.
    generalize (PeanoNat.Nat.max_spec_le (newvar u) (nvC l)); intro h2.
    lia.
(* term case *)
- simpl; intros [h | h]; try discriminate h.
  generalize (IHl h); intro h1.
  generalize (PeanoNat.Nat.max_spec_le (newvar u) (nvC l)); lia.
Qed.

Lemma fresh_nvC: forall (v:var) (t:tp) (l:list atm),
  (In (oft (Var v) t) l) -> (nvC l <> v).
Proof.
intros v t l h; generalize (greater_nvC v t l); intro h1.
generalize (h1 h); intro; lia.
Qed.

(****************************************************************
   Definition of prog
  ****************************************************************)

Inductive prog : atm -> oo_ -> Prop :=
  | of_l :
      forall (A B:tp) (M:uexp -> uexp),
      abstr M ->
      prog (oft (lam A (fun x => M x)) (arr A B))
        (All (fun x : uexp => Imp (oft x A) (atom_ (oft (M x) B))))
  | of_a :
      forall M N: uexp, forall A B:tp,
      prog (oft (app M N) A)
        (Conj (atom_ (oft M (arr B A))) (atom_ (oft N B)))
  | tm_l : forall (A:tp) (M:uexp -> uexp),
      abstr M ->
      prog (term (lam A (fun x => (M x))))
        (All (fun x:uexp => (Imp (term x) (atom_ (term (M x))))))
  | tm_a : forall M N:uexp,
      prog (term (app M N))
        (Conj (atom_ (term M)) (atom_ (term N))).

(****************************************************************
   Instantiation of seq
  ****************************************************************)

Definition seq_ : nat -> list atm -> oo_ -> Prop := seq prog.
Definition seq'_ := seq' prog.
Definition seq0 (B : oo_) : Prop := exists i : nat, seq_ i nil B.

End encoding.

#[global] Hint Resolve level_CON level_VAR level_BND level_APP level_ABS : hybrid.
#[global] Hint Resolve proper_APP abstr_proper : hybrid.
#[global] Hint Unfold proper: hybrid.
#[global] Hint Resolve proper_Var : hybrid.
#[global] Hint Resolve of_l of_a tm_l tm_a : hybrid.
#[global] Hint Unfold oo_ atom_ T_: hybrid.
#[global] Hint Unfold seq_ seq'_ seq0: hybrid.

(****************************************************************
 1. Type Uniqueness
  ****************************************************************)

(************)
(* Contexts *)
(************)

Section ctx_uniq.

Inductive xtG : list atm -> Prop :=
| nil_t : xtG nil
| cons_t : forall (Phi_t:list atm) (t:tp),
    xtG Phi_t -> xtG (oft (Var (nvC Phi_t)) t::Phi_t).

Lemma memb_xtG_NoLam : forall (Phi_t:list atm) (E:uexp->uexp) (T T':tp),
  xtG Phi_t -> ~(In (oft (lam T E) T') Phi_t).
Proof.
intros Phi_t E T T'; induction 1; simpl; try tauto.
intro h; elim IHxtG.
destruct h as [h2 | h2]; try discriminate h2; auto.
Qed.

Lemma memb_xtG_NoApp : forall (Phi_t:list atm) (M N:uexp) (T:tp),
  xtG Phi_t -> ~(In (oft (app M N) T) Phi_t).
Proof.
intros Phi_t M N T; induction 1; simpl; try tauto.
intro h; elim IHxtG.
destruct h as [h2 | h2]; try discriminate h2; auto.
Qed.

Lemma memb_uniq : forall (Phi_t:list atm) (e:uexp) (t t':tp),
  xtG Phi_t ->
  In (oft e t) Phi_t -> In (oft e t') Phi_t ->
   (exists v:var, e = Var v) /\ t = t'.
Proof.
intros Phi_t e t t'; induction 1; try (simpl; tauto).
simpl; intros h1 h2; destruct h1 as [h1 | h1]; destruct h2 as [h2 | h2].
- injection h1; injection h2; intros; subst; subst; simpl; split; auto.
  exists (nvC Phi_t); auto.
- injection h1; intros; subst; subst.
  specialize fresh_nvC with (1:=h2); tauto.
- injection h2; intros; subst; subst.
  specialize fresh_nvC with (1:=h1); tauto.
- auto.
Qed.

End ctx_uniq.

(****************************)
(* Main Lemmas and Theorems *)
(****************************)

#[global] Hint Resolve memb_xtG_NoLam memb_xtG_NoApp nil_t cons_t : hybrid.

Section uniq.

Lemma uniq : forall (i:nat) (Phi_t:list atm) (M:uexp) (A:tp),
  xtG Phi_t ->
  seq_ i Phi_t (atom_ (oft M A)) ->
  forall (k:nat) (B:tp), seq_ k Phi_t (atom_ (oft M B)) -> A = B.
Proof.
intros i Phi_t M A h1 h2 k B h3.
generalize
 (lt_wf_ind i
    (fun i:nat =>
     forall (Phi_t:list atm) (M:uexp) (A:tp),
     xtG Phi_t ->
     seq_ i Phi_t (atom_ (oft M A)) ->
     forall (k:nat) (B:tp), seq_ k Phi_t (atom_ (oft M B)) -> A = B)).
intro H'.
apply H' with Phi_t M k; clear H'; auto.
clear h1 h2 h3 i Phi_t M A B k.
intros i hInd Phi_t M A hCInv h1 k B h2.
inversion h1; subst.
- inversion H0; subst; clear H0.
  (* lam case *)
  + inversion H3; subst; clear H3.
    inversion h2; subst; clear h2.
    * inversion H0; subst; clear H0.
      inversion H5; subst; clear H5.
      assert (h: proper (Var (nvC Phi_t))); auto with hybrid.
      generalize (H2 (Var (nvC Phi_t)) h); clear H2; intro H2.
      generalize (H6 (Var (nvC Phi_t)) h); clear H6; intro H6.
      clear h; assert (h:ext_eq M0 M).
      { apply abstr_lam_simp_eta; auto. }
      unfold ext_eq in h.
      assert (hp:proper (Var (nvC Phi_t))); auto with hybrid.
      specialize h with (1:=hp).
      rewrite <- h in H6; clear h H3 H7 M.
      inversion H2; subst; clear H2.
      inversion H6; subst; clear H6.
      assert (hCInv':xtG (oft (Var (nvC Phi_t)) A0::Phi_t)); eauto with hybrid.
      assert (h6: i0 < i0+1+1+1); try lia.
      generalize (hInd i0 h6 (oft (Var (nvC Phi_t)) A0::Phi_t)
                       (M0 (Var (nvC Phi_t))) B0 hCInv' H3 i B1 H2);
        intro h7; rewrite h7; auto.
    * absurd (In (oft (lam A0 (fun x:uexp => M0 x)) B) (A'::L)); eauto with hybrid.
  (* app case *)
  + inversion H3; subst; clear H3.
    inversion h2; subst; clear h2.
    * inversion H0; subst; clear H0.
      inversion H3; subst; clear H3.
      assert (h2: i < i+1+1); try lia.
      generalize (hInd i h2 Phi_t M0 (arr B0 A) hCInv H4 i1 (arr B1 B) H6);
        intro h4.
      inversion h4; auto.
    * absurd (In (oft (app M0 N) B) (A'::L)); eauto with hybrid.
- (* context case *)
  generalize hCInv; intro h'.
  inversion h'; subst.
  specialize memb_uniq with (1:=hCInv) (2:=H2) (3:=H2);
    intros [[v h3] h4]; clear h4; subst.
  inversion h2; subst; clear h2.
  + inversion H1.
  + specialize memb_uniq with (1:=hCInv) (2:=H2) (3:=H3);
      intros [h3 h4]; subst; auto.
Qed.

Lemma uniq_cor: forall (M:uexp) (A B:tp),
  seq0 (atom_ (oft M A)) -> seq0 (atom_ (oft M B)) -> (A=B).
Proof.
intros M A B h1 h2.
unfold seq0 in h1; elim h1; clear h1; intros i h1.
unfold seq0 in h2; elim h2; clear h2; intros k h2.
apply uniq with i (nil:list atm) M k; eauto with hybrid.
Qed.

End uniq.

(****************************************************************
 2. Adequacy
  ****************************************************************)

(************)
(* Contexts *)
(************)

Section ctx_adeq.

Inductive xtR : list atm -> list atm -> Prop :=
| nil_xt : xtR nil nil
| cons_xt : forall (Phi_x Phi_t:list atm) (x:uexp) (t:tp),
    xtR Phi_x Phi_t ->
    xtR (term x::Phi_x) (oft x t::Phi_t).

Lemma memb_adeq : forall (Phi_x Phi_t:list atm) (E:uexp) (T:tp),
  xtR Phi_x Phi_t -> In (oft E T) Phi_t -> In (term E) Phi_x.
Proof.
intros Phi_x Phi_t E B; induction 1; try (simpl; tauto).
intro h2; simpl in h2; destruct h2 as [h2 | h2].
- injection h2; intros; subst; subst; simpl; auto.
- simpl; right; auto with hybrid.
Qed.

End ctx_adeq.

#[global] Hint Resolve memb_adeq nil_xt cons_xt : hybrid.

(****************)
(* oft Adequacy *)
(****************)

Section of_adeq.

Hint Rewrite ext_eq_eta : hybrid.

Lemma of_adeq :
  forall (i:nat) (M:uexp) (A:tp) (Phi_x Phi_t:list atm),
  xtR Phi_x Phi_t ->
  seq_ i Phi_t (atom_ (oft M A)) ->
  seq_ i Phi_x (atom_ (term M)).
Proof.
intro i.
generalize
 (lt_wf_ind i
   (fun i:nat =>
    forall (M : uexp) (A : tp) (Phi_x Phi_t : list atm),
    xtR Phi_x Phi_t ->
    seq_ i Phi_t (atom_ (oft M A)) -> seq_ i Phi_x (atom_ (term M)))).
intro H'.
apply H'; clear H' i; auto.
intros i h M A Phi_x Phi_t h1 h2.
inversion h2; subst; clear h2.
- inversion H0; subst; clear H0.
  (* lam case *)
  + inversion H3; subst; clear H3.
    unfold seq_,atom_;
      apply s_bc with (All (fun x:uexp => (Imp (term x) (atom_ (term (M0 x))))));
      auto with hybrid.
    apply s_all; auto.
    intros x h2.
    generalize (H2 x h2); clear H2; intro H2.
    inversion H2; subst; clear H2.
    apply s_imp; auto.
    apply h with B (oft x A0::Phi_t); eauto with hybrid; try lia.
    (* app case *)
  + inversion H3; subst; clear H3.
    unfold seq_,atom_;
      apply s_bc with (Conj (atom_ (term M0)) (atom_ (term N)));
      auto with hybrid.
    apply s_and; auto.
    * apply h with (arr B A) Phi_t; auto; try lia.
    * apply h with B Phi_t; auto; try lia.
(* context case *)
- inversion h1; subst.
  apply s_init; eauto with hybrid.
Qed.

Lemma of_adeq_cor : forall (M:uexp) (A:tp),
  seq0 (atom_ (oft M A)) ->
  seq0 (atom_ (term M)).
Proof.
intros M A [n h].
generalize nil_xt; intro h1.
specialize of_adeq with (1:=h1) (2:=h); intro h2.
exists n; auto.
Qed.

End of_adeq.
