Require Import List Lia.
Import ListNotations.
Require Import Undecidability.SystemF.SysF Undecidability.SystemF.Autosubst.syntax.
From Undecidability.SystemF.Util Require Import Facts poly_type_facts pure_term_facts term_facts typing_facts pure_typing_facts pure_typable_prenex.

Require Undecidability.SemiUnification.SemiU.
Module SU := SemiU.

Require Import ssreflect ssrbool ssrfun.

Set Default Proof Using "Type".
Set Default Goal Selector "!".

Module Argument.
Section LU2SemiU_to_SysF_TYP.

Variables SUs SUt0 SUt1 : SU.term.
Fixpoint SU_term_to_poly_type (s: SU.term) :=
  match s with
  | SU.atom n => poly_var n
  | SU.arr s t => poly_arr (SU_term_to_poly_type s) (SU_term_to_poly_type t)
  end.

Definition SUs' := SU_term_to_poly_type SUs.
Definition SUt0' := SU_term_to_poly_type SUt0.
Definition SUt1' := SU_term_to_poly_type SUt1.

Definition N_W := pure_abs (many_pure_app (pure_var 2) [pure_var 0; pure_var 0]).
Definition M_W : pure_term := many_pure_app (pure_var 0) [N_W].

Definition t_0_W := (poly_arr (poly_arr (poly_var 0) (poly_var 0)) (poly_var 1)).
Definition t_1_W := poly_arr (poly_arr SUt0' SUt0') (poly_arr (poly_arr SUt1' SUt1') (poly_arr SUs' SUs')).
Definition n_W : nat := locked (poly_var_bound t_1_W).
Definition Gamma_W : list poly_type := [poly_abs (poly_abs t_0_W); many_poly_abs n_W t_1_W].

Lemma is_simple_SU_term_to_poly_type {s} : is_simple (SU_term_to_poly_type s) <-> True.
Proof. elim: s; [done | by move=> /= ? + ? + => -> ->]. Qed.

Lemma is_simple_t_1_W : is_simple t_1_W.
Proof.
  by rewrite /t_1_W /= /SUt0' /SUt1' /SUs' ?is_simple_SU_term_to_poly_type.
Qed.

Lemma closed_t_1_W : allfv_poly_type (gt n_W) t_1_W.
Proof. rewrite /n_W -lock. by apply: poly_var_boundP. Qed.

Lemma is_simple_t_0_W : is_simple t_0_W.
Proof. done. Qed.

Lemma closed_t_0_W : allfv_poly_type (gt 2) t_0_W.
Proof. move=> /=. by lia. Qed.

Definition HM_W1 := pure_typable_intro_prenex M_W t_0_W 2 is_simple_t_0_W closed_t_0_W.

Definition HM_W2 := pure_typable_intro_prenex (sval HM_W1) t_1_W n_W is_simple_t_1_W closed_t_1_W.

Lemma SysF_TYPI Gamma {M} : pure_typable Gamma M -> SysF_TYP M.
Proof. move=> [t] /pure_typing_iff_type_assignment ?. by exists Gamma, t. Qed.

Lemma prenex_Gamma_W :
  Forall (fun t => exists n s, t = many_poly_abs n s /\ is_simple s /\ allfv_poly_type (gt n) s) Gamma_W.
Proof.
  apply /Forall_cons_iff. constructor.
  { exists 2, t_0_W. constructor; [done | constructor; [done | by move=> /=; lia ]]. }
  apply /Forall_cons_iff. constructor; last done.
  do 2 eexists. constructor; first by reflexivity.
  by constructor; [apply: is_simple_t_1_W | apply: closed_t_1_W ].
Qed.

Section InverseTransport.
Local Arguments many_pure_app : simpl never.
Local Arguments map : simpl never.

