(* --------------------------------------------------------------------- *)
(* This is the place for "smart constructors" and "smart inversion" lemmas. They are especialy useful for constructing derivations from a particular variable and avoiding the co-finite quantification stuff.  *)
(* In a locally nameless development, these should be stated and immediately proven after substitution. *)

(* Specifications: *)
Lemma An_Pi_exists : forall x G rho A B,
x `notin` dom G \u fv_tm_tm_tm B
→ AnnTyping ([(x, Tm A)] ++ G)
(open_tm_wrt_tm B (a_Var_f x)) a_Star
→ AnnTyping G A a_Star
→ AnnTyping G (a_Pi rho A B) a_Star.
Lemma An_Pi_inversion :
∀ (G:context) rho A B T,
AnnTyping G (a_Pi rho A B) T ->
T = a_Star /\
AnnTyping G A a_Star /\
∀ x, x \notin dom G -> AnnTyping (( x ~ Tm  A) ++ G) (open_tm_wrt_tm B (a_Var_f x)) a_Star.
Lemma An_Abs_exists :
∀ x (G:context) rho (A a B:tm),
x \notin dom G \u fv_tm_tm_tm a \u fv_tm_tm_tm B ->
AnnTyping G A a_Star ->
AnnTyping (( x ~ Tm  A) ++ G)
(open_tm_wrt_tm a (a_Var_f x))
(open_tm_wrt_tm B (a_Var_f x))  ->
RhoCheck rho x (erase_tm (open_tm_wrt_tm a (a_Var_f x))) ->
AnnTyping G (a_Abs rho A a) (a_Pi rho A B).
Lemma fv_erase_tm_other : forall y G A x a,
fv_tm_tm_tm (open_tm_wrt_tm a (a_Var_f y)) [<=] dom ([(y, Tm A)] ++ G) ->
y `notin` {{ x }}  ->
x `notin` dom G ->
x `notin` fv_tm_tm_tm (erase_tm a).
Lemma An_Abs_inversion :
∀ (G:context) rho (a:tm) A A1,
AnnTyping G (a_Abs rho A a) A1 ->
(exists B, A1 = a_Pi rho A B /\
AnnTyping G A a_Star /\
∀ x, x \notin dom G ->
RhoCheck rho x (erase_tm (open_tm_wrt_tm a (a_Var_f x))) /\
AnnTyping (( x ~ Tm  A) ++ G)
(open_tm_wrt_tm a (a_Var_f x))
(open_tm_wrt_tm B (a_Var_f x))).
Lemma An_Abs_impossible :   forall x G rho,
x \notin dom G
-> forall A a, (forall B, not (AnnTyping ((x ~ Tm A) ++ G) (open_tm_wrt_tm a (a_Var_f x)) B))
-> forall A1, not (AnnTyping G (a_Abs rho A a) A1).
Lemma An_CPi_exists :
∀ c (G : context) (phi : constraint) (B : tm),
c \notin dom G \u fv_co_co_tm B ->
AnnPropWff G phi
→ AnnTyping ([(c, Co phi)] ++ G) (open_tm_wrt_co B (g_Var_f c)) a_Star
→ AnnTyping G (a_CPi phi B) a_Star.
Lemma An_CPi_inversion :
∀ (G:context) (phi : constraint) (B T : tm),
AnnTyping G (a_CPi phi B) T ->
T = a_Star /\
AnnPropWff G phi /\
∀ c, c \notin dom G
-> AnnTyping ([(c, Co phi)] ++ G) (open_tm_wrt_co B (g_Var_f c)) a_Star.
Lemma An_CAbs_exists :  ∀ c (G : context) (phi : constraint) (a B : tm),
c \notin dom G \u fv_co_co_tm a \u fv_co_co_tm B
->  AnnPropWff G phi
→ AnnTyping ([(c, Co phi)] ++ G) (open_tm_wrt_co a (g_Var_f c))
(open_tm_wrt_co B (g_Var_f c))
→ AnnTyping G (a_CAbs phi a) (a_CPi phi B).
Lemma An_CAbs_inversion : ∀ (G : context) (phi : constraint) (a A : tm),
AnnTyping G (a_CAbs phi a) A
-> exists B, A = (a_CPi phi B) /\
forall c, c  `notin` dom G
->   AnnPropWff G phi /\
AnnTyping ([(c, Co phi)] ++ G) (open_tm_wrt_co a (g_Var_f c))
(open_tm_wrt_co B (g_Var_f c)).
Lemma An_AbsCong_exists : ∀ x1 x2 (G : context) D rho (g1 g2 : co) (A1 b1 A2 b3 b2 B : tm),
x1 `notin` (dom G \u fv_tm_tm_tm b1 \u fv_tm_tm_tm b2 \u  fv_tm_tm_co g2)
-> x2 `notin` (dom G \u fv_tm_tm_tm b2 \u fv_tm_tm_tm b3 \u  fv_tm_tm_co g1)
->  AnnDefEq G D g1 A1 A2
→ (AnnDefEq ([(x1, Tm A1)] ++ G) D  (open_co_wrt_tm g2 (a_Var_f x1))
(open_tm_wrt_tm b1 (a_Var_f x1)) (open_tm_wrt_tm b2 (a_Var_f x1)))
→ (open_tm_wrt_tm b3 (a_Var_f x2) =
open_tm_wrt_tm b2 (a_Conv (a_Var_f x2) (g_Sym g1)))
→ AnnTyping G A1 a_Star
→ AnnTyping G A2 a_Star
→ RhoCheck rho x1 (erase_tm (open_tm_wrt_tm b1 (a_Var_f x1)))
→ RhoCheck rho x2 (erase_tm (open_tm_wrt_tm b3 (a_Var_f x2)))
-> AnnTyping G (a_Abs rho A1 b2) B
→ AnnDefEq G D (g_AbsCong rho g1 g2) (a_Abs rho A1 b1) (a_Abs rho A2 b3).
Lemma An_AbsCong_inversion :
forall G D rho g1 g2 B1 B2,
AnnDefEq G D (g_AbsCong rho g1 g2) B1 B2 ->
exists A1 A2 b1 b2 b3 B,
B1 = (a_Abs rho A1 b1) /\
B2 = (a_Abs rho A2 b3) /\
AnnTyping G A1 a_Star  /\
AnnTyping G A2 a_Star  /\
AnnDefEq G D g1 A1 A2  /\
AnnTyping G (a_Abs rho A1 b2) B /\
(forall x, x \notin dom G ->
AnnDefEq  ((x ~ Tm A1) ++  G) D (open_co_wrt_tm g2 (a_Var_f x))
(open_tm_wrt_tm b1 (a_Var_f x))
((open_tm_wrt_tm b2 (a_Var_f x))) /\
(open_tm_wrt_tm b3 (a_Var_f x)) =
(open_tm_wrt_tm b2 (a_Conv (a_Var_f x) (g_Sym g1))) /\
(RhoCheck rho x  (erase_tm (open_tm_wrt_tm b1 (a_Var_f x)))) /\
(RhoCheck rho x  (erase_tm (open_tm_wrt_tm b3 (a_Var_f x))))).
Lemma An_CPiCong_exists :  ∀ c1 c2 (G : context) D (g1 g3 : co) (phi1 : constraint)
(B1 : tm) (phi2 : constraint) (B3 B2 : tm),
AnnIso G D g1 phi1 phi2
-> c1 `notin` D \u fv_co_co_tm B2 \u fv_co_co_tm B1 \u fv_co_co_co g3
-> c2 `notin` fv_co_co_co g1 \u fv_co_co_tm B2 \u fv_co_co_tm B3
→ (AnnDefEq ([(c1, Co phi1)] ++ G) D (open_co_wrt_co g3 (g_Var_f c1))
(open_tm_wrt_co B1 (g_Var_f c1)) (open_tm_wrt_co B2 (g_Var_f c1)))
→ (open_tm_wrt_co B3 (g_Var_f c2) =
open_tm_wrt_co B2 (g_Cast (g_Var_f c2) (g_Sym g1)))
→ AnnTyping G (a_CPi phi1 B1) a_Star
→ AnnTyping G (a_CPi phi2 B3) a_Star
-> AnnTyping G (a_CPi phi1 B2) a_Star
→ AnnDefEq G D (g_CPiCong g1 g3) (a_CPi phi1 B1)
(a_CPi phi2 B3).
Lemma An_CPiCong_inversion :  ∀ (G : context) D (g1 g3 : co)
(A1 A2 : tm),
AnnDefEq G D (g_CPiCong g1 g3) A1 A2
-> exists phi1 phi2 B1 B2 B3,
A1 = (a_CPi phi1 B1) /\
A2 = (a_CPi phi2 B3) /\
AnnIso G D g1 phi1 phi2 /\
AnnTyping G (a_CPi phi1 B1) a_Star /\
AnnTyping G (a_CPi phi2 B3) a_Star /\
AnnTyping G (a_CPi phi1 B2) a_Star /\
(forall c, c  `notin` dom G
→ (AnnDefEq ([(c, Co phi1)] ++ G) D (open_co_wrt_co g3 (g_Var_f c))
(open_tm_wrt_co B1 (g_Var_f c)) (open_tm_wrt_co B2 (g_Var_f c))) /\
(open_tm_wrt_co B3 (g_Var_f c) =
open_tm_wrt_co B2 (g_Cast (g_Var_f c) (g_Sym g1)))).
Lemma An_PiCong_exists : forall x1 x2 (G:context) D rho
(g1 g2 : co) (A1 B1 A2 B3 B2 : tm),
x1 `notin` (dom G \u fv_tm_tm_tm B1 \u fv_tm_tm_tm B2 \u  fv_tm_tm_co g2)
-> x2 `notin` (dom G \u fv_tm_tm_tm B2 \u fv_tm_tm_tm B3 \u  fv_tm_tm_co g1)
-> AnnDefEq G D g1 A1 A2
→ AnnDefEq ([(x1, Tm A1)] ++ G) D (open_co_wrt_tm g2 (a_Var_f x1))
(open_tm_wrt_tm B1 (a_Var_f x1)) (open_tm_wrt_tm B2 (a_Var_f x1))
→ (open_tm_wrt_tm B3 (a_Var_f x2) =
open_tm_wrt_tm B2 (a_Conv (a_Var_f x2) (g_Sym g1)))
→ AnnTyping G (a_Pi rho A1 B1) a_Star
→ AnnTyping G (a_Pi rho A2 B3) a_Star
→ AnnTyping G (a_Pi rho A1 B2) a_Star
→ AnnDefEq G D (g_PiCong rho g1 g2) (a_Pi rho A1 B1) (a_Pi rho A2 B3).
Lemma An_PiCong_inversion : forall (G:context) (D:available_props) (rho:relflag) (g1 g2:co) (C1 C2 :tm),
AnnDefEq G D (g_PiCong rho g1 g2) C1 C2
-> exists A1 B1 A2 B2 B3,
C1 = (a_Pi rho A1 B1) /\
C2 = (a_Pi rho A2 B3) /\
AnnTyping G (a_Pi rho A1 B1) a_Star /\
AnnTyping G (a_Pi rho A2 B3) a_Star /\
AnnTyping G (a_Pi rho A1 B2) a_Star /\
AnnDefEq G D g1 A1 A2 /\
(forall x , x \notin dom G  ->
AnnDefEq  (( x ~ Tm  A1 ) ++  G )  D  ( open_co_wrt_tm g2 (a_Var_f x) )   ( open_tm_wrt_tm B1 (a_Var_f x) )   (  (open_tm_wrt_tm  B2   (a_Var_f x) )  )  /\
(open_tm_wrt_tm B3 (a_Var_f x)  = (open_tm_wrt_tm  B2   (a_Conv (a_Var_f x) (g_Sym g1))))).
Lemma An_CAbsCong_exists :
forall c1 c2 (G : context) (D : available_props) (g1 g3 g4 : co)
(phi1 : constraint) (a1 : tm) (phi2 : constraint) (a3 a2 B1 B2 B : tm),
AnnIso G D g1 phi1 phi2
-> c1 `notin` D \u fv_co_co_tm a2 \u fv_co_co_tm a1 \u fv_co_co_co g3
-> c2 `notin` fv_co_co_co g1 \u fv_co_co_tm a2 \u fv_co_co_tm a3
→ (AnnDefEq ([(c1, Co phi1)] ++ G) D (open_co_wrt_co g3 (g_Var_f c1))
(open_tm_wrt_co a1 (g_Var_f c1)) (open_tm_wrt_co a2 (g_Var_f c1)))
→ (open_tm_wrt_co a3 (g_Var_f c2) =
open_tm_wrt_co a2 (g_Cast (g_Var_f c2) (g_Sym g1)))
→ AnnTyping G (a_CAbs phi1 a1) (a_CPi phi1 B1)
→ AnnTyping G (a_CAbs phi2 a3) (a_CPi phi2 B2)
→ AnnDefEq G (dom G) g4 (a_CPi phi1 B1) (a_CPi phi2 B2)
-> AnnTyping G (a_CAbs phi1 a2) B
→ AnnDefEq G D (g_CAbsCong g1 g3 g4) (a_CAbs phi1 a1) (a_CAbs phi2 a3).
Lemma An_CAbsCong_inversion :
forall (G : context) (D : available_props) (g1 g3 g4 : co) A1 A2,
AnnDefEq G D (g_CAbsCong g1 g3 g4) A1 A2
-> exists phi1 phi2 a1 a2 a3 B1 B2 B,
A1 = (a_CAbs phi1 a1) /\
A2 = (a_CAbs phi2 a3) /\
AnnIso G D g1 phi1 phi2 /\
AnnTyping G (a_CAbs phi1 a1) (a_CPi phi1 B1) /\
AnnTyping G (a_CAbs phi2 a3) (a_CPi phi2 B2) /\
AnnTyping G (a_CAbs phi1 a2) B /\
AnnDefEq G (dom G) g4 (a_CPi phi1 B1) (a_CPi phi2 B2) /\
forall c1,
c1`notin` dom G
→ (AnnDefEq ([(c1, Co phi1)] ++ G) D (open_co_wrt_co g3 (g_Var_f c1))
(open_tm_wrt_co a1 (g_Var_f c1)) (open_tm_wrt_co a2 (g_Var_f c1))) /\
(open_tm_wrt_co a3 (g_Var_f c1) =
open_tm_wrt_co a2 (g_Cast (g_Var_f c1) (g_Sym g1))).