Require Export heap.
Require Export Omega.
Require Export hetList.
Require Export Coq.Program.Equality.
Require Export Coq.Logic.ProofIrrelevance.

Ltac solveByInv :=
  match goal with
      |H:_ |- _ => solve[inv H]
  end.

(*evaluation context decomposition*)
Inductive decompose : term -> ctxt -> term -> Prop :=
|decompApp : forall e1 e2 E e, decompose e1 E e ->
                               decompose (app e1 e2) (appCtxt e2 E) e
|decompAppVal : forall v e2 E e (prf:value v),
                  decompose e2 E e -> decompose (app v e2) (appValCtxt v prf E) e
|appHole : forall e v, value v -> decompose (app (lambda e) v) hole (app (lambda e) v)
|decompGet : forall e E e', decompose e E e' ->
                            decompose (get e) (getCtxt E) e'
|decompGetHole : forall l, decompose (get (loc l)) hole (get (loc l))
|decompPut : forall e1 e2 E e, decompose e1 E e ->
                               decompose (put e1 e2) (putCtxt e2 E) e
|decompPutVal : forall v e2 E e (prf:value v),
                  decompose e2 E e ->
                  decompose (put v e2) (putValCtxt v prf E) e
|decompPutHole : forall n v, value v -> decompose (put (loc n) v) hole (put (loc n) v)
|decompAlloc : forall e E e', decompose e E e' ->
                              decompose (alloc e) (allocCtxt E) e'
|decompAllocHole : forall v, value v -> decompose (alloc v) hole (alloc v)
|decompAtomicHole : forall e, decompose (atomic e) hole (atomic e)
|decompFork : forall e, decompose (fork e) hole (fork e)
|decompInAtomic : forall e e' E, decompose e E e' ->
                            decompose (inatomic e) (inatomicCtxt E) e'
|decompInAtomicHole : forall v, value v -> decompose (inatomic v) hole (inatomic v).

Theorem decomposeValFalse : forall t E e, value t -> decompose t E e -> False.
Proof.
  intros. inv H0; try solve[inv H].
Qed.

Ltac decompVal :=
  match goal with
      |H:value ?t, H':decompose ?t ?E ?e |- _ =>
       apply decomposeValFalse in H'; auto; inv H'
  end.

Theorem decomposeDeterministic : forall t E E' e e',
                                   decompose t E e -> decompose t E' e' ->
                                   E = E' /\ e = e'.
Proof.
  intros. genDeps {{E'; e'}}. induction H; intros.
  {inv H0. eapply IHdecompose in H5. invertHyp. auto. decompVal. inv H. }
  {inv H0; try decompVal. eapply IHdecompose in H5. invertHyp.
   assert(prf=prf0). apply proof_irrelevance. subst. auto. }
  {inv H0. inv H5. decompVal. auto. }
  {inv H0. eapply IHdecompose in H2. invertHyp. auto. inv H. }
  {inv H0. inv H1. auto. }
  {inv H0. eapply IHdecompose in H5. invertHyp. eauto. decompVal. inv H. }
  {inv H0. decompVal. eapply IHdecompose in H5. invertHyp.
   assert(prf=prf0). apply proof_irrelevance. subst. auto. decompVal. }
  {inv H0. inv H5. decompVal. auto. }
  {inv H0. eapply IHdecompose in H2. invertHyp. auto. decompVal. }
  {inv H0. decompVal. auto. }
  {inv H0. auto. }
  {inv H0. auto. }
  {inv H0. eapply IHdecompose in H2. invertHyp. auto. decompVal. }
  {inv H0. decompVal. auto. }
Qed.

Ltac decompSame :=
  match goal with
      |H:decompose ?t ?E ?e,H':decompose ?t ?E' ?e' |- _ =>
       eapply decomposeDeterministic in H; eauto; invertHyp
  end.

(*fill an evaluation context*)
Fixpoint fill (E:ctxt) (e:term) :=
  match E with
      |appCtxt e' E => app (fill E e) e'
      |appValCtxt v _ E => app v (fill E e)
      |getCtxt E => get (fill E e)
      |putCtxt e' E => put (fill E e) e'
      |putValCtxt v _ E => put v (fill E e)
      |allocCtxt E => alloc (fill E e)
      |inatomicCtxt E => inatomic (fill E e)
      |hole => e
  end.

(*
commit H ==> indicates the log is valid and H contains the new writes from the log
abort e L ==> log was invalid, resume execution at term e with log L
*)
Inductive validateRes : Type :=
|commit : heap -> validateRes
|abort : term -> log -> validateRes.

(*Transactional log validation*)
Inductive validate : stamp -> log -> heap -> stamp -> validateRes -> Prop :=
|validateNil : forall S S' H, validate S nil H S' (commit H)
|validateCommitRead : forall S S' S'' l v E H H' L,
                        lookup H l = Some(v, S') -> S > S' ->
                        validate S L H S'' (commit H') ->
                        validate S (readItem l E v::L) H S'' (commit H')
|validateAbortPropogate : forall S S' L H x L' e,
                            validate S L H S' (abort e L') ->
                            validate S (x::L) H S' (abort e L')
|validateAbortRead : forall S S' S'' H L E H' l v v',
              validate S L H S'' (commit H') -> lookup H l = Some(v, S') ->
              S' >= S -> validate S (readItem l E v'::L) H S''
                                (abort (fill E(get(loc l))) L)
|validateWrite : forall S S' L H H' l v,
                   validate S L H S' (commit H') ->
                   validate S (writeItem l v::L) H S' (commit ((l, v, S')::H'))
.