Lemma map_ren_poly_type_Gamma_W {ξ}: map (ren_poly_type ξ) Gamma_W = Gamma_W.
Proof.
  congr cons. congr cons. apply: ren_poly_type_closed_id.
  rewrite allfv_poly_type_many_poly_abs.
  apply: allfv_poly_type_impl closed_t_1_W.
  move=> ? ?. rewrite iter_scons_lt; by [|lia].
Qed.

Lemma decompose_M_W : pure_typable Gamma_W M_W ->
  exists s, pure_typing Gamma_W N_W (poly_arr s s).
Proof.
  move=> [?] /pure_typingE /= [?] [?] [?] [?] [+] [+] [_ ?].
  move=> /pure_typingE' [?] [[?]]. subst.
  move=> /(contains_poly_arrE (n := 2)) [?] [?] [? ?]. subst.
  rewrite map_ren_poly_type_Gamma_W => ?. eexists. by eassumption.
Qed.

Lemma decompose_N_W {s} : pure_typing Gamma_W N_W (poly_arr s s) ->
  exists n σ,
    s = many_poly_abs n (subst_poly_type σ (poly_arr SUs' SUs')) /\
    pure_typing (map (ren_poly_type (Nat.add n)) (s :: Gamma_W)) (pure_var 0) (subst_poly_type σ (poly_arr SUt0' SUt0')) /\
    pure_typing (map (ren_poly_type (Nat.add n)) (s :: Gamma_W)) (pure_var 0) (subst_poly_type σ (poly_arr SUt1' SUt1')).
Proof.
  move=> /pure_typingE' /=.
  move=> /pure_typingE /= [n1] [?] [?] [?] [+] [+] [H1C ?].
  move=> /pure_typingE' [?] [?] [+] [+ H2C].
  move=> /pure_typingE' [?] [[?]] H3C. subst.
  move=> H1 H2.
  move: H3C. rewrite ren_poly_type_many_poly_abs /t_1_W /=.
  move=> /contains_poly_arrE [?] [?] [? ?]. subst.
  move: H2C => /containsE [? ?]. subst.
  move: H1C => /containsE ?. subst.
  do 2 eexists.
  constructor; first by rewrite ?poly_type_norm.
  constructor; [move: H1 | move: H2]; by rewrite ?poly_type_norm.
Qed.

Lemma decompose_x_W {n σ Gamma s' t'} :
  let s := many_poly_abs n (subst_poly_type σ (poly_arr s' s')) in
  pure_typing ((ren_poly_type (Nat.add n) s) :: Gamma) (pure_var 0) (subst_poly_type σ (poly_arr t' t')) ->
  exists τ, subst_poly_type (funcomp (subst_poly_type τ) σ) s' = subst_poly_type σ t'.
Proof.
  move=> /= /pure_typingE' /= [?] [[?]]. subst.
  rewrite ren_poly_type_many_poly_abs /=.
  move=> /contains_poly_arrE [?] [?] [-> _].
  rewrite ?poly_type_norm. by eexists.
Qed.

Fixpoint prune (s: poly_type) : SU.term :=
  match s with
  | poly_var x => SU.atom (1 + x)
  | poly_arr s t => SU.arr (prune s) (prune t)
  | poly_abs _ => SU.atom 0
  end.

Fact substitute_prune_ex τ t : exists ψ, SU.substitute ψ (prune t) = prune (subst_poly_type τ t).
Proof.
  exists (fun n=> if n is S n then prunen) else SU.atom 0).
  elim: t; [done | by move=> ? + ? /= => -> -> | done ].
Qed.

Fact substitute_prune {σ t} :
  SU.substitute (funcomp prune σ) t = prune (subst_poly_type σ (SU_term_to_poly_type t)).
Proof. elim: t; [done | by move=> ? + ? => /= -> -> ]. Qed.

Lemma introduce_simple_substitutions {s t σ τ} :
  subst_poly_type (funcomp (subst_poly_type τ) σ) (SU_term_to_poly_type s) = subst_poly_type σ (SU_term_to_poly_type t) ->
  exists ψ, SU.substitute ψ (SU.substitute (funcomp prune σ) s) = SU.substitute (funcomp prune σ) t.
