Require Import List Lia.
Require Import Undecidability.SystemF.SysF Undecidability.SystemF.Autosubst.syntax.
From Undecidability.SystemF.Util Require Import Facts poly_type_facts typing_facts.
Require Import ssreflect ssrbool ssrfun.

Set Default Goal Selector "!".

Local Arguments nth_error_In {A l n x}.
Local Arguments In_nth_error {A l x}.

Definition iipc2 (Gamma: environment) (t: poly_type) := exists P, typing Gamma P t.
Arguments iipc2 : simpl never.

Lemma iipc2I {Gamma P t} : typing Gamma P t -> iipc2 Gamma t.
Proof. move=> ?. eexists. by eassumption. Qed.

Lemma iipc2_weakening {Gamma Gamma' t} :
  incl Gamma Gamma' -> iipc2 Gamma t -> iipc2 Gamma' t.
Proof. by move=> /incl_nth_error [?] /typing_ren_term' H [?] /H /iipc2I. Qed.

Lemma iipc2_generalization {Gamma Gamma' t} :
  Forall (iipc2 Gamma') Gamma -> iipc2 Gamma t -> iipc2 Gamma' t.
Proof.
  move=> H [P HP]. elim: P Gamma Gamma' t HP H.
  - move=> > /typingE /nth_error_In. rewrite Forall_forall.
    by move=> + H => /H.
  - move=> ? IH1 ? IH2 > /typingE [?] [/IH1{}IH1 /IH2{}IH2].
    move=> /copy [/IH1 [? ?] /IH2 [? ?]]. eexists. apply: typing_app; by eassumption.
  - move=> s > IH Gamma Gamma' > /typingE [?] [->] /IH.
    move=> /(_ (s :: Gamma')) + HG. apply: unnest.
    {
      constructor.
      - exists (var 0). by apply: typing_var.
      - move: HG. apply: Forall_impl. move=> ?.
        apply: iipc2_weakening => ? /=. clear. by firstorder done.
    }
    move=> [? ?]. eexists. apply: typing_abs. by eassumption.
  - move=> ? IH > /typingE [?] [->] /IH{}IH /IH [? ?].
    eexists. apply: typing_ty_app. by eassumption.
  - move=> > IH Gamma Gamma' > /typingE [?] [->] /IH.
    move=> /(_ (map (ren_poly_type S) Gamma')) + HG. apply: unnest.
    {
      rewrite Forall_map. apply: Forall_impl HG.
      move=> ? [Q]. eexists. apply: typing_ren_poly_type. by eassumption.
    }
    move=> [? ?]. eexists. apply: typing_ty_abs. by eassumption.
Qed.

Lemma iipc2_poly_arrE {Gamma s t}: iipc2 Gamma (poly_arr s t) -> iipc2 (s :: Gamma) t.
Proof.
  move=> [?] /(@typing_ren_term' S) => /(_ (s :: Gamma) ltac:(done)).
  move=> /typing_app => /(_ (var 0)) H. eexists. apply: H. by apply: typing_var.
Qed.

Corollary last_poly_var_typingI {t Gamma}:
  (if last_poly_var t is Some x then In (poly_var x) Gamma else False) -> iipc2 Gamma t.
Proof.
  move Hox: (last_poly_var t) => ox Hx. case: ox Hox Hx; last done.
  move=> x. elim: t x Gamma.
  - move=> /= > [->] /In_nth_error [? ?]. eexists. apply: typing_var. by eassumption.
  - move=> ? _ ? IH > /= /IH H /H [P]. move=> /(@typing_ren_term' S) HP. eexists.
    apply: typing_abs. by apply: HP.
  - move=> s + x Gamma /= => /(_ (S x) (map (ren_poly_type S) Gamma)).
    case: (last_poly_var s); last done. move=> [|y]; first done.
    move=> + [?] ?. subst y. rewrite in_map_iff.
    apply: unnest; first done. apply: unnest.
    { eexists. constructor; last by eassumption. done. }
    move=> [?] /typing_ty_abs ?. eexists. by eassumption.
Qed.

Lemma iipc2_poly_arr_comp {Gamma s r t} : iipc2 Gamma (poly_arr s r) -> iipc2 Gamma (poly_arr r t) ->
  iipc2 Gamma (poly_arr s t).
Proof.
  move=> [?] /(@typing_ren_term' S) HP1 [?] /(@typing_ren_term' S) HP2.
  eexists. apply: typing_abs. apply: typing_app; first by apply: HP2.
  apply: typing_app; first by apply: HP1.
  by apply: (typing_var (x := 0)).
Qed.

Lemma Forall2_typing_Forall_iipc2 {Gamma Ps ts} : Forall2 (typing Gamma) Ps ts -> Forall (iipc2 Gamma) ts.
Proof. elim; [by constructor | by move=> > /iipc2I; constructor]. Qed.

Lemma iipc2_ren_poly_type {Gamma t} ξ : iipc2 Gamma t ->
  iipc2 (map (ren_poly_type ξ) Gamma) (ren_poly_type ξ t).
Proof. by move=> [?] /(typing_ren_poly_type ξ) /iipc2I. Qed.

Lemma iipc2_var_InI {Gamma t} : In t Gamma -> iipc2 Gamma t.
Proof. move=> /In_nth_error [?] /typing_var ?. eexists. by eassumption. Qed.

Lemma iipc2_poly_arrI {Gamma s t} : iipc2 (s :: Gamma) t -> iipc2 Gamma (poly_arr s t).
Proof. move=> [?] /typing_abs ?. eexists. by eassumption. Qed.

Lemma iipc2_poly_absI (x: nat) {Gamma t} :
  Forall (fresh_in x) Gamma -> fresh_in (S x) t ->
  iipc2 Gamma (ren_poly_type (scons x id) t) ->
  iipc2 Gamma (poly_abs t).
Proof.
  pose ξ := fun y => if PeanoNat.Nat.eq_dec x y then 0 else S y.
  move=> HG Ht /(iipc2_ren_poly_type ξ).
  have -> : map (ren_poly_type ξ) Gamma = (map (ren_poly_type S) Gamma).
  {
    elim: Gamma HG; first done.
    move=> s Gamma H /Forall_cons_iff [Hx /H{H} /= ->].
    congr cons. apply: ext_ren_poly_type_allfv_poly_type.
    apply: allfv_poly_type_impl Hx => ? ?. rewrite /ξ. by case: (PeanoNat.Nat.eq_dec _ _).
  }
  move=> [?] /typing_ty_abs /iipc2I. congr iipc2. congr poly_abs.
  rewrite poly_type_norm /ξ /= ren_poly_type_allfv_id; last done.
  apply: allfv_poly_type_impl Ht. clear.
  move=> [|y]; case: (PeanoNat.Nat.eq_dec _ _); move=> /=; by lia.
Qed.

Lemma iipc2_poly_varI x {Gamma ts ss y} :
  nth_error Gamma x = Some (many_poly_abs (length ts) (many_poly_arr ss (poly_var (length ts + y)))) ->
  Forall (iipc2 Gamma) (map (subst_poly_type (fold_right scons poly_var (rev ts))) ss) ->
  iipc2 Gamma (poly_var y).
Proof.
  move=> /typing_var /typing_many_ty_appI => /(_ ts ltac:(done)).
  rewrite subst_poly_type_many_poly_arr /subst_poly_type -rev_length fold_right_length_ts -/subst_poly_type.
  move=> /iipc2I. move: (map _ ss) => {}ss. move: (poly_var y) => t.
  elim: ss t; first done.
  by move=> s ss IH t /= [?] /typing_app H /Forall_cons_iff [[?] /H{H} /iipc2I /IH].
Qed.