(*lookup a term in a thread's write set*)
Fixpoint logLookup (L:log) (l:location) :=
  match L with
      |readItem _ _ _::L' => logLookup L' l
      |writeItem l' v::L' => if eq_nat_dec l l'
                            then Some v
                            else logLookup L' l
      |nil => None
  end.

Fixpoint open (e:term) (k:nat) (e':term) :=
  match e with
      |lambda e => lambda (open e (S k) e')
      |loc l => loc l
      |unit => unit
      |var k' => if eq_nat_dec k k'
                then e'
                else var k'
      |app e1 e2 => app (open e1 k e') (open e2 k e')
      |get e => get (open e k e')
      |put e1 e2 => put (open e1 k e') (open e2 k e')
      |alloc e => alloc (open e k e')
      |fork e => fork (open e k e')
      |atomic e => atomic (open e k e')
      |inatomic e => inatomic (open e k e')
  end.

(*transactional step (used by both p_step and f_step)*)
Inductive trans_step (H:heap) : thread -> thread -> Prop :=
|t_readStep : forall S L E l t v e0 S',
                decompose t E (get (loc l)) -> logLookup L l = None ->
                lookup H l = Some(v, S') ->
                trans_step H (Some(S, e0), L, t)
                             (Some(S, e0), readItem l E v::L, fill E v)
|t_readInDomainStep : forall S l v L E t e0,
                      decompose t E (get (loc l)) -> logLookup L l = Some v ->
                      trans_step H (Some(S, e0), L, t) (Some(S, e0), L, fill E v)
|t_writeStep : forall S L E l v t,
               decompose t E (put (loc l) v) -> S <> None ->
               trans_step H (S, L, t) (S, writeItem l v::L, fill E unit)
|t_atomicIdemStep : forall E e t L S,
                     decompose t E (atomic e) -> S <> None ->
                     trans_step H (S, L, t) (S, L, fill E e)
|t_betaStep : forall L E e t v S,
              decompose t E (app (lambda e) v) -> S <> None ->
              trans_step H (S, L, t) (S, L, fill E (open e 0 v))
.

Inductive replay_step H : thread -> thread -> Prop :=
|r_readStepValid : forall S L E l t v e0 S',
                lookup H l = Some(v, S') -> S > S' ->
                decompose t E (get (loc l)) -> logLookup L l = None ->
                replay_step H (Some(S, e0), L, t) (Some(S, e0), readItem l E v::L, fill E v)
|r_readStepInvalid : forall S L E v' l t v e0 S',
                lookup H l = Some(v',S') -> S' >= S ->
                decompose t E (get (loc l)) -> logLookup L l = None ->
                replay_step H (Some(S, e0), L, t) (Some(S, e0), readItem l E v::L, fill E v)
|r_readInDomainStep : forall S l v L E t e0,
                      decompose t E (get (loc l)) -> logLookup L l = Some v ->
                      replay_step H (Some(S, e0), L, t) (Some(S, e0), L, fill E v)
|r_writeStep : forall S L E l v t,
               decompose t E (put (loc l) v) -> S <> None ->
               replay_step H (S, L, t) (S, writeItem l v::L, fill E unit)
|r_atomicIdemStep : forall E e t L S,
                     decompose t E (atomic e) -> S <> None ->
                     replay_step H (S, L, t) (S, L, fill E e)
|r_betaStep : forall L E e t v S,
              decompose t E (app (lambda e) v) -> S <> None ->
              replay_step H (S, L, t) (S, L, fill E (open e 0 v))
.

Inductive replay H : thread -> thread -> Prop :=
|replayRefl : forall t, replay H t t
|replayStep : forall t t' t'',
                replay_step H t t' -> replay H t' t'' ->
                replay H t t''.

(*left recursive version of replay*)
Inductive rewind H : thread -> thread -> Prop :=
|rewindRefl : forall t, rewind H t t
|rewindStep : forall t t' t'',
                rewind H t t' -> replay_step H t' t'' ->
                rewind H t t''.

(*If inversion produces the same hypothesis, skip it, otherwise invert all equalities*)
Ltac invertEq :=
  match goal with
      |H:?a = ?b |- _ => let n := fresh
                         in inversion H as n; match type of n with
                                                  |?a = ?b => fail
                                              end
      |H:?a = ?b |- _ => inv H
  end.

(*parital abort STM semantics (single step)*)
Inductive p_step : nat -> heap -> pool -> nat -> heap -> pool -> Prop :=
|p_transStep : forall C H t t', trans_step H t t' ->
                           p_step C H (Single t) C H (Single t')
|p_parLStep : forall C H T1 T2 C' H' T1',
          p_step C H T1 C' H' T1' -> p_step C H (Par T1 T2) C' H' (Par T1' T2)
|p_parRStep : forall C H T1 T2 C' H' T2',
          p_step C H T2 C' H' T2' -> p_step C H (Par T1 T2) C' H' (Par T1 T2')
|p_forkStep : forall C H E e t,
              decompose t E (fork e) ->
              p_step C H (Single(None, nil, t)) C H
                   (Par (Single(None, nil, fill E unit)) (Single(None, nil, e)))

|p_abortStep : forall L S H L' C e e0 e' S',
           validate S L H S' (abort e' L') ->
           p_step C H (Single(Some(S, e0), L, e))
                  (plus 1 C) H (Single(Some(C, e0), L', e'))
|p_allocStep : forall C H v E t l,
               lookup H l = None -> decompose t E (alloc v) ->
               p_step C H (Single(None, nil, t)) (plus 1 C) ((l, v, C)::H)
                    (Single(None, nil, fill E (loc l)))
|p_commitStep : forall C H S L v t E H' e0,
                validate S L H C (commit H') -> decompose t E (inatomic v) ->
                p_step C H (Single(Some(S, e0), L, t)) (plus 1 C) H' (Single(None, nil, fill E v))
|p_atomicStep : forall C H E e t,
                decompose t E (atomic e) ->
                p_step C H (Single(None, nil, t)) (plus 1 C) H
                       (Single(Some(C, fill E(inatomic e)),[],fill E (inatomic e)))
|p_betaStep : forall C H E e t v,
              decompose t E (app (lambda e) v) ->
              p_step C H (Single(None, nil, t)) C H
                     (Single(None, nil, fill E (open e 0 v))).

Inductive p_multistep : nat -> heap -> pool -> nat -> heap -> pool -> Prop :=
|p_multi_refl : forall C H T, p_multistep C H T C H T
|p_multi_step : forall C H T C' H' T' C'' H'' T'',
                p_step C H T C' H' T' -> p_multistep C' H' T' C'' H'' T'' ->
                p_multistep C H T C'' H'' T''.

(*full abort STM semantics (single step)*)
Inductive f_step : nat -> heap -> pool -> nat -> heap -> pool -> Prop :=
|f_transStep : forall C H t t', trans_step H t t' ->
                           f_step C H (Single t) C H (Single t')
|f_parLStep : forall C H T1 T2 C' H' T1',
          f_step C H T1 C' H' T1' -> f_step C H (Par T1 T2) C' H' (Par T1' T2)
|f_parRStep : forall C H T1 T2 C' H' T2',
          f_step C H T2 C' H' T2' -> f_step C H (Par T1 T2) C' H' (Par T1 T2')
|f_forkStep : forall C H E e t,
              decompose t E (fork e) ->
              f_step C H (Single(None, nil, t)) C H
                   (Par (Single(None, nil, fill E unit)) (Single(None, nil, e)))
|f_abortStep : forall L S H L' C e e0 e' S',
           validate S L H S' (abort e' L') ->
           f_step C H (Single(Some(S, e0), L, e)) (plus 1 C) H (Single(Some(C, e0), nil, e0))
|f_allocStep : forall C H v E t l,
               lookup H l = None -> decompose t E (alloc v) ->
               f_step C H (Single(None, nil, t)) (plus 1 C) ((l, v, C)::H)
                    (Single(None, nil, fill E (loc l)))
|f_commitStep : forall C H S L v t E H' e0,
                validate S L H C (commit H') -> decompose t E (inatomic v) ->
                f_step C H (Single(Some(S, e0), L, t)) (plus 1 C) H' (Single(None, nil, fill E v))
|f_atomicStep : forall C H E e t,
                decompose t E (atomic e) ->
                f_step C H (Single(None, nil, t)) (plus 1 C) H
                       (Single(Some(C, fill E (inatomic e)), nil, fill E (inatomic e)))
|f_betaStep : forall C H E e t v,
              decompose t E (app (lambda e) v) ->
              f_step C H (Single(None, nil, t)) C H
                     (Single(None, nil, fill E (open e 0 v))).

Inductive f_multistep : nat -> heap -> pool -> nat -> heap -> pool -> Prop :=
|f_multi_refl : forall C H T, f_multistep C H T C H T
|f_multi_step : forall C H T C' H' T' C'' H'' T'',
                f_step C H T C' H' T' -> f_multistep C' H' T' C'' H'' T'' ->
                f_multistep C H T C'' H'' T''.

Inductive trans_multistep H : thread -> thread -> Prop :=
|trans_refl : forall t, trans_multistep H t t
|trans_multi_step : forall t t' t'',
                      trans_step H t t' -> trans_multistep H t' t'' ->
                      trans_multistep H t t''.

Definition postfix {A:Type} (L1 L2 : list A) := exists diff, L2 = diff ++ L1.

(*all threads can rewind to their initial term of a transaction and have
**a stamp number less than the global clock*)
Inductive poolRewind C H : pool -> Prop :=
|rewindSingleNoTX : forall e, poolRewind C H (Single(None,nil,e))
|rewindSingleInTX : forall S e0 L e,
                      rewind H (Some(S,e0),nil,e0) (Some(S,e0),L,e) ->
                      S < C -> poolRewind C H (Single(Some(S,e0),L,e))
|rewindPar : forall T1 T2, poolRewind C H T1 -> poolRewind C H T2 -> poolRewind C H (Par T1 T2).

Theorem f_multi_L : forall C H T1 T2 T1' C' H',
                      f_multistep C H T1 C' H' T1' ->
                      f_multistep C H (Par T1 T2) C' H' (Par T1' T2).
Proof.
  intros. induction H0.
  {constructor. }
  {econstructor. eapply f_parLStep. eassumption. eassumption. }
Qed.

Theorem f_multi_R : forall C H T1 T2 T2' C' H',
                      f_multistep C H T2 C' H' T2' ->
                      f_multistep C H (Par T1 T2) C' H' (Par T1 T2').
Proof.
  intros. induction H0.
  {constructor. }
  {econstructor. eapply f_parRStep. eassumption. eassumption. }
Qed.

(*validation is idempotent*)
Theorem validateValidate : forall S L H S' L' e,
                             validate S L H S' (abort e L') ->
                             exists H'', validate S L' H S' (commit H'').
Proof.
  intros. dependent induction H0.
  {invertHyp. exists x0. auto. }
  {exists H'. assumption. }
Qed.

Theorem abortLogPostfix : forall S L H S' L' e,
                            validate S L H S' (abort e L') ->
                           postfix L' L.
Proof.
  intros. remember (abort e L'). induction H0; try solve[inv Heqv].
  {apply IHvalidate in Heqv. unfold postfix in *. invertHyp. exists (x::x0). auto. }
  {inv Heqv. unfold postfix. exists [readItem l E v']. auto. }
Qed.

Theorem decomposeEq : forall E t e, decompose t E e -> t = fill E e.
Proof.
  induction E; intros; try solve[inv H; simpl;erewrite <- IHE; eauto].
  {inv H; auto. }
Qed.

Theorem lengthsEq : forall (A:Type) (x y : list A), x = y -> length x = length y.
Proof.
  induction x; intros.
  {destruct y. auto. inv H. }
  {destruct y. inv H. inv H. simpl. apply f_equal. auto. }
Qed.

Theorem trans_replay: forall H t t' t'',
                         replay H t' t'' -> replay H t t' ->
                         replay H t t''.
Proof.
  intros. induction H1. auto. econstructor. eauto. auto.
Qed.

Theorem rewindTrans : forall H t t' t'',
                        rewind H t t' -> rewind H t' t'' ->
                        rewind H t t''.
Proof.
  intros. induction H1; auto. econstructor; eauto.
Qed.

Theorem rewindIFFReplay : forall H t t',
                            rewind H t t' <-> replay H t t'.
Proof.
  intros. split; intros.
  {induction H0. constructor. eapply trans_replay; eauto.
   econstructor. eauto. constructor. }
  {induction H0. constructor. eapply rewindTrans; eauto.
   econstructor; eauto. constructor. }
Qed.

Theorem p_multi_trans : forall C H T C' H' T' C'' H'' T'',
                          p_multistep C H T C' H' T' ->
                          p_multistep C' H' T' C'' H'' T'' ->
                          p_multistep C H T C'' H'' T''.
Proof.
  intros. induction H0; auto. econstructor; eauto.
Qed.

Theorem f_multi_trans : forall C H T C' H' T' C'' H'' T'',
                          f_multistep C H T C' H' T' ->
                          f_multistep C' H' T' C'' H'' T'' ->
                          f_multistep C H T C'' H'' T''.
Proof.
  intros. induction H0; auto. econstructor; eauto.
Qed.


(* *********************************************************************)
(*                                                                     *)
(*              The Compcert verified compiler                         *)
(*                                                                     *)
(*          Xavier Leroy, INRIA Paris-Rocquencourt                     *)
(*                                                                     *)
(*  Copyright Institut National de Recherche en Informatique et en     *)
(*  Automatique.  All rights reserved.  This file is distributed       *)
(*  under the terms of the GNU General Public License as published by  *)
(*  the Free Software Foundation, either version 2 of the License, or  *)
(*  (at your option) any later version.  This file is also distributed *)
(*  under the terms of the INRIA Non-Commercial License Agreement.     *)
(*                                                                     *)
(* *********************************************************************)

(** Defining recursive functions by Noetherian induction.  This is a simplified
  interface to the [Wf] module of Coq's standard library, where the functions
  to be defined have non-dependent types, and function extensionality is assumed. *)

Require Import Axioms.
Require Import Init.Wf.
Require Import Wf_nat.

Set Implicit Arguments.

Section FIX.

Variables A B: Type.
Variable R: A -> A -> Prop.
Hypothesis Rwf: well_founded R.
Variable F: forall (x: A), (forall (y: A), R y x -> B) -> B.

Definition Fix (x: A) : B := Wf.Fix Rwf (fun (x: A) => B) F x.

Theorem unroll_Fix:
  forall x, Fix x = F (fun (y: A) (P: R y x) => Fix y).
Proof.
  unfold Fix; intros. apply Wf.Fix_eq with (P := fun (x: A) => B).
  intros. assert (f = g). apply functional_extensionality_dep; intros.
  apply functional_extensionality; intros. auto.
  subst g; auto.
Qed.

End FIX.

(** Same, with a nonnegative measure instead of a well-founded ordering *)

Section FIXM.

Variables A B: Type.
Variable measure: A -> nat.
Variable F: forall (x: A), (forall (y: A), measure y < measure x -> B) -> B.

Definition Fixm (x: A) : B := Wf.Fix (well_founded_ltof A measure) (fun (x: A) => B) F x.

Theorem unroll_Fixm:
  forall x, Fixm x = F (fun (y: A) (P: measure y < measure x) => Fixm y).
Proof.
  unfold Fixm; intros. apply Wf.Fix_eq with (P := fun (x: A) => B).
  intros. assert (f = g). apply functional_extensionality_dep; intros.
  apply functional_extensionality; intros. auto.
  subst g; auto.
Qed.

End FIXM.


(*
 * Copyright (c) 2022 BedRock Systems, Inc.
 * This software is distributed under the terms of the BedRock Open-Source License.
 * See the LICENSE-BedRock file in the repository root for details.
 *)

(*
 * Some of the following code is derived from code original to the
 * stdpp project. That original code is
 *
 *	Copyright stdpp developers and contributors
 *
 * and used according to the following license.
 *
 *	SPDX-License-Identifier: BSD-3-Clause
 *
 * Original stdpp License:
 * https://gitlab.mpi-sws.org/iris/stdpp/-/blob/54252fbc10eaa88941ec1e157ce2c2e575e42604/LICENSE
 *)

Require Export stdpp.fin_maps.
Require Import bedrock.prelude.base.

(* TODO upstream to stdpp. *)
Section fin_maps.
  Context `{FinMap K M} {A : Type}.
  Implicit Type (m : M A).
  #[local] Set Default Proof Using "Type*".

  Lemma map_positive m1 m2 : m1 ∪ m2 = ∅ → m1 = ∅ ∧ m2 = ∅.
  Proof.
    intros Heq. pose proof map_positive_l _ _ Heq as ->.
    by rewrite ->left_id_L in Heq by apply _.
  Qed.

  Lemma map_positive_r m1 m2 : m1 ∪ m2 = ∅ → m2 = ∅.
  Proof. by intros [_ ?]%map_positive. Qed.

  (* Inspired by stdpp's size_union *)
  Lemma map_size_union m1 m2 (Hdisj : m1 ##ₘ m2) : size (m1 ∪ m2) = size m1 + size m2.
  Proof.
    unfold size, map_size. rewrite <- app_length.
    apply Permutation_length, NoDup_Permutation.
    - apply NoDup_map_to_list.
    - apply NoDup_app; repeat split; try apply NoDup_map_to_list.
      intros []. rewrite !elem_of_map_to_list. intros ??.
      by eapply map_disjoint_spec in Hdisj.
    - intros []. by rewrite ->elem_of_app, !elem_of_map_to_list, lookup_union_Some.
  Qed.

  (* Remaining lemmas use ssreflect.
  TODO: avoid if upstreaming to stdpp. *)
  Lemma map_union_difference (m1 m2 : M A) :
    (m1 ∪ m2) ∖ m1 = m2 ∖ m1.
  Proof.
    apply map_eq => i. apply option_eq => a.
    rewrite !lookup_difference_Some lookup_union_Some_raw.
    intuition congruence.
  Qed.

  Lemma map_difference_union_distr_l m1 m2 m3 : (m1 ∪ m2) ∖ m3 = m1 ∖ m3 ∪ m2 ∖ m3.
  Proof.
    apply map_eq => i. apply option_eq => a.
    rewrite !(lookup_difference_Some, lookup_difference_None, lookup_union_Some_raw).
    intuition try congruence; unfold is_Some in *; naive_solver.
  Qed.

  Lemma difference_map_disjoint (m1 m2 : M A) :
    m1 ##ₘ m2 → m2 ∖ m1 = m2.
  Proof.
    intros Hdisj. apply map_eq => i. apply option_eq => a.
    rewrite !lookup_difference_Some.
    intuition eauto using map_disjoint_Some_l.
  Qed.

  Lemma map_difference_union_distr_disj_l m1 m2 m3 :
    m1 ##ₘ m3 ->
    (m1 ∪ m2) ∖ m3 = m1 ∪ (m2 ∖ m3).
  Proof. intros. by rewrite map_difference_union_distr_l difference_map_disjoint. Qed.

  Lemma map_difference_insert_ne i j x y m (Hne : i ≠ j) :
    <[i:=x]> m ∖ {[j := y]} =
    <[i:=x]> (m ∖ {[j := y]}).
  Proof.
    rewrite !insert_union_singleton_l map_difference_union_distr_disj_l //.
    rewrite map_disjoint_singleton_l. exact: lookup_singleton_ne.
  Qed.

  Lemma map_insert_difference (m : M A) i x :
    <[i:=x]> m ∖ {[ i := x ]} = m ∖ {[ i := x ]}.
  Proof. by rewrite insert_union_singleton_l map_union_difference. Qed.

  Lemma map_fmap_difference {B} (f : A -> B) m a (i : K) :
    f <$> (m ∖ {[i := a]}) = (f <$> m) ∖ {[i := f a]}.
  Proof.
    rewrite !map_difference_filter map_filter_fmap /=. f_equiv.
    apply map_filter_ext => j r /= _.
    by rewrite !lookup_singleton_None.
  Qed.

  Lemma map_Forall_fmap {B} (m : M A) (f : A -> B) P :
    map_Forall (λ k _, P k) (f <$> m) <-> map_Forall (λ k _, P k) m.
  Proof.
    rewrite /map_Forall; setoid_rewrite lookup_fmap.
    split; intros Hin i d Hs. { eapply Hin. by rewrite Hs. }
    specialize (Hin i). destruct (m !! i); naive_solver.
  Qed.
End fin_maps.


Load "Example19".

(*****************Fresh(G,g,a,b,c,d) /\ (clos_bol b)->[G;g;g^a;g^b;g^c; if_then_else_B b (g^ab) (g^bc)]~[G;g;g^a;g^b;g^c;g^d]***********************)
Variable fmb4 : message -> message -> message -> message ->  Bool.
Theorem Example20: forall (n n1 n2 n3 n4: nat), (Fresh [ n ; n1 ; n2 ; n3 ; n4] []  = true ) -> (clos_bol (fmb4  (g n)  (exp (G n) (g n) (r n1)) (exp (G n) (g n) (r n2)) (exp (G n) (g n) (r n3))) = true) -> [ msg (G n) ; msg (g n); msg (exp (G n) (g n) (r n1)); msg (exp (G n) (g n) (r n2)); msg (exp (G n) (g n) (r n3)); msg (if_then_else_M (fmb4  (g n) (exp (G n) (g n) (r n1)) (exp (G n) (g n) (r n2)) (exp (G n) (g n) (r n3))) (exp (G n) (exp (G n) (g n) (r n1)) (r n2))  (exp (G n) (exp (G n) (g n) (r n2)) (r n3)))] ~   [ msg (G n) ; msg (g n); msg (exp (G n) (g n) (r n1)); msg (exp (G n) (g n) (r n2)); msg (exp (G n) (g n) (r n3)); msg (exp (G n) (g n) (r n4))].

Proof. intros.
assert(H2 : Fresh[n;n1;n2;n4] [] = true).
unfold Fresh in H.
apply andb_prop in H. inversion H.
unfold nodup_ilist in H1.
unfold count_occur in H1.
repeat rewrite <- beq_nat_refl in H1.
assert(H3: beq_nat n1 n = false).
Fr_pf.

assert(H4: beq_nat n2 n = false).
Fr_pf.
assert(H5: beq_nat n3 n =false).
Fr_pf.
assert(H6: beq_nat n4 n = false).
Fr_pf.
assert(H7: beq_nat n2 n1 = false).
Fr_pf.
assert(H8 : beq_nat n3 n1 = false).
Fr_pf.
assert(H9: beq_nat n4 n1 = false).
Fr_pf.
assert(H10: beq_nat n3 n2 = false).
Fr_pf.
assert(H11 : beq_nat n4 n2 = false).
Fr_pf.
assert(H12 : beq_nat n4 n3 = false).
Fr_pf.
(**************************************************************************************)
(*************************************************************************************)

unfold Fresh. unfold nodup_ilist. unfold count_occur. repeat rewrite <- beq_nat_refl.
 rewrite H3; rewrite H4;rewrite H6. simpl. rewrite H7;rewrite H9; rewrite H11.
simpl. reflexivity.

apply DDH in H2.
pose proof(FRESHIND).

assert (Frccl:  (clos_mylist ( [msg (G n); msg (g n);
        msg (exp (G n) (g n) (r n1));
        msg (exp (G n) (g n) (r n2));
        msg
          (exp (G n)
             (exp (G n) (g n) (r n1))
             (r n2))] ++ [msg (G n); msg (g n);
       msg (exp (G n) (g n) (r n1));
       msg (exp (G n) (g n) (r n2));
       msg (exp (G n) (g n) (r n4))])= true) /\ (Fresh [n3] ([msg (G n); msg (g n);
        msg (exp (G n) (g n) (r n1));
        msg (exp (G n) (g n) (r n2));
        msg
          (exp (G n)
             (exp (G n) (g n) (r n1))
             (r n2))] ++ [msg (G n); msg (g n);
       msg (exp (G n) (g n) (r n1));
       msg (exp (G n) (g n) (r n2));
       msg (exp (G n) (g n) (r n4))])= true) /\ (Fresh [n3]( [msg (G n); msg (g n);
        msg (exp (G n) (g n) (r n1));
        msg (exp (G n) (g n) (r n2));
        msg
          (exp (G n)
             (exp (G n) (g n) (r n1))
             (r n2))] ++ [msg (G n); msg (g n);
       msg (exp (G n) (g n) (r n1));
       msg (exp (G n) (g n) (r n2));
       msg (exp (G n) (g n) (r n4))])= true) /\ ( [msg (G n); msg (g n);
      msg (exp (G n) (g n) (r n1));
      msg (exp (G n) (g n) (r n2));
      msg
        (exp (G n)
           (exp (G n) (g n) (r n1)) (r n2))] ~
     [msg (G n); msg (g n);
     msg (exp (G n) (g n) (r n1));
     msg (exp (G n) (g n) (r n2));
     msg (exp (G n) (g n) (r n4))])).
repeat (split); repeat (reflexivity).
simpl.

unfold Fresh in H.
apply andb_prop in H. inversion H.
unfold nodup_ilist in H3.
unfold count_occur in H3.
repeat rewrite <- beq_nat_refl in H3.
assert(H5: beq_nat n1 n = false).
Fr_pf.

assert(H6: beq_nat n2 n = false).
Fr_pf.
assert(H7: beq_nat n3 n =false).
Fr_pf.
assert(H8: beq_nat n4 n = false).
Fr_pf.
assert(H9: beq_nat n2 n1 = false).
Fr_pf.
assert(H10 : beq_nat n3 n1 = false).
Fr_pf.
assert(H11: beq_nat n4 n1 = false).

Fr_pf.
assert(H12: beq_nat n3 n2 = false).
Fr_pf.

assert(H13 : beq_nat n4 n2 = false).
Fr_pf.

assert(H14 : beq_nat n4 n3 = false).
Fr_pf.
(*******************************************************)

(*******************************************************)

SearchAbout beq_nat.
rewrite Nat.eqb_sym. rewrite H7. rewrite Nat.eqb_sym. rewrite H10.  rewrite Nat.eqb_sym. rewrite H12. rewrite H14. reflexivity.

simpl.
unfold Fresh in H.
apply andb_prop in H. inversion H.
unfold nodup_ilist in H3.
unfold count_occur in H3.
repeat rewrite <- beq_nat_refl in H3.
assert(H5: beq_nat n1 n = false).
Fr_pf.
assert(H6: beq_nat n2 n = false).
Fr_pf.
assert(H7: beq_nat n3 n =false).
Fr_pf.
assert(H8: beq_nat n4 n = false).
Fr_pf.
assert(H9: beq_nat n2 n1 = false).
Fr_pf.
assert(H10 : beq_nat n3 n1 = false).
Fr_pf.
assert(H11: beq_nat n4 n1 = false).
Fr_pf.
assert(H12: beq_nat n3 n2 = false).
Fr_pf.

assert(H13 : beq_nat n4 n2 = false).
Fr_pf.

assert(H14 : beq_nat n4 n3 = false).
Fr_pf.

rewrite Nat.eqb_sym.
rewrite H7. rewrite Nat.eqb_sym. rewrite H10. rewrite Nat.eqb_sym. rewrite H12. rewrite H14.  simpl.
reflexivity. apply H2.
clear H1.

assert(H4: ((msg (r n3)) +++ [msg (G n); msg (g n);
        msg (exp (G n) (g n) (r n1));
        msg (exp (G n) (g n) (r n2));
        msg
          (exp (G n)
             (exp (G n) (g n) (r n1))
             (r n2))]) ~
       ( (msg (r n3)) +++ [msg (G n); msg (g n);
       msg (exp (G n) (g n) (r n1));
       msg (exp (G n) (g n) (r n2));
       msg (exp (G n) (g n) (r n4))])).

apply FRESHIND with (n1:=n3) (n2:=n3) in Frccl ; apply Frccl.

pose proof(FUNCApp_expatpos).

simpl in H4.
apply FUNCApp_expatpos with (p1:= 2)(p2:= 3) (p3:= 1) in H4.
unfold exp_at_pos in H4.
simpl in H4.

restr_swap_in 6 7 H4.

apply FUNCApp_att4 with (p1:=3) (p2:=4) (p3:=5) (p4:= 6) (fmb4 := fmb4) in H4.
unfold getelt_at_pos in H4. simpl in H4.
restr_swap_in 7 8 H4.

assert(DDHbc: Fresh[n;n2;n3;n4] [] = true).

clear H1.

unfold Fresh in H.
apply andb_prop in H. inversion H.
unfold nodup_ilist in H1.
unfold count_occur in H1.
repeat rewrite <- beq_nat_refl in H1.
assert(H5: beq_nat n1 n = false).
Fr_pf.
assert(H6: beq_nat n2 n = false).
Fr_pf.
assert(H7: beq_nat n3 n =false).
Fr_pf.
assert(H8: beq_nat n4 n = false).
Fr_pf.
assert(H9: beq_nat n2 n1 = false).
Fr_pf.
assert(H10 : beq_nat n3 n1 = false).
Fr_pf.
assert(H11: beq_nat n4 n1 = false).

Fr_pf.
assert(H12: beq_nat n3 n2 = false).
Fr_pf.

assert(H13 : beq_nat n4 n2 = false).
Fr_pf.

assert(H14 : beq_nat n4 n3 = false).
Fr_pf.
unfold Fresh. simpl. repeat rewrite <- beq_nat_refl.
rewrite Nat.eqb_sym.
rewrite H6.  rewrite Nat.eqb_sym. rewrite H7. rewrite H12. rewrite H8.  simpl. rewrite H13. simpl.
rewrite H14. simpl.
reflexivity.

apply DDH in DDHbc.
assert (Fracl:  (clos_mylist ( [msg (G n); msg (g n);
        msg (exp (G n) (g n) (r n2));
        msg (exp (G n) (g n) (r n3));
        msg
          (exp (G n)
             (exp (G n) (g n) (r n2))
             (r n3))] ++ [msg (G n); msg (g n);
       msg (exp (G n) (g n) (r n2));
       msg (exp (G n) (g n) (r n3));
       msg (exp (G n) (g n) (r n4))])= true) /\ (Fresh [n1] ([msg (G n); msg (g n);
        msg (exp (G n) (g n) (r n2));
        msg (exp (G n) (g n) (r n3));
        msg
          (exp (G n)
             (exp (G n) (g n) (r n2))
             (r n3))] ++ [msg (G n); msg (g n);
       msg (exp (G n) (g n) (r n2));
       msg (exp (G n) (g n) (r n3));
       msg (exp (G n) (g n) (r n4))])= true) /\ (Fresh [n1]( [msg (G n); msg (g n);
        msg (exp (G n) (g n) (r n2));
        msg (exp (G n) (g n) (r n3));
        msg
          (exp (G n)
             (exp (G n) (g n) (r n2))
             (r n3))] ++ [msg (G n); msg (g n);
       msg (exp (G n) (g n) (r n2));
       msg (exp (G n) (g n) (r n3));
       msg (exp (G n) (g n) (r n4))])= true) /\ ( [msg (G n); msg (g n);
      msg (exp (G n) (g n) (r n2));
      msg (exp (G n) (g n) (r n3));
      msg
        (exp (G n)
           (exp (G n) (g n) (r n2)) (r n3))] ~
     [msg (G n); msg (g n);
     msg (exp (G n) (g n) (r n2));
     msg (exp (G n) (g n) (r n3));
     msg (exp (G n) (g n) (r n4))])).

repeat (try split); repeat (try reflexivity). simpl. clear H1.

unfold Fresh in H.
apply andb_prop in H. inversion H.
unfold nodup_ilist in H1.
unfold count_occur in H1.
repeat rewrite <- beq_nat_refl in H1.
assert(H5: beq_nat n1 n = false).
Fr_pf.
assert(H6: beq_nat n2 n = false).
Fr_pf.
assert(H7: beq_nat n3 n =false).
Fr_pf.
assert(H8: beq_nat n4 n = false).
Fr_pf.
assert(H9: beq_nat n2 n1 = false).
Fr_pf.
assert(H10 : beq_nat n3 n1 = false).
Fr_pf.
assert(H11: beq_nat n4 n1 = false).
Fr_pf.
assert(H12: beq_nat n3 n2 = false).
Fr_pf.

assert(H13 : beq_nat n4 n2 = false).
Fr_pf.

assert(H14 : beq_nat n4 n3 = false).
Fr_pf.
unfold Fresh. simpl. repeat rewrite <- beq_nat_refl.
rewrite Nat.eqb_sym.
rewrite H5.   rewrite H9. rewrite H10. rewrite H11.
reflexivity.  clear H1.
simpl.

unfold Fresh in H.
apply andb_prop in H. inversion H.
unfold nodup_ilist in H1.
unfold count_occur in H1.
repeat rewrite <- beq_nat_refl in H1.
assert(H5: beq_nat n1 n = false).
Fr_pf.
assert(H6: beq_nat n2 n = false).
Fr_pf.
assert(H7: beq_nat n3 n =false).
Fr_pf.
assert(H8: beq_nat n4 n = false).
Fr_pf.
assert(H9: beq_nat n2 n1 = false).
Fr_pf.
assert(H10 : beq_nat n3 n1 = false).
Fr_pf.
assert(H11: beq_nat n4 n1 = false).
Fr_pf.
assert(H12: beq_nat n3 n2 = false).
Fr_pf.
assert(H13 : beq_nat n4 n2 = false).
Fr_pf.

assert(H14 : beq_nat n4 n3 = false).
Fr_pf.
rewrite Nat.eqb_sym.
rewrite H5.   rewrite H9. rewrite H10. rewrite H11.
reflexivity.  clear H1.
apply DDHbc.

assert(H6: ((msg (r n1)) +++ [msg (G n); msg (g n);
        msg (exp (G n) (g n) (r n2));
        msg (exp (G n) (g n) (r n3));
        msg
          (exp (G n)
             (exp (G n) (g n) (r n2))
             (r n3))]) ~
       ( (msg (r n1)) +++ [msg (G n); msg (g n);
       msg (exp (G n) (g n) (r n2));
       msg (exp (G n) (g n) (r n3));
       msg (exp (G n) (g n) (r n4))])).

apply FRESHIND with (n1:=n1) (n2:=n1) in Fracl; apply Fracl.
simpl in H6.

apply FUNCApp_expatpos with (p1:= 2)(p2:= 3) (p3:= 1) in H6.
unfold exp_at_pos in H6.
simpl in H6.

restr_swap_in 6 7 H6.
restr_swap_in 5 6 H6.

restr_swap_in 4 5 H6.

apply FUNCApp_att4 with (p1:=3) (p2:=4) (p3:=5) (p4:= 6) (fmb4:= fmb4) in H6.
unfold getelt_at_pos in H6. simpl in H6.
restr_swap_in 7 8 H6.

pose proof(IFBRANCH_M).
assert (ifbr: ([msg (G n); msg (g n); msg (exp (G n) (g n) (r n1));
 msg (exp (G n) (g n) (r n2));
 msg (exp (G n) (g n) (r n3))] ++[ bol
       (fmb4 (g n) (exp (G n) (g n) (r n1))
          (exp (G n) (g n) (r n2))
          (exp (G n) (g n) (r n3))); msg
   (if_then_else_M (fmb4 (g n) (exp (G n) (g n) (r n1))
         (exp (G n) (g n) (r n2))
         (exp (G n) (g n) (r n3)))
      (exp (G n) (exp (G n) (g n) (r n1)) (r n2))
      (exp (G n) (exp (G n) (g n) (r n2)) (r n3)))]) ~
([msg (G n); msg (g n); msg (exp (G n) (g n) (r n1));
msg (exp (G n) (g n) (r n2));
msg (exp (G n) (g n) (r n3))] ++
[bol (fmb4 (g n) (exp (G n) (g n) (r n1))
          (exp (G n) (g n) (r n2))
          (exp (G n) (g n) (r n3))); msg (if_then_else_M (fmb4 (g n) (exp (G n) (g n) (r n1))
         (exp (G n) (g n) (r n2))
         (exp (G n) (g n) (r n3)))
       (exp (G n) (g n) (r n4))
      (exp (G n) (g n) (r n4)))])).

apply IFBRANCH_M with (n:=5) (ml1:= [msg (G n); msg (g n); msg (exp (G n) (g n) (r n1));
 msg (exp (G n) (g n) (r n2));
 msg (exp (G n) (g n) (r n3))]) (ml2:= [msg (G n); msg (g n); msg (exp (G n) (g n) (r n1));
 msg (exp (G n) (g n) (r n2));
 msg (exp (G n) (g n) (r n3))])  (b:= (fmb4 (g n) (exp (G n) (g n) (r n1))
      (exp (G n) (g n) (r n2)) (exp (G n) (g n) (r n3)))) (b':= (fmb4 (g n) (exp (G n) (g n) (r n1))
      (exp (G n) (g n) (r n2)) (exp (G n) (g n) (r n3))))
 (x :=  (exp (G n) (exp (G n) (g n) (r n1)) (r n2))) (x':= (exp (G n) (g n) (r n4)))
(y:=  (exp (G n) (exp (G n) (g n) (r n2)) (r n3))) (y':= (exp (G n) (g n) (r n4))).

simpl.
dropone_in H4. apply H4.
dropone_in H6. apply H6.

simpl in ifbr.
rewrite IFSAME_M with (b:= (fmb4 (g n) (exp (G n) (g n) (r n1))
            (exp (G n) (g n) (r n2))
            (exp (G n) (g n) (r n3)))) (x:=  (exp (G n) (g n) (r n4))) in ifbr.
restr_proj_in 6 ifbr.

apply ifbr.
Qed.


Require Import QuickChick.

Class liftable (A B : Type) :=
  {
    lift_m : A -> B
  }.

Global
Instance lift0 {A} : liftable (G A) (G A) :=
  {
    lift_m := id
  }.

Global
Instance liftN {A B R} `(liftable (G B) R) : liftable (G (A -> B)) (G A -> R):=
   {
     lift_m f ga :=
       lift_m (liftGen2 id f ga)
   }.

Definition liftM {A B R} `{liftable (G B) R} (f : A -> B) (g : G A) : R :=
  lift_m (fmap f g).

Definition ex1 : G nat := liftM (fun x => x + 3) (returnGen 0).
Definition ex2 : G nat := liftM (fun x y => x + y) (returnGen 0) (returnGen 1).
Definition ex3 : G nat := liftM (fun x y z => x + y + z)
                                (returnGen 0) (returnGen 1) (returnGen 2).

(*
Eval cbv -[plus] in ex1.
(* = fmap (fun x : nat => x + 3) (returnGen 0) -- fair enough *)

Eval cbv -[plus] in ex2.
(* = liftGen2 id (fmap (fun x y : nat => x + y) (returnGen 0)) (returnGen 1)
where
fmap : (nat -> (nat -> nat)) -> G nat -> G (nat -> nat)
liftGen2 : ((nat -> nat) -> nat -> nat) ->
            (G (nat -> nat)) -> G nat -> G nat
 *)

Eval cbv -[plus] in ex3.
(* = liftGen2 (fun x : nat -> nat => x)
         (liftGen2 (fun x : nat -> nat -> nat => x)
            (fmap (fun x y z : nat => x + y + z) (returnGen 0))
            (returnGen 1)) (returnGen 2)
*)

(* this is not well typed ... wtf? *)
Check (liftM (fun x y => x + y + y) (returnGen 0) (returnGen 1) (returnGen 2)
  : G nat).

(* it's even worse ... all kinds of stuff are accepted in Check and Eval *)
Eval simpl in (liftM (fun x => x + 1) (returnGen 0) 0 0 : G nat).
*)
(*
liftM nat nat (nat -> nat -> G nat) `{liftable (G nat) (nat -> nat -> G nat)} ...
-- but we don't have such an instance!
We need to use definitions to get a type error
Definition xxx := (liftM (fun x => x + 1) (returnGen 0) 0 0 : G nat).
Toplevel input, characters 19-24:
Error: Cannot infer the implicit parameter H of
liftM.
Could not find an instance for "liftable (G nat) (nat -> nat -> G nat)".
*)


(* Clean-slate look at subtyping relation based on *)
(* singleton types (single env) *)

(* TODO: get rid of all admits
   Rewriting semantics
   Soundness proof
   Type checker / examples
*)

Require Export SfLib.

Require Export Arith.EqNat.
Require Export Arith.Lt.

Module STLC.

Definition id := nat.

Inductive ty : Type :=
  | TBot   : ty
  | TTop   : ty
  | TBool  : ty
  | TFun   : ty -> ty -> ty
  | TVar   : bool -> id -> ty
  | TVarB  : id -> ty
  | TMem   : ty -> ty -> ty (* intro *)
  | TSel   : ty -> ty (* elim *)
  | TBind  : ty -> ty
.

Inductive vl : Type :=
  | vbool : bool -> vl
  | vabs  : ty -> ty -> vl
  | vty   : ty -> vl
.

Definition venv := list vl.
Definition tenv := list ty.

Hint Unfold venv.
Hint Unfold tenv.

Fixpoint index {X : Type} (n : id) (l : list X) : option X :=
  match l with
    | [] => None
    | a :: l'  => if beq_nat n (length l') then Some a else index n l'
  end.

(* closed i j k means normal variables < i and < j, bound variables < k *)
Inductive closed: nat -> nat -> nat -> ty -> Prop :=
| cl_bot: forall i j k,
    closed i j k TBot
| cl_top: forall i j k,
    closed i j k TTop
| cl_bool: forall i j k,
    closed i j k TBool
| cl_fun: forall i j k T1 T2,
    closed i j k T1 ->
    closed i j k T2 ->
    closed i j k (TFun T1 T2)
| cl_var0: forall i j k x,
    i > x ->
    closed i j k (TVar false x)
| cl_var1: forall i j k x,
    j > x ->
    closed i j k (TVar true x)
| cl_varB: forall i j k x,
    k > x ->
    closed i j k (TVarB x)
| cl_mem: forall i j k T1 T2,
    closed i j k T1 ->
    closed i j k T2 ->
    closed i j k (TMem T1 T2)
| cl_sel: forall i j k T1,
    closed i j k T1 ->
    closed i j k (TSel T1)
| cl_bind: forall i j k T1,
    closed i j (S k) T1 ->
    closed i j k (TBind T1)
.

Fixpoint open (k: nat) (u: ty) (T: ty) { struct T }: ty :=
  match T with
    | TVar b x => TVar b x (* free var remains free. functional, so we can't check for conflict *)
    | TVarB x => if beq_nat k x then u else TVarB x
    | TTop        => TTop
    | TBot        => TBot
    | TBool       => TBool
    | TSel T1     => TSel (open k u T1)
    | TFun T1 T2  => TFun (open k u T1) (open k u T2)
    | TMem T1 T2  => TMem (open k u T1) (open k u T2)
    | TBind T1    => TBind (open (S k) u T1)
  end.

Fixpoint subst (U : ty) (T : ty) {struct T} : ty :=
  match T with
    | TTop         => TTop
    | TBot         => TBot
    | TBool        => TBool
    | TMem T1 T2   => TMem (subst U T1) (subst U T2)
    | TSel T1      => TSel (subst U T1)
    | TVarB i      => TVarB i
    | TVar true i  => TVar true i
    | TVar false i => if beq_nat i 0 then U else TVar false (i-1)
    | TFun T1 T2   => TFun (subst U T1) (subst U T2)
    | TBind T2     => TBind (subst U T2)
  end.

(*
Fixpoint nosubst (T : ty) {struct T} : Prop :=
  match T with
    | TTop         => True
    | TBot         => True
    | TBool        => True
    | TMem m T1 T2   => nosubst T1 /\ nosubst T2
    | TSel (varB i) m => True
    | TSel (varF i) m => True
    | TSel (varH i) m => i <> 0
    | TAll m T1 T2   => nosubst T1 /\ nosubst T2
    | TBind T2     => nosubst T2
    | TAnd T1 T2   => nosubst T1 /\ nosubst T2
    | TOr  T1 T2   => nosubst T1 /\ nosubst T2
  end.
*)

Inductive stp: tenv -> ty -> ty -> Prop :=
| stp_bot: forall G1 T,
    closed (length G1) 0 0  T ->
    stp G1 TBot T
| stp_top: forall G1 T,
    closed (length G1) 0 0 T ->
    stp G1 T TTop
| stp_bool: forall G1,
    stp G1 TBool TBool
| stp_fun: forall G1 T1 T2 T3 T4,
    stp G1 T3 T1 ->
    stp G1 T2 T4 ->
    stp G1 (TFun T1 T2) (TFun T3 T4)
| stp_mem: forall G1 T1 T2 T3 T4,
    stp G1 T3 T1 ->
    stp G1 T2 T4 ->
    stp G1 (TMem T1 T2) (TMem T3 T4)
| stp_varx: forall G1 x,
    x < length G1 ->
    stp G1 (TVar false x) (TVar false x)
(* | stp_vary: forall G1 x y,
    index x G1 = Some (TVar false y) ->
    y < length G1 ->
    stp G1 (TVar false y) (TVar false x) *)
| stp_var1: forall G1 x T1,
    index x G1 = Some T1 ->
    closed (length G1) 0 0 T1 ->
    stp G1 (TVar false x) T1
| stp_sel1: forall G1 T2 b x,
    stp G1 (TVar b x) (TMem TBot T2) ->
    stp G1 (TSel (TVar b x)) T2
| stp_sel2: forall G1 T1 b x,
    stp G1 (TVar b x) (TMem T1 TTop) ->
    stp G1 T1 (TSel (TVar b x))

.

Inductive stp2: nat -> bool -> tenv -> venv -> ty -> ty -> nat -> Prop :=
| stp2_bot: forall m GH G1 T n1,
    closed (length GH) (length G1) 0  T ->
    stp2 m true GH G1 TBot T (S n1)
| stp2_top: forall m GH G1 T n1,
    closed (length GH) (length G1) 0 T ->
    stp2 m true GH G1 T  TTop (S n1)
| stp2_bool: forall m GH G1 n1,
    stp2 m true GH G1 TBool TBool (S n1)
| stp2_fun: forall m GH G1 T1 T2 T3 T4 n1 n2,
    stp2 m false GH G1 T3 T1 n1 ->
    stp2 m false GH G1 T2 T4 n2 ->
    stp2 m true GH G1 (TFun T1 T2) (TFun T3 T4) (S (n1+n2))
| stp2_mem: forall m GH G1 T1 T2 T3 T4 n1 n2,
    stp2 m false GH G1 T3 T1 n2 ->
    stp2 m false GH G1 T2 T4 n1 ->
    stp2 m true GH G1 (TMem T1 T2) (TMem T3 T4) (S (n1+n2))

| stp2_varx: forall m GH G1 x n1,
    x < length G1 ->
    stp2 m true GH G1 (TVar true x) (TVar true x) (S n1)
| stp2_var1: forall m m2 GH G1 x T2 n1,
    vtp m2 G1 x T2 n1 -> (* slack for: val_type G2 v T2 *)
    stp2 m true GH G1 (TVar true x) T2 (S n1)

(* not sure if we should have these 2 or not ... ? *)
(*
| stp2_varb1: forall m GH G1 T2 x n1,
    stp2 m true GH G1 (TVar true x) (TBind T2) n1 ->
    stp2 m true GH G1 (TVar true x) (open 0 (TVar false x) T2) (S n1)

| stp2_varb2: forall m GH G1 T2 x n1,
    stp2 m true GH G1 (TVar true x) (open 0 (TVar false x) T2) n1 ->
    stp2 m true GH G1 (TVar true x) (TBind T2) (S n1)
*)

| stp2_varax: forall m GH G1 x n1,
    x < length GH ->
    stp2 m true GH G1 (TVar false x) (TVar false x) (S n1)
(*
| stp2_varay: forall m GH G1 TX x1 x2 n1,
    index x1 GH = Some TX ->
    stp2 m false GH G1 TX (TVar false x2) n1 ->
    stp2 m true GH G1 (TVar false x2) (TVar false x1) (S n1)
| stp2_vary: forall m GH G1 x1 x2 n1,
    index x1 GH = Some (TVar true x2) ->
    x2 < length G1 ->
    stp2 m true GH G1 (TVar true x2) (TVar false x1) (S n1)
*)
| stp2_vara1: forall m GH G1 T2 x n1,
    htp  m false GH G1 x T2 n1 -> (* TEMP -- SHOULD ALLOW UP TO x *)
    stp2 m true GH G1 (TVar false x) T2 (S n1)
(*
| stp2_varab1: forall m GH G1 T2 x n1,
    stp2 m true GH G1 (TVar false x) (TBind T2) n1 ->
    stp2 m true GH G1 (TVar false x) (open 0 (TVar false x) T2) (S n1)

| stp2_varab2: forall m GH G1 T2 x n1,
    stp2 m true GH G1 (TVar false x) (open 0 (TVar false x) T2) n1 ->
    stp2 (S m) true GH G1 (TVar false x) (TBind T2) (S n1)
*)

| stp2_strong_sel1: forall m GH G1 T2 TX x n1,
    index x G1 = Some (vty TX) ->
    stp2 m false [] G1 TX T2 n1 ->
    stp2 m true GH G1 (TSel (TVar true x)) T2 (S n1)
| stp2_strong_sel2: forall m GH G1 T1 TX x n1,
    index x G1 = Some (vty TX) ->
    stp2 m false [] G1 T1 TX n1 ->
    stp2 m true GH G1 T1 (TSel (TVar true x)) (S n1)

| stp2_sel1: forall m GH G1 T2 n1,
    htp  m false GH G1 0 (TMem TBot T2) n1 ->
    stp2 m true GH G1 (TSel (TVar false 0)) T2 (S n1)

| stp2_sel2: forall m GH G1 T1 n1,
    htp  m false GH G1 0 (TMem T1 TTop) n1 ->
    stp2 m true GH G1 T1 (TSel (TVar false 0)) (S n1)

| stp2_bind1: forall m GH G1 T1 T1' T2 n1,
    htp m false (T1'::GH) G1 (length GH) T2 n1 ->
    T1' = (open 0 (TVar false (length GH)) T1) ->
    closed (length GH) (length G1) 1 T1 ->
    closed (length GH) (length G1) 0 T2 ->
    stp2 m true GH G1 (TBind T1) T2 (S n1)

| stp2_bindx: forall m GH G1 T1 T1' T2 T2' n1,
    htp m false (T1'::GH) G1 (length GH) T2' n1 ->
    T1' = (open 0 (TVar false (length GH)) T1) ->
    T2' = (open 0 (TVar false (length GH)) T2) ->
    closed (length GH) (length G1) 1 T1 ->
    closed (length GH) (length G1) 1 T2 ->
    stp2 m true GH G1 (TBind T1) (TBind T2) (S n1)

| stp2_wrapf: forall m GH G1 T1 T2 n1,
    stp2 m true GH G1 T1 T2 n1 ->
    stp2 m false GH G1 T1 T2 (S n1)
| stp2_transf: forall m GH G1 T1 T2 T3 n1 n2 m2 m3,
    stp2 m false GH G1 T1 T2 n1 ->
    stp2 m2 false GH G1 T2 T3 n2 ->
    stp2 m3 false GH G1 T1 T3 (S (n1+n2))

with wf_env : venv -> tenv -> Prop :=
| wfe_nil : wf_env nil nil
| wfe_cons : forall v t vs ts,
    val_type (v::vs) v t ->
    wf_env vs ts ->
    wf_env (cons v vs) (cons t ts)

with val_type0 : venv -> vl -> ty -> Prop :=
| v_bool: forall venv b,
    val_type0 venv (vbool b) TBool
| v_abs: forall venv T1 T2,
    val_type0 venv (vabs T1 T2) (TFun T1 T2)
| v_ty: forall venv T1,
    val_type0 venv (vty T1) (TMem T1 T1)

with val_type : venv -> vl -> ty -> Prop :=
| v_sub: forall G1 T1 T2 v,
    val_type0 G1 v T1 ->
    (exists n, stp2 0 true [] G1 T1 T2 n) ->
    val_type G1 v T2

with htp: nat -> bool -> tenv -> venv -> nat -> ty -> nat -> Prop :=
| htp_var: forall m b GH G1 x TX n1,
    (* can we assign (TVar false x) ? probably not ... *)
    index x GH = Some TX ->
    htp m b GH G1 x TX (S n1)
| htp_bind: forall m b GH G1 x TX n1,
    (* is it needed given stp2_bind1? for the moment, yes ...*)
    htp m b GH G1 x (TBind TX) n1 ->
    closed x (length G1) 1 TX ->
    htp m b GH G1 x (open 0 (TVar false x) TX) (S n1)
| htp_sub: forall m b GH GU GL G1 x T1 T2 n1 n2,
    htp m b GH G1 x T1 n1 ->
    stp2 m b GL G1 T1 T2 n2 ->
    length GL = S x ->
    GH = GU ++ GL -> (* NOTE: restriction to GL means we need trans in sel rules *)
    htp m b GH G1 x T2 (S (n1+n2))

with vtp : nat -> venv -> nat -> ty -> nat -> Prop :=
| vtp_top: forall m G1 x n1,
    x < length G1 ->
    vtp m G1 x TTop (S n1)
| vtp_bool: forall m G1 x b n1,
    index x G1 = Some (vbool b) ->
    vtp m G1 x (TBool) (S (n1))
| vtp_mem: forall m G1 x TX T1 T2 ms n1 n2,
    index x G1 = Some (vty TX) ->
    stp2 ms false [] G1 T1 TX n1 ->
    stp2 ms false [] G1 TX T2 n2 ->
    vtp m G1 x (TMem T1 T2) (S (n1+n2))
| vtp_bind: forall m G1 x T2 n1,
    vtp m G1 x (open 0 (TVar true x) T2) n1 ->
    vtp (S m) G1 x (TBind T2) (S (n1))
| vtp_sel: forall m G1 x y TX n1,
    index y G1 = Some (vty TX) ->
    vtp m G1 x TX n1 ->
    vtp m G1 x (TSel (TVar true y)) (S (n1))

with vtp2 : venv -> nat -> ty -> nat -> Prop :=
| vtp2_refl: forall G1 x n1,
    x < length G1 ->
    vtp2 G1 x (TVar true x) (S n1)
| vtp2_down: forall m G1 x T n1,
    vtp m G1 x T n1 ->
    vtp2 G1 x T (S n1)
| vtp2_sel: forall G1 x y TX n1,
    index y G1 = Some (vty TX) ->
    vtp2 G1 x TX n1 ->
    vtp2 G1 x (TSel (TVar true y)) (S (n1)).

Definition stpd2 m b GH G1 T1 T2 := exists n, stp2 m b GH G1 T1 T2 n.

Definition htpd m b GH G1 x T1 := exists n, htp m b GH G1 x T1 n.

Definition vtpd m G1 x T1 := exists n, vtp m G1 x T1 n.
Definition vtpd2 G1 x T1 := exists n, vtp2 G1 x T1 n.

Definition vtpdd m G1 x T1 := exists m1 n, vtp m1 G1 x T1 n /\ m1 <= m.

Hint Constructors stp2.
Hint Constructors vtp.

Ltac ep := match goal with
             | [ |- stp2 ?M ?B ?GH ?G1 ?T1 ?T2 ?N ] => assert (exists (n:nat), stp2 M B GH G1 T1 T2 n) as EEX
           end.

Ltac eu := match goal with
             | H: stpd2 _ _ _ _ _ _ |- _ => destruct H as [? H]
             | H: htpd _ _ _ _ _ _ |- _ => destruct H as [? H]
             | H: vtpd _ _ _ _ |- _ => destruct H as [? H]
             | H: vtpd2 _ _ _ |- _ => destruct H as [? H]
             | H: vtpdd _ _ _ _ |- _ => destruct H as [? [? [H ?]]]
           end.

Lemma stpd2_bot: forall m GH G1 T,
    closed (length GH) (length G1) 0 T ->
    stpd2 m true GH G1 TBot T.
Proof. intros. exists 1. eauto. Qed.
Lemma stpd2_top: forall m GH G1 T,
    closed (length GH) (length G1) 0 T ->
    stpd2 m true GH G1 T TTop.
Proof. intros. exists 1. eauto. Qed.
Lemma stpd2_bool: forall m GH G1,
    stpd2 m true GH G1 TBool TBool.
Proof. intros. exists 1. eauto. Qed.
Lemma stpd2_fun: forall m GH G1 T1 T2 T3 T4,
    stpd2 m false GH G1 T3 T1 ->
    stpd2 m false GH G1 T2 T4 ->
    stpd2 m true  GH G1 (TFun T1 T2) (TFun T3 T4).
Proof. intros. repeat eu. eexists. eauto. Qed.
Lemma stpd2_mem: forall m GH G1 T1 T2 T3 T4,
    stpd2 m false GH G1 T3 T1 ->
    stpd2 m false GH G1 T2 T4 ->
    stpd2 m true  GH G1 (TMem T1 T2) (TMem T3 T4).
Proof. intros. repeat eu. eexists. eauto. Qed.

(*
Lemma stpd2_varx: forall m GH G1 x,
    x < length G1 ->
    stpd2 m true GH G1 (TVar true x) (TVar true x).
Proof. intros. repeat eu. exists 1. eauto. Qed.
Lemma stpd2_var1: forall m GH G1 TX x T2 v,
    index x G1 = Some v ->
    val_type0 G1 v TX -> (* slack for: val_type G2 v T2 *)
    stpd2 m true GH G1 TX T2 ->
    stpd2 m true GH G1 (TVar true x) T2.
Proof. intros. repeat eu. eexists. eauto. Qed.
(*Lemma stpd2_var1b: forall m GH G1 GX TX G2 x x2 T2 v,
    index x G1 = Some v ->
    index x2 G2 = Some v ->
    val_type0 GX v TX -> (* slack for: val_type G2 v T2 *)
    stpd2 m true GH GX TX G2 (open 0 (TVar true x2) T2) ->
    stpd2 m true GH G1 (TVar true x) G2 (TBind T2).
Proof. intros. repeat eu. eexists. eauto. Qed.*)
Lemma stpd2_sel: forall m GH G1 T1 T2,
    stpd2 m false GH G1 T2 T1 ->
    stpd2 m true  GH G1 T1 T2 ->
    stpd2 m true  GH G1 (TSel T1) (TSel T2).
Proof. intros. repeat eu. eexists. eauto. Qed.
Lemma stpd2_red: forall m GH G1 T1 T2,
    stpd2 m true GH G1 T1 (TMem TBot T2) ->
    stpd2 m true GH G1 (TSel T1) T2.
Proof. intros. repeat eu. eexists. eauto. Qed.
Lemma stpd2_red2: forall m GH G1 TX T1 T2 T0,
    real T0 ->
    stpd2 m true GH G1 T0 T2 ->
    stpd2 m false GH G1 T2 (TMem TX TTop) ->
    stpd2 m true GH G1 T0 (TMem TX TTop) ->
    stpd2 m false GH G1 T1 TX ->
    stpd2 m true GH G1 T1 (TSel T2).
Proof. intros. repeat eu. eexists. eauto. Qed.

(*
Lemma stpd2_bindI: forall G1 G2 T2 x,
    stpd2 true G1 (TVar x) G2 (open 0 (TVar x) T2) ->
    stpd2 true G1 (TVar x) G2 (TBind T2).
Proof. intros. repeat eu. eexists. eauto. Qed. *

Lemma stpd2_bindE: forall G1 G2 T2 x,
    stpd2 true G1 (TVar x) G2 (TBind T2) ->
    stpd2 true G1 (TVar x) G2 (open 0 (TVar x) T2).
Proof. intros. repeat eu. eexists. eauto. Qed.
*)

(*Lemma stpd2_bind1: forall m GH G1 T1 T2,
    closed (length GH) (length G2) 0 T2 ->
    stpd2 m true ((G1,(open 0 (TVar false (length GH)) T1))::GH)
          G1 (TVar false (length GH)) G2 T2 ->
    stpd2 m true GH G1 (TBind T1) G2 T2.
Proof. intros. repeat eu. eexists. eauto. Qed.
*)

*)

Lemma stpd2_wrapf: forall m GH G1 T1 T2,
    stpd2 m true GH G1 T1 T2 ->
    stpd2 m false GH G1 T1 T2.
Proof. intros. repeat eu. eexists. eauto. Qed.
Lemma stpd2_transf: forall m GH G1 T1 T2 T3,
    stpd2 m false GH G1 T1 T2 ->
    stpd2 m true GH G1 T2 T3 ->
    stpd2 m false GH G1 T1 T3.
Proof. intros. repeat eu. eexists. eauto. Qed.

Hint Constructors ty.
Hint Constructors vl.

Hint Constructors stp2.
Hint Constructors val_type.
Hint Constructors wf_env.

Hint Unfold stpd2.

Hint Constructors option.
Hint Constructors list.

Hint Unfold index.
Hint Unfold length.

Hint Resolve ex_intro.

Ltac ev := repeat match goal with
                    | H: exists _, _ |- _ => destruct H
                    | H: _ /\  _ |- _ => destruct H
           end.

Lemma wf_length : forall vs ts,
                    wf_env vs ts ->
                    (length vs = length ts).
Proof.
  intros. induction H. auto.
  assert ((length (v::vs)) = 1 + length vs). constructor.
  assert ((length (t::ts)) = 1 + length ts). constructor.
  rewrite IHwf_env in H1. auto.
Qed.

Hint Immediate wf_length.

Lemma index_max : forall X vs n (T: X),
                       index n vs = Some T ->
                       n < length vs.
Proof.
  intros X vs. induction vs.
  Case "nil". intros. inversion H.
  Case "cons".
  intros. inversion H.
  case_eq (beq_nat n (length vs)); intros E.
  SCase "hit".
  rewrite E in H1. inversion H1. subst.
  eapply beq_nat_true in E.
  unfold length. unfold length in E. rewrite E. eauto.
  SCase "miss".
  rewrite E in H1.
  assert (n < length vs). eapply IHvs. apply H1.
  compute. eauto.
Qed.

Lemma index_exists : forall X vs n,
                       n < length vs ->
                       exists (T:X), index n vs = Some T.
Proof.
  intros X vs. induction vs.
  Case "nil". intros. inversion H.
  Case "cons".
  intros. inversion H.
  SCase "hit".
  assert (beq_nat n (length vs) = true) as E. eapply beq_nat_true_iff. eauto.
  simpl. subst n. rewrite E. eauto.
  SCase "miss".
  assert (beq_nat n (length vs) = false) as E. eapply beq_nat_false_iff. omega.
  simpl. rewrite E. eapply IHvs. eauto.
Qed.

Lemma index_extend : forall X vs n a (T: X),
                       index n vs = Some T ->
                       index n (a::vs) = Some T.

Proof.
  intros.
  assert (n < length vs). eapply index_max. eauto.
  assert (n <> length vs). omega.
  assert (beq_nat n (length vs) = false) as E. eapply beq_nat_false_iff; eauto.
  unfold index. unfold index in H. rewrite H. rewrite E. reflexivity.
Qed.

Lemma closed_extend : forall T X (a:X) i k G,
                       closed i (length G) k T ->
                       closed i (length (a::G)) k T.
Proof.
  intros T. induction T; intros; inversion H;  econstructor; eauto.
  simpl. omega.
Qed.

(*
Lemma stp2_extend : forall m b GH  v1 G1 G2 T1 T2 n,
                      stp2 m b GH G1 T1 G2 T2 n ->
                      stp2 m b GH (v1::G1) T1 G2 T2 n /\
                      stp2 m b GH G1 T1 (v1::G2) T2 n /\
                      stp2 m b GH (v1::G1) T1 (v1::G2) T2 n.
Proof.
  intros. induction H; try solve [repeat split; econstructor; try eauto;
          try eapply index_extend; eauto; try eapply closed_extend; eauto;
          try eapply IHstp2; eauto;
          try eapply IHstp2_1; try eapply IHstp2_2;
            try eapply IHstp2_3; try eapply IHstp2_4].
(*
  repeat split; eapply stp2_var1b. eapply index_extend; eauto. eauto. eauto.
  eauto. eauto. eapply index_extend; eauto. eauto. eapply IHstp2. eapply index_extend; eauto.
  eapply index_extend; eauto. eauto.
  eapply IHstp2.
*)
  admit. (* bind1 *)
  admit.
  admit.

  repeat split; eapply stp2_transf; try eapply IHstp2_1; eauto; try eapply IHstp2_2; eauto.
Qed.

Lemma stpd2_extend : forall m b GH v1 G1 G2 T1 T2,
                      stpd2 m b GH G1 T1 G2 T2 ->
                      stpd2 m b GH (v1::G1) T1 G2 T2 /\
                      stpd2 m b GH G1 T1 (v1::G2) T2 /\
                      stpd2 m b GH (v1::G1) T1 (v1::G2) T2.
Proof.
  intros. repeat eu. repeat split; eexists; eapply stp2_extend; eauto.
Qed.

Lemma stp2_extend1 : forall m b GH v1 G1 G2 T1 T2 n, stp2 m b GH G1 T1 G2 T2 n -> stp2 m b GH (v1::G1) T1 G2 T2 n.
Proof. intros. eapply stp2_extend. eauto. Qed.

Lemma stp2_extend2 : forall m b GH v1 G1 G2 T1 T2 n, stp2 m b GH G1 T1 G2 T2 n -> stp2 m b GH G1 T1 (v1::G2) T2 n.
Proof. intros. eapply stp2_extend. eauto. Qed.

Lemma stpd2_extend1 : forall m b GH v1 G1 G2 T1 T2, stpd2 m b GH G1 T1 G2 T2 -> stpd2 m b GH (v1::G1) T1 G2 T2.
Proof. intros. eapply stpd2_extend. eauto. Qed.

Lemma stpd2_extend2 : forall m b GH v1 G1 G2 T1 T2, stpd2 m b GH G1 T1 G2 T2 -> stpd2 m b GH G1 T1 (v1::G2) T2.
Proof. intros. eapply stpd2_extend. eauto. Qed.
*)

Lemma stp_closed : forall G1 T1 T2,
                      stp G1 T1 T2 ->
                      closed (length G1) 0 0 T1 /\
                      closed (length G1) 0 0 T2.
Proof.
 admit. (*   intros. induction H; repeat split; try  econstructor; try eapply IHstp1; try eapply IHstp2; eauto; try eapply IHstp; eauto; try eapply index_max; eauto.
  destruct IHstp. inversion H1. eauto.
    destruct IHstp. inversion H1. eauto. *)

Qed.

Lemma stpd2_closed : forall m b GH G1 T1 T2,
                      stpd2 m b GH G1 T1 T2 ->
                      closed (length GH) (length G1) 0 T1 /\
                      closed (length GH) (length G1) 0 T2.
Proof.
  admit. (*
  intros. eu. induction H; repeat split; try  econstructor; try eapply IHstp2_1; try eapply IHstp2_2; eauto; try eapply IHstp2; eauto; try eapply index_max; eauto.
  destruct IHstp2. inversion H1. eauto.
  eapply IHstp2_4. *)

Qed.

Lemma stpd2_closed1 : forall m b GH G1 T1 T2,
                      stpd2 m b GH G1 T1 T2 ->
                      closed (length GH) (length G1) 0 T1.
Proof. intros. eapply (stpd2_closed m b GH G1 T1 T2); eauto. Qed.

Lemma stp2_closed2 : forall m b GH G1 T1 T2 n1,
                      stp2 m b GH G1 T1 T2 n1 ->
                      closed (length GH) (length G1) 0 T2.
Proof. intros. eapply (stpd2_closed m b GH G1); eauto. Qed.

Lemma stpd2_closed2 : forall m b GH G1 T1 T2,
                      stpd2 m b GH G1 T1 T2 ->
                      closed (length GH) (length G1) 0 T2.
Proof. intros. eapply (stpd2_closed m b GH G1); eauto. Qed.

Lemma valtp_extend : forall vs v v1 T,
                       val_type vs v T ->
                       val_type (v1::vs) v T.
Proof.
  admit.
  (*intros. induction H; econstructor; eauto; try eapply stpd2_extend; eauto; try eapply index_extend; eauto.
   *)
Qed.

Lemma index_safe_ex: forall H1 G1 TF i,
             wf_env H1 G1 ->
             index i G1 = Some TF ->
             exists v, index i H1 = Some v /\ val_type H1 v TF.
Proof. intros. induction H.
       Case "nil". inversion H0.
       Case "cons". inversion H0.
         case_eq (beq_nat i (length ts)).
           SCase "hit".
             intros E.
             rewrite E in H3. inversion H3. subst t.
             assert (beq_nat i (length vs) = true). eauto.
             assert (index i (v :: vs) = Some v). eauto.  unfold index. rewrite H2. eauto.
             eauto.
           SCase "miss".
             intros E.
             assert (beq_nat i (length vs) = false). eauto.
             rewrite E in H3.
             assert (exists v0, index i vs = Some v0 /\ val_type vs v0 TF) as HI. eapply IHwf_env. eauto.
           inversion HI as [v0 HI1]. inversion HI1.
           eexists. econstructor. eapply index_extend; eauto. eapply valtp_extend; eauto.
Qed.

Lemma stpd2_refl: forall m GH G1 T1,
  closed (length GH) (length G1) 0 T1 ->
  stpd2 m true GH G1 T1 T1.
Proof.
admit. (*  intros. induction T1; inversion H.
  - Case "bot". exists 1. eauto.
  - Case "top". exists 1. eauto.
  - Case "bool". eapply stpd2_bool; eauto.
  - Case "fun". eapply stpd2_fun; try eapply stpd2_wrapf; eauto.
  - Case "var0". exists 1. eauto.
  - Case "var1".
    assert (exists v, index i G1 = Some v) as E. eapply index_exists; eauto.
    destruct E.
    eapply stpd2_varx; eauto.
  - Case "varb". inversion H4.
  - Case "mem". eapply stpd2_mem; try eapply stpd2_wrapf; eauto.
  - Case "sel". eapply stpd2_sel; try eapply stpd2_wrapf; eauto.
  - Case "bind".
    admit.

    (* don't have index & val_type0 evidence *)

    (*exists 3. eapply stp2_bind1. eauto.
    eapply stp2_bind2.
    simpl. eauto.

    eapply stp2_vara1. simpl. rewrite <-beq_nat_refl. eauto. *)

    (* TODO:
       stp2 m true ((G1, open 0 (TVar false (length GH)) T1) :: GH) G1
     (open 0 (TVar false (length GH)) T1) G1
     (open 0 (TVar false (length GH)) T1) 0
     *)*)
Qed.

Lemma stpd2_reg1 : forall m b GH G1 T1 T2,
                      stpd2 m b GH G1 T1 T2 ->
                      stpd2 m true GH G1 T1 T1.
Proof.
  intros. eapply stpd2_refl. eapply (stpd2_closed m b GH G1 T1 T2). eauto.
Qed.

Lemma stpd2_reg2 : forall m b GH G1 T1 T2,
                      stpd2 m b GH G1 T1 T2 ->
                      stpd2 m true GH G1 T2 T2.
Proof.
  intros. eapply stpd2_refl. eapply (stpd2_closed m b GH G1). eauto.
Qed.

(*

Lemma invert_bind1: forall n, forall venv vf T1 GX TX n1,
  val_type0 GX vf TX -> stp2 true GX TX venv (TBind T1) n1 -> n1 < n ->
  exists x n2,
    index x venv = Some vf ->
    n2 < n1 ->
    stp2 true GX TX venv (open 0 (TVar x) T1) n2.
Proof.
  intros n. induction n; intros. solve by inversion.
  inversion H; subst.
  - Case "bool". solve by inversion.
  - Case "fun". solve by inversion.
(*   - Case "var". subst. inversion H0; subst.
    + SCase "normal".
    assert (vf = v) as A. rewrite H2 in H4. inversion H4. eauto.
    rewrite A. assert (n0 < n) as B. omega.
    specialize (IHn venv0 v T1 GX0 TX n0 H5 H6 B).
    ev. repeat eexists; eauto.
  (* repeat eapply IHn; eauto. omega. *)
    + SCase "bindE". eauto.
    + eauto.      *)
  - Case "mem". solve by inversion.
Qed.
*)

Lemma stp2_downgrade: forall m m2 b GH G1 T1 T2 n1,
  stp2 m b GH G1 T1 T2 n1 -> m <= m2 ->
  stp2 m2 b GH G1 T1 T2 n1.
Proof.
  admit.
Qed.

(*
Lemma stpd2_trans_axiom_aux: forall n, forall m m2 GH G1 T1 T2 T3 n1,
  stpd2 m false GH G1 T1 T2 ->
  stp2 m2 false GH G1 T2 T3 n1 -> n1 < n -> m2 <= m ->
  stpd2 m2 false GH G1 T1 T3.
Proof.
  intros n. induction n; intros; try omega; repeat eu; subst; inversion H0; clear H0; subst.
  - Case "wrapf". eexists. eapply stp2_transf. eauto. eauto. eauto.
  - Case "transf".
    assert (m = m2 + (m - m2)). omega.
    assert (stpd2 m false GH G1 T1 T4). eapply IHn. eauto. rewrite H0. eapply stp2_downgrade. eauto. eapply stp2_downgrade. eauto. omega. eauto.
    eu. eexists. eapply stp2_transf. eauto. eauto. omega.
Qed.

Lemma stp2_trans_axiom: forall m b GH G1 T1 T2 T3,
  stpd2 m false GH G1 T1 T2 ->
  stpd2 m b GH G1 T2 T3 ->
  stpd2 m false GH G1 T1 T3.
Proof.
  intros. destruct b; eu; eu; eapply stpd2_trans_axiom_aux; eauto.
Qed.
*)

Ltac index_subst := match goal with
                      | H1: index ?x ?G = ?V1 , H2: index ?x ?G = ?V2 |- _ => rewrite H1 in H2; inversion H2; subst
                      | _ => idtac
                    end.

Ltac invty := match goal with
                | H1: TBot     = _ |- _ => inversion H1
                | H1: TBool    = _ |- _ => inversion H1
                | H1: TSel _   = _ |- _ => inversion H1
                | H1: TMem _ _ = _ |- _ => inversion H1
                | H1: TVar _ _ = _ |- _ => inversion H1
                | H1: TFun _ _ = _ |- _ => inversion H1
                | H1: TBind  _ = _ |- _ => inversion H1
                | _ => idtac
              end.

Ltac invstp_var := match goal with
  | H1: stp2 _ true _ _ TBot        (TVar _ _) _ |- _ => inversion H1
  | H1: stp2 _ true _ _ TTop        (TVar _ _) _ |- _ => inversion H1
  | H1: stp2 _ true _ _ TBool       (TVar _ _) _ |- _ => inversion H1
  | H1: stp2 _ true _ _ (TFun _ _)  (TVar _ _) _ |- _ => inversion H1
  | H1: stp2 _ true _ _ (TMem _ _)  (TVar _ _) _ |- _ => inversion H1
  | H1: val_type0 _ _ _ |- _ => inversion H1
  | _ => idtac
end.

Lemma closed_no_subst: forall T i j k TX,
   closed i j k T ->
   subst TX T = T.
Proof.
  admit.
(*  intros T. induction T; intros; inversion H; simpl; eauto;
    try rewrite (IHT (S j) TX); eauto;
    try rewrite (IHT2 (S j) TX); eauto;
    try rewrite (IHT j TX); eauto;
    try rewrite (IHT1 j TX); eauto;
    try rewrite (IHT2 j TX); eauto.

  eapply closed_upgrade. eauto. eauto.
  subst. omega.
  subst. eapply closed_upgrade. eassumption. omega.
  subst. eapply closed_upgrade. eassumption. omega. *)
Qed.

(*
Lemma subst_open_commute: forall i j n V l T2, closed i (n+1) (j+1) (n+1) T2 -> closed 0 0 (TSel V l) ->
    subst V (open_rec j (varH (n+1)) T2) = open_rec j (varH n) (subst V T2).
Proof.
  intros. eapply subst_open_commute_m; eauto.
Qed.
*)

(* FIXME: need some closed evidence, but don't worry about it for now *)

Lemma subst_open_commute: forall T0 TX,
  (subst TX (open 0 (TVar false 0) T0)) = open 0 TX T0.
Proof. admit. Qed.

Lemma subst_open_commute1: forall T0 x x0,
 (open 0 (TVar true x0) (subst (TVar true x) T0))
 = (subst (TVar true x) (open 0 (TVar true x0) T0)).
Proof. admit. Qed.

Definition substt x T := (subst (TVar true x) T).

Hint Immediate substt.

Lemma closed_subst: forall i j k x T2,
  closed (i + 1) j k T2 ->
  closed i j k (substt x T2).
Proof. admit. Qed.

Lemma index_subst: forall GH TX T0 T3 x,
  index (length (GH ++ [TX])) (T0 :: GH ++ [TX]) = Some T3 ->
  index (length GH) (map (substt x) (T0 :: GH)) = Some (substt x T3).
Proof. admit. Qed.

Lemma index_hit0: forall (GH:tenv) TX T2,
 index 0 (GH ++ [TX]) = Some T2 -> T2 = TX.
Proof. admit. Qed.

Lemma subst_open3: forall (GH:tenv) TX TX0 x,
  (substt x (open 0 (TVar false (length (GH ++ [TX]))) TX0)) =
  (open 0 (TVar false (length GH)) (substt x TX0)).
Proof. admit. Qed.

Lemma subst_open4: forall (GH:tenv) TX T0 x,
  substt x (open 0 (TVar false (length (GH ++ [TX]))) T0) =
  open 0 (TVar false (length (map (substt x) GH))) (substt x T0).
Proof. admit. Qed.

Lemma gh_match: forall (GH:tenv) GU GL TX T0,
  T0 :: GH ++ [TX] = GU ++ GL ->
  length GL = S (length (GH ++ [TX])) ->
  GU = [] /\ GL = T0 :: GH ++ [TX].
Proof. admit. Qed.

Lemma subst_closed_id: forall x j k T2,
  closed 0 j k T2 ->
  substt x T2 = T2.
Proof. admit. Qed.

Lemma vtp_closed: forall m G1 x T2 n1,
  vtp m G1 x T2 n1 ->
  closed 0 (length G1) 0 T2.
Proof. admit. Qed.

Lemma vtp_closed1: forall m G1 x T2 n1,
  vtp m G1 x T2 n1 ->
  x < length G1.
Proof. admit. Qed.

Lemma vtp2_closed1: forall G1 x T2 n1,
  vtp2 G1 x T2 n1 ->
  x < length G1.
Proof. admit. Qed.

Lemma beq_nat_true_eq: forall A, beq_nat A A = true.
Proof. intros. eapply beq_nat_true_iff. eauto. Qed.

Lemma sub_env1: forall (GL:tenv) GU GH TX,
  GH ++ [TX] = GU ++ GL ->
  length GL = 1 ->
  GL = [TX].
Proof. admit. Qed.

(* NOT SO EASY ... *) (*
It seems like we'd need to have z:z.type, which is problematic
Lemma htp_bind_admissible: forall m GH G1 x TX n1, (* is it needed given stp2_bind1? *)
    htp m true GH G1 x (TBind TX) n1 ->
    htpd m true GH G1 x (open 0 (TVar false x) TX).
Proof.
  intros.
  assert (closed x (length G1) 1 TX). admit.
  assert (GL: tenv). admit.
  assert (length GL = S x). admit.

  eexists. eapply htp_sub. eapply H. eapply stp2_bind1.
  eapply htp_sub. eapply htp_var. simpl. rewrite beq_nat_true_eq. eauto.

Qed.
*)

Lemma stp2_subst_narrow0: forall n, forall m2 b GH G1 T1 T2 TX x n2,
   stp2 m2 b (GH++[TX]) G1 T1 T2 n2 -> x < length G1 -> n2 < n ->
   (forall (m1 : nat) GH (T3 : ty) (n1 : nat),
      htp m1 false (GH++[TX]) G1 0 T3 n1 -> n1 < n ->
      exists m2, vtpd m2 G1 x (substt x T3)) ->
   stpd2 m2 b (map (substt x) GH) G1 (substt x T1) (substt x T2).
Proof.
  intros n. induction n. intros. omega.
  intros ? ? ? ? ? ? ? ? ? ? ? ? narrowX.

  (* helper lemma for htp *)
    assert (forall ni n2, forall m T0 T2,
      htp m false (T0 :: GH ++ [TX]) G1 (length (GH ++ [TX])) T2 n2 -> n2 < ni -> ni < S n ->
      htpd m false (map (substt x) (T0::GH)) G1 (length GH) (substt x T2)) as htp_subst_narrow0. {
      induction ni. intros. omega.
      intros. inversion H2.
      + (* var *) subst. repeat eexists. eapply htp_var. eapply index_subst. eauto.
      + (* bind *) subst.
        assert (htpd m false
         (map (substt x) (T0 :: GH)) G1
         (length (GH)) (substt x (TBind TX0))) as BB.
        eapply IHni. eapply H5. omega. omega.
        rewrite subst_open3.
        eu. repeat eexists. eapply htp_bind. eauto. eapply closed_subst. rewrite app_length in H6. eauto.
      + (* sub *) subst.
        assert (GU = [] /\ GL = T0 :: GH ++ [TX]) as A. eapply gh_match; eauto.
        destruct A. subst GL. subst GU.

        assert (htpd m false
                     (map (substt x) (T0 :: GH)) G1
                     (length GH) (substt x T4)) as AA.
        eapply IHni. eauto. omega. omega.
        assert (stpd2 m false (map (substt x)  ( T0 :: GH)) G1 (substt x T4) (substt x T3)) as BB.
        eapply IHn. eauto. eauto. omega. { intros. eapply narrowX. eauto. eauto. }
        eu. eu. repeat eexists. eapply htp_sub. eauto. eauto.
        (* - *)
        simpl. rewrite map_length. eauto. instantiate (1:=[]). simpl. eauto.
    }

                                                                                assert (forall ni n2, forall m GH T2 xi,
      htp m false (GH ++ [TX]) G1 xi T2 n2 -> n2 < ni -> ni < S n ->
      htpd m false (map (substt x) GH) G1 (xi-1) (substt x T2)) as htp_subst_narrow02. {
      admit. (* TODO: generalize narrow0 above *)
    }

(* main logic *)
  inversion H.
  - Case "bot". subst.
    eapply stpd2_bot; eauto. rewrite map_length. simpl. eapply closed_subst. rewrite app_length in H2. simpl in H2. eapply H2.
  - Case "top". subst.
    eapply stpd2_top; eauto. rewrite map_length. simpl. eapply closed_subst. rewrite app_length in H2. simpl in H2. eapply H2.
  - Case "bool". subst.
    eapply stpd2_bool; eauto.
  - Case "fun". subst.
    eapply stpd2_fun. eapply IHn; eauto. omega. eapply IHn; eauto. omega.
  - Case "mem". subst.
    eapply stpd2_mem. eapply IHn; eauto. omega. eapply IHn; eauto. omega.

  - Case "varx". subst.
    eexists. eapply stp2_varx. eauto.
  - Case "var1". subst.
    assert (substt x T2 = T2) as R. eapply subst_closed_id. eapply vtp_closed. eauto.
    eexists. eapply stp2_var1. rewrite R. eauto.
  - Case "varax". subst.
    case_eq (beq_nat x0 0); intros E.
    + (* hit *)
      assert (x0 = 0). eapply beq_nat_true_iff. eauto.
      repeat eexists. unfold substt. subst x0. simpl. eapply stp2_varx. eauto.
    + (* miss *)
      assert (x0 <> 0). eapply beq_nat_false_iff. eauto.
      repeat eexists. unfold substt. simpl. rewrite E. eapply stp2_varax. rewrite map_length. rewrite app_length in H2. simpl in H2. omega.
  - Case "vara1".
    case_eq (beq_nat x0 0); intros E.
    + (* hit *)
      assert (x0 = 0). eapply beq_nat_true_iff. eauto. subst x0.
      assert (exists m0, vtpd m0 G1 x (substt x T2)). subst. eapply narrowX; eauto. omega.
      ev. eu. subst. repeat eexists. simpl. eapply stp2_var1. eauto.
    + (* miss *)
      assert (x0 <> 0). eapply beq_nat_false_iff. eauto.
      subst.
      assert (htpd m2 false (map (substt x) GH) G1 (x0-1) (substt x T2)). eapply htp_subst_narrow02. eauto. eauto. eauto.
      eu. repeat eexists. unfold substt at 2. simpl. rewrite E. eapply stp2_vara1. eauto.
(*
  - Case "varab1".
    case_eq (beq_nat x0 0); intros E.
    + (* hit *)
      assert (x0 = 0). eapply beq_nat_true_iff; eauto.
      assert (exists m0, vtpd m0 G1 x (substt x (TBind T0))). subst. eapply H1; eauto. omega.
      assert ((subst 0 (TVar true x) T0) = T0) as R. admit. (* closed! *)
      ev. eu. inversion H11. rewrite R in H16. clear R. subst.
      repeat eexists. eapply stp2_var1. unfold substt. rewrite subst_open_commute. eauto.
    + (* miss *)
      assert (stpd2 m2 true (map (substt x) GH) G1 (substt x (TVar false x0))
         (substt x (TBind T0))). eapply IHn; eauto. omega.
      assert (substt x (TBind T0) = TBind (substt x T0)) as R1. admit.
      assert (substt x (open 0 (TVar false x0) T0) = open 0 (TVar false x0) (substt x T0)) as R2. admit.
      assert (substt x (TVar false x0) = (TVar false x0)) as R3. admit.
      rewrite R2. rewrite R3. eu. repeat eexists. eapply stp2_varab1. rewrite <-R3. rewrite <-R1. eauto.
  - Case "varab2".
    case_eq (beq_nat x0 0); intros E.
    + (* hit *)
      assert (x0 = 0). eapply beq_nat_true_iff; eauto. subst x0.
      assert (exists m0, vtpd m0 G1 x (substt x (open 0 (TVar false 0) T0))). subst. eapply H1; eauto. omega.
      assert ((subst 0 (TVar true x) T0) = T0) as R. admit. (* closed! *)
      ev. eu. unfold substt in H10. rewrite subst_open_commute in H10.
      repeat eexists. unfold substt. simpl. eapply stp2_var1. eapply vtp_bind. eauto.
      rewrite R. eauto.
    + (* miss *)
      assert (stpd2 m true (map (substt x) GH) G1 (substt x (TVar false x0))
         (substt x (open 0 (TVar false x0) T0))). eapply IHn; eauto. omega.
      assert (substt x (TBind T0) = TBind (substt x T0)) as R1. admit.
      assert (substt x (open 0 (TVar false x0) T0) = open 0 (TVar false x0) (substt x T0)) as R2. admit.
      assert (substt x (TVar false x0) = (TVar false x0)) as R3. admit.
      rewrite R3. rewrite R1. eu. repeat eexists. eapply stp2_varab2.
      rewrite R3 in H10. rewrite R2 in H10. eauto.
*)

  - Case "ssel1". subst.
    assert (substt x T2 = T2) as R. eapply subst_closed_id. eapply stpd2_closed2 with (GH:=[]). eauto.
    eexists. eapply stp2_strong_sel1. eauto. rewrite R. eauto.

  - Case "ssel2". subst.
    assert (substt x T1 = T1) as R. eapply subst_closed_id. eapply stpd2_closed1 with (GH:=[]). eauto.
    eexists. eapply stp2_strong_sel2. eauto. rewrite R. eauto.

  - Case "sel1". subst. (* invert htp to vtp and create strong_sel node *)
    assert (exists m0, vtpd m0 G1 x (substt x (TMem TBot T2))) as A. eapply narrowX. eauto. omega.
    destruct A as [? A]. eu. inversion A. subst.
    repeat eexists. eapply stp2_strong_sel1. eauto. unfold substt.
    instantiate (1:=n2). admit. (* FIXME: m2 vs ms *)

  - Case "sel2". subst. (* invert htp to vtp and create strong_sel node *)
    assert (exists m0, vtpd m0 G1 x (substt x (TMem T1 TTop))) as A. eapply narrowX. eauto. omega.
    destruct A as [? A]. eu. inversion A. subst.
    repeat eexists. eapply stp2_strong_sel2. eauto. unfold substt.
    instantiate (1:=n2). admit. (* FIXME: m2 vs ms *)

   - Case "bind1".
    assert (htpd m2 false (map (substt x) (T1'::GH)) G1 (length GH) (substt x T2)).
    eapply htp_subst_narrow0. eauto. eauto. omega.
    eu. repeat eexists. eapply stp2_bind1. rewrite map_length. eapply H13.
    simpl. subst T1'. fold subst. eapply subst_open4.
    fold subst. eapply closed_subst. rewrite app_length in H4. simpl in H4. rewrite map_length. eauto.
    eapply closed_subst. rewrite map_length. rewrite app_length in H5. simpl in H5. eauto.

  - Case "bindx".
    assert (htpd m2 false (map (substt x) (T1'::GH)) G1 (length GH) (substt x T2')).
    eapply htp_subst_narrow0. eauto. eauto. omega.
    eu. repeat eexists. eapply stp2_bindx. rewrite map_length. eapply H14.
    subst T1'. fold subst. eapply subst_open4.
    subst T2'. fold subst. eapply subst_open4.
    rewrite app_length in H5. simpl in H5. eapply closed_subst. rewrite map_length. eauto.
    rewrite app_length in H6. simpl in H6. eapply closed_subst. rewrite map_length. eauto.

  - Case "wrapf".
    assert (stpd2 m2 true (map (substt x) GH) G1 (substt x T1) (substt x T2)).
    eapply IHn; eauto. omega.
    eu. repeat eexists. eapply stp2_wrapf. eauto.
  - Case "transf".
    assert (stpd2 m false (map (substt x) GH) G1 (substt x T1) (substt x T3)).
    eapply IHn; eauto. omega.
    assert (stpd2 m0 false (map (substt x) GH) G1 (substt x T3) (substt x T2)).
    eapply IHn; eauto. omega.
    eu. eu. repeat eexists. eapply stp2_transf. eauto. eauto.

Grab Existential Variables.
apply 0. apply 0. apply 0. apply 0.
Qed.

Lemma stp2_subst_narrowX: forall ml, forall nl, forall m b m2 GH G1 T2 TX x n1 n2,
   vtp m G1 x (substt x TX) n1 ->
   htp m2 b (GH++[TX]) G1 0 T2 n2 -> x < length G1 -> m < ml -> n2 < nl ->
   (forall (m0 m1 : nat) (b : bool) (G1 : venv) x (T2 T3 : ty) (n1 n2 : nat),
        vtp m0 G1 x T2 n1 ->
        stp2 m1 b [] G1 T2 T3 n2 -> m0 <= m ->
        vtpdd m0 G1 x T3) ->
   vtpdd m G1 x (substt x T2). (* decrease b/c transitivity *)
Proof.
  intros ml. (* induction ml. intros. omega. *)
  intros nl. induction nl. intros. omega.
  intros.
  inversion H0.
  - Case "var". subst.
    assert (T2 = TX). eapply index_hit0. eauto.
    subst T2.
    repeat eexists. eauto. eauto.
  - Case "bind". subst.
    assert (vtpdd m G1 x (substt x (TBind TX0))) as A.
    eapply IHnl. eauto. eauto. eauto. eauto. omega. eauto.
    destruct A as [? [? [A ?]]]. inversion A. subst.
    repeat eexists. unfold substt. rewrite subst_open_commute.
    assert (closed 0 (length G1) 0 (TBind (substt x TX0))). eapply vtp_closed. unfold substt in A. simpl in A. eapply A.
    assert ((substt x (TX0)) = TX0) as R. eapply subst_closed_id. eauto.
    unfold substt in R. rewrite R in H12. eapply H12. omega.
  - Case "sub". subst.
    assert (GL = [TX]). eapply sub_env1; eauto. subst GL.
    assert (vtpdd m G1 x (substt x T1)) as A.
    eapply IHnl. eauto. eauto. eauto. eauto. omega. eauto.
    eu.
    assert (stpd2 m2 b (map (substt x) []) G1 (substt x T1) (substt x T2)) as B.
    eapply stp2_subst_narrow0. eauto. eauto. eauto. {
      intros. eapply IHnl in H. eu. repeat eexists. eauto. eauto. eauto. eauto. omega. eauto.
    }
    simpl in B. eu.
    assert (vtpdd x0 G1 x (substt x T2)).
    eapply H4. eauto. eauto. eauto.
    eu. repeat eexists. eauto. omega.
Qed.

Lemma stp2_trans: forall l, forall n, forall k, forall m1 m2 b G1 x T2 T3 n1 n2,
  vtp m1 G1 x T2 n1 ->
  stp2 m2 b [] G1 T2 T3 n2 ->
  m1 < l -> n2 < n -> n1 < k ->
  vtpdd m1 G1 x T3.
Proof.
  intros l. induction l. intros. solve by inversion.
  intros n. induction n. intros. solve by inversion.
  intros k. induction k; intros. solve by inversion.
  destruct b.
  (* b = true *) {
  inversion H.
  - Case "top". inversion H0; subst; invty.
    + SCase "top". repeat eexists; eauto.
    + SCase "ssel2".
      assert (vtpdd m1 G1 x TX). eapply IHn; eauto. omega.
      eu. repeat eexists. eapply vtp_sel. eauto. eauto. eauto.
    + SCase "sel2".
      eapply stp2_closed2 in H0. simpl in H0. inversion H0. inversion H9. omega.
  - Case "bool". inversion H0; subst; invty.
    + SCase "top". repeat eexists. eapply vtp_top. eapply index_max. eauto. eauto.
    + SCase "bool". repeat eexists; eauto.
    + SCase "ssel2".
      assert (vtpdd m1 G1 x TX). eapply IHn; eauto. omega.
      eu. repeat eexists. eapply vtp_sel. eauto. eauto. eauto.
    + SCase "sel2".
      eapply stp2_closed2 in H0. simpl in H0. inversion H0. inversion H9. omega.
  - Case "mem". inversion H0; subst; invty.
    + SCase "top". repeat eexists. eapply vtp_top. eapply index_max. eauto. eauto.
    + SCase "mem". invty. subst.
      repeat eexists. eapply vtp_mem. eauto.
      eapply stp2_transf. eauto. eapply H5.
      eapply stp2_transf. eauto. eapply H13.
      eauto.
    + SCase "sel2".
      assert (vtpdd m1 G1 x TX0). eapply IHn; eauto. omega.
      eu. repeat eexists. eapply vtp_sel. eauto. eauto. eauto.
    + SCase "sel2".
      eapply stp2_closed2 in H0. simpl in H0. inversion H0. inversion H11. omega.
  - Case "bind".
    inversion H0; subst; invty.
    + SCase "top". repeat eexists. eapply vtp_top. eapply vtp_closed1. eauto. eauto.
    + SCase "sel2".
      assert (vtpdd (S m) G1 x TX). eapply IHn; eauto. omega.
      eu. repeat eexists. eapply vtp_sel. eauto. eauto. eauto.
    + SCase "sel2".
      eapply stp2_closed2 in H0. simpl in H0. inversion H0. inversion H9. omega.
    + SCase "bind1".
      invty. subst.
      remember (TVar false (length [])) as VZ.
      remember (TVar true x) as VX.

      (* left *)
      assert (vtpd m G1 x (open 0 VX T0)) as LHS. eexists. eassumption.
      eu.
      (* right *)
      assert (substt x (open 0 VZ T0) = (open 0 VX T0)) as R. unfold substt. subst. eapply subst_open_commute.
      assert (substt x T3 = T3) as R1. eapply subst_closed_id. eauto.

      assert (vtpdd m G1 x (substt x T3)) as BB. {
        eapply stp2_subst_narrowX. rewrite <-R in LHS. eapply LHS.
        instantiate (2:=nil). simpl. eapply H10. eapply vtp_closed1. eauto. eauto. eauto.
        { intros. eapply IHl. eauto. eauto. omega. eauto. eauto. }
      }
      rewrite R1 in BB.
      eu. repeat eexists. eauto. omega.
    + SCase "bindx".
      invty. subst.
      remember (TVar false (length [])) as VZ.
      remember (TVar true x) as VX.

      (* left *)
      assert (vtpd m G1 x (open 0 VX T0)) as LHS. eexists. eassumption.
      eu.
      (* right *)
      assert (substt x (open 0 VZ T0) = (open 0 VX T0)) as R. unfold substt. subst. eapply subst_open_commute.

      assert (vtpdd m G1 x (substt x (open 0 VZ T4))) as BB. {
        eapply stp2_subst_narrowX. rewrite <-R in LHS. eapply LHS.
        instantiate (2:=nil). simpl. eapply H10. eapply vtp_closed1. eauto. eauto. eauto.
        { intros. eapply IHl. eauto. eauto. omega. eauto. eauto. }
      }
      unfold substt in BB. subst. rewrite subst_open_commute in BB.
      clear R.
      eu. repeat eexists. eapply vtp_bind. eauto. omega. (* enough slack to add bind back *)
  - Case "ssel2". subst. inversion H0; subst; invty.
    + SCase "top". repeat eexists. eapply vtp_top. eapply vtp_closed1. eauto. eauto.
    + SCase "ssel1". index_subst. eapply IHn. eauto. eauto. eauto. omega. eauto.
    + SCase "ssel2".
      assert (vtpdd m1 G1 x TX0). eapply IHn; eauto. omega.
      eu. repeat eexists. eapply vtp_sel. eauto. eauto. eauto.
    + SCase "sel1".
      assert (closed (length ([]:tenv)) (length G1) 0 (TSel (TVar false 0))).
      eapply stpd2_closed2. eauto.
      simpl in H7. inversion H7. inversion H12. omega.

  }  (* b = false *) {
    inversion H0.

  - Case "wrapf". eapply IHn. eapply H. eauto. eauto. omega. eauto.
  - Case "transf". subst.
    assert (vtpdd m1 G1 x T0) as LHS. eapply IHn. eauto. eauto. eauto. omega. eauto.
    eu.
    assert (vtpdd x0 G1 x T3) as BB. eapply IHn. eapply LHS. eauto. omega. omega. eauto.
    eu. repeat eexists. eauto. omega.
  }

Grab Existential Variables.
apply 0. apply 0. apply 0. apply 0. apply 0.
Qed.

Lemma stp2_trans2: forall n, forall m b G1 T2 T3 x n1 n2,
  vtp2 G1 x T2 n1 ->
  stp2 m b [] G1 T2 T3 n2 -> n2 < n ->
  vtpd2 G1 x T3.
Proof.
  intros n. induction n. intros. omega.
  intros.
  inversion H.
  - Case "self". subst.
    inversion H0.
    + SCase "top". subst.
      eexists. eapply vtp2_down. eapply vtp_top. eauto.
    + SCase "varx". subst.
      eexists. eapply vtp2_refl. eauto.
    + SCase "var1". subst.
      eexists. eapply vtp2_down. eauto.
    + SCase "ssel2". subst.
      assert (vtpd2 G1 x TX). eapply IHn; eauto. omega.
      eu. eexists. eapply vtp2_sel. eauto. eauto.
    + SCase "sel2". subst.
      assert (closed (length ([]:tenv)) (length G1) 0 (TVar false 0)) as CL.
      eapply stpd2_closed1. eauto.
      simpl in CL. inversion CL. omega.
    + SCase "wrapf". subst.
      clear H0. eapply IHn; eauto. omega.
    + SCase "transf". subst.
      assert (vtpd2 G1 x T2). eapply IHn; eauto. omega.
      eu. eapply IHn; eauto. omega.
  - Case "down". subst.
    assert (vtpdd m0 G1 x T3). eapply stp2_trans; eauto.
    eu. eexists. eapply vtp2_down. eauto.
  - Case "sel". subst.
    inversion H0.
    + SCase "top". subst.
      eexists. eapply vtp2_down. eapply vtp_top. eapply vtp2_closed1. eauto.
    + SCase "varx". subst.
      index_subst.
      eapply IHn. eauto. eauto. omega.
    + SCase "ssel1". subst.
      assert (vtpd2 G1 x TX0). eapply IHn. eapply H. eauto. omega.
      eu. eexists. eapply vtp2_sel. eauto. eauto.
    + SCase "sel1". subst.
      assert (closed (length ([]:tenv)) (length G1) 0 (TVar false 0)) as CL.
      eapply stpd2_closed1. eauto.
      simpl in CL. inversion CL. omega.
    + SCase "wrapf". subst.
      clear H0. eapply IHn; eauto. omega.
    + SCase "transf". subst.
      assert (vtpd2 G1 x T2). eapply IHn; eauto. omega.
      eu. eapply IHn; eauto. omega.

Grab Existential Variables.
apply 0. apply 0. apply 0. apply 0. apply 0.
Qed.

(* TODO: stp_to_stp2 *)

(* TODO: specific inversion lemmas *)

(* TODO: evaluation semantics / soundness *)

End STLC.