Proof.
  move=> H. haveHψ] := substitute_prune_ex τ (subst_poly_type σ (SU_term_to_poly_type s)).
  exists ψ. by rewrite ?substitute_prune Hψ -H poly_type_norm.
Qed.

End InverseTransport.

Lemma inverse_transport : SysF_TYP (sval HM_W2) -> SU.LU2SemiU (SUs, SUt0, SUt1).
Proof.
  move=> [Gamma] [t] /pure_typing_iff_type_assignment.
  move=> /pure_typing_tidyI /pure_typableI /(svalP HM_W2) /(svalP HM_W1).
  move=> [?] /(pure_typing_ren_pure_term_allfv_pure_term id (Delta := map tidy Gamma_W)).
  apply: unnest; first done. rewrite ren_pure_term_id => /pure_typableI.
  move=> /(pure_typable_tidy_iff (Gamma1 := [])). apply: unnest; first by apply: prenex_Gamma_W.
  move=> /decompose_M_W [?] /decompose_N_W [?] [σ] [->].
  move=> [/decompose_x_W [?] + /decompose_x_W [?] +].
  move=> /introduce_simple_substitutions [ψ0 ?] /introduce_simple_substitutions [ψ1 ?].
  by exists (funcomp prune σ), ψ0, ψ1.
Qed.

Section Transport.

Variables φ ψ0 ψ1 : nat -> SU.term.
Variable Hφψ0 : SU.substitute ψ0 (SU.substitute φ SUs) = SU.substitute φ SUt0.
Variable Hφψ1 : SU.substitute ψ1 (SU.substitute φ SUs) = SU.substitute φ SUt1.

Definition t_x_W' := SU_term_to_poly_type (SU.substitute φ SUs).
Definition n_W' := locked (poly_var_bound (poly_arr t_x_W' t_x_W')).
Definition t_x_W : poly_type := many_poly_abs n_W' (poly_arr t_x_W' t_x_W').

Definition t_x_0_W : poly_type := SU_term_to_poly_type (SU.substitute φ SUt0).
Definition t_x_1_W : poly_type := SU_term_to_poly_type (SU.substitute φ SUt1).

Definition ts_0_W : list poly_type := map (fun n => SU_term_to_poly_type (φ n)) (rev (seq 0 n_W)).
Definition ts_1_W : list poly_type := map (fun n => SU_term_to_poly_type (ψ0 n)) (rev (seq 0 n_W')).
Definition ts_2_W : list poly_type := map (fun n => SU_term_to_poly_type (ψ1 n)) (rev (seq 0 n_W')).

Definition x_1_W : term := many_ty_app (var 0) ts_1_W.
Definition x_2_W : term := many_ty_app (var 0) ts_2_W.
Definition Q_W : term := abs t_x_W (many_ty_abs n_W' (app (app (many_ty_app (var 2) ts_0_W) x_1_W) x_2_W)).
Definition P_W : term := app (ty_app (ty_app (var 0) (poly_var 0)) t_x_W) Q_W.

Fact poly_var_bound_SUt0'_n_W : poly_var_bound SUt0' < n_W.
Proof. rewrite /n_W /t_1_W /= -lock. by lia. Qed.

Fact poly_var_bound_SUt1'_n_W : poly_var_bound SUt1' < n_W.
Proof. rewrite /n_W /t_1_W /= -lock. by lia. Qed.

Fact poly_var_bound_SUs'_n_W : poly_var_bound SUs' < n_W.
Proof. rewrite /n_W /t_1_W /= -lock. by lia. Qed.

Lemma SU_term_to_poly_type_SU_substitute {ζ t} :
  subst_poly_type (fun x : nat => SU_term_to_poly_type (ζ x)) (SU_term_to_poly_type t) =
    SU_term_to_poly_type (SU.substitute ζ t).
Proof. elim: t; [done | by move=> /= ? -> ? ->]. Qed.

Lemma subst_poly_type_SU_term_to_poly_type {ζ n t} :
  poly_var_bound (SU_term_to_poly_type t) < n ->
  subst_poly_type
    (fold_right scons poly_var (map (fun x => SU_term_to_poly_type (ζ x)) (seq 0 n)))
    (SU_term_to_poly_type t) =
  SU_term_to_poly_type (SU.substitute ζ t).
Proof.
  move=> ?. rewrite subst_poly_type_map_seq; first by lia.
  by apply: SU_term_to_poly_type_SU_substitute.
Qed.

Fact poly_arr_eq_eq {s t} : s = t -> poly_arr s s = poly_arr t t.
Proof. by move=> ->. Qed.

Lemma typing_x_1_W :
  (forall Gamma, typing (many_poly_abs n_W' (poly_arr t_x_W' t_x_W') :: Gamma) x_1_W (poly_arr t_x_0_W t_x_0_W)).
Proof using Hφψ0.
  move=> Gamma. rewrite /x_1_W. evar (t: poly_type).
  have := @typing_var (many_poly_abs n_W' (poly_arr t_x_W' t_x_W') :: Gamma) 0 t.
  subst t. move=> /(_ ltac:(reflexivity)) /typing_many_ty_appI => /(_ ts_1_W).
  apply: unnest. { by rewrite /ts_1_W map_length rev_length seq_length. }
  congr typing => /=. apply: poly_arr_eq_eq. rewrite /ts_1_W /t_x_W' /t_x_0_W -Hφψ0.
  rewrite -map_rev rev_involutive. apply: subst_poly_type_SU_term_to_poly_type.
  rewrite /n_W' /t_x_W' -lock /=. by lia.
Qed.

Lemma typing_x_2_W :
  (forall Gamma, typing (many_poly_abs n_W' (poly_arr t_x_W' t_x_W') :: Gamma) x_2_W (poly_arr t_x_1_W t_x_1_W)).
Proof using Hφψ1.
  move=> Gamma. rewrite /x_2_W. evar (t: poly_type).
  have := @typing_var (many_poly_abs n_W' (poly_arr t_x_W' t_x_W') :: Gamma) 0 t.
  subst t. move=> /(_ ltac:(reflexivity)) => /typing_many_ty_appI => /(_ ts_2_W).
  apply: unnest. { by rewrite /ts_2_W map_length rev_length seq_length. }
  congr typing => /=. apply: poly_arr_eq_eq. rewrite /ts_2_W /t_x_W' /t_x_1_W -Hφψ1.
  rewrite -map_rev rev_involutive. apply: subst_poly_type_SU_term_to_poly_type.
  rewrite /n_W' /t_x_W' -lock /=. by lia.
Qed.

Lemma typing_Q_W :
  (forall Gamma, typing (many_poly_abs n_W' (poly_arr t_x_W' t_x_W') :: Gamma) x_1_W (poly_arr t_x_0_W t_x_0_W)) ->
  (forall Gamma, typing (many_poly_abs n_W' (poly_arr t_x_W' t_x_W') :: Gamma) x_2_W (poly_arr t_x_1_W t_x_1_W)) ->
  typing Gamma_W Q_W (poly_arr t_x_W t_x_W).
Proof.
  move=> H1 H2.
  apply /typing_abs /typing_many_ty_absI.
  apply: (typing_app (s := (poly_arr t_x_1_W t_x_1_W))); [apply: (typing_app (s := (poly_arr t_x_0_W t_x_0_W))) |].
  - evar (t : poly_type).
    have := @typing_var (map (ren_poly_type (Nat.add n_W')) (t_x_W :: Gamma_W)) 2 t.
    subst t. move=> /(_ ltac:(reflexivity)).
    rewrite ren_poly_type_many_poly_abs.
    move /typing_many_ty_appI => /(_ ts_0_W). apply: unnest.
    { by rewrite /ts_0_W map_length rev_length seq_length. }
    congr typing. rewrite /t_1_W /= -map_rev rev_involutive.
    congr poly_arr; [| congr poly_arr]; apply: poly_arr_eq_eq.
    + have := poly_var_bound_SUt0'_n_W. rewrite /t_x_0_W /SUt0' /ts_0_W.
      move=> ?. rewrite ren_poly_type_poly_var_bound; first by lia.
      by apply: subst_poly_type_SU_term_to_poly_type.
    + have := poly_var_bound_SUt1'_n_W. rewrite /t_x_1_W /SUt1' /ts_0_W.
      move=> ?. rewrite ren_poly_type_poly_var_bound; first by lia.
      by apply: subst_poly_type_SU_term_to_poly_type.
    + have := poly_var_bound_SUs'_n_W. rewrite /t_x_W' /SUs' /ts_0_W.
      move=> ?. rewrite ren_poly_type_poly_var_bound; first by lia.
      by apply: subst_poly_type_SU_term_to_poly_type.
  - rewrite /(map _ (_ :: _)) -/(map _ _). move: (map _ Gamma_W) => Gamma.
    rewrite /t_x_W ren_poly_type_many_poly_abs ren_poly_type_poly_var_bound;
      first by (rewrite /n_W' -lock /=; lia).
    by apply: H1.
  - rewrite /(map _ (_ :: _)) -/(map _ _). move: (map _ Gamma_W) => Gamma.
    rewrite /t_x_W ren_poly_type_many_poly_abs ren_poly_type_poly_var_bound;
      first by (rewrite /n_W' -lock /=; lia).
    by apply: H2.
Qed.

Lemma erase_P_W : erase P_W = M_W.
Proof.
  by rewrite /P_W /= erase_many_ty_abs /= /x_1_W /x_2_W ?erase_many_ty_app.
Qed.

Lemma typing_P_W :
  typing Gamma_W Q_W (poly_arr t_x_W t_x_W) ->
  typing Gamma_W P_W (poly_var 0).
Proof.
  rewrite /P_W. apply: typing_app.
  apply: typing_ty_appI; [apply: typing_ty_appI |]; [by apply: typing_var | done | done].
Qed.

Lemma pure_typable_M_W :
  typing Gamma_W P_W (poly_var 0) ->
  pure_typable Gamma_W M_W.
Proof.
  move=> /typing_to_pure_typing /pure_typableI. by rewrite erase_P_W.
Qed.

End Transport.

Lemma transport : SU.LU2SemiU (SUs, SUt0, SUt1) -> SysF_TYP (sval HM_W2).
Proof.
  move=> [φ] [ψ0] [ψ1] [Hφψ0 HHφψ1]. apply: (SysF_TYPI (map tidy [])).
  apply /(svalP HM_W2) /(svalP HM_W1).
  have := iffRL (pure_typable_tidy_iff _ (Gamma1 := [])).
  move=> /(_ [many_poly_abs 2 t_0_W; many_poly_abs n_W t_1_W]).
  apply; first by apply: prenex_Gamma_W.
  apply: (pure_typable_M_W φ ψ0 ψ1).
  apply: typing_P_W.
  apply: typing_Q_W; [apply: typing_x_1_W | apply: typing_x_2_W]; by eassumption.
Qed.

End LU2SemiU_to_SysF_TYP.
End Argument.

Require Import Undecidability.Synthetic.Definitions.
Import SemiU.

Theorem reduction : LU2SemiU SysF_TYP.
Proof.
  exists (fun '(s, t0, t1) => (sval (Argument.HM_W2 s t0 t1))).
  move=> [[s t0] t1]. constructor.
  - exact: Argument.transport.
  - exact: Argument.inverse_transport.
Qed.