From Undecidability.LambdaCalculus Require Import Lambda Util.term_facts Util.wCBN_facts.

From Undecidability Require Import L.Util.L_facts.

From Coq Require Import Relations Wellfounded.Transitive_Closure List Lia.
Import L (term, var, app, lam).
Import Lambda (subst, wCBN_step, wCBN_stepSubst, wCBN_stepApp).
From Coq Require Import ssreflect.

Import Relation_Operators (t1n_trans).
Import Datatypes (id).

Set Default Goal Selector "!".

#[local] Notation step := wCBN_step.

Module Argument.

#[local] Arguments id : simpl never.
#[local] Arguments clos_refl_trans {A}.
#[local] Arguments clos_trans {A}.
#[local] Unset Implicit Arguments.

Lemma t_trans' {A : Type} {R : relation A} {x x' y z : A} :
  x = x' -> clos_trans R x y -> clos_trans R y z -> clos_trans R x' z.
Proof. move=> ->. by apply: t_trans. Qed.

Lemma clos_t_rt s t : clos_trans step s t -> clos_refl_trans step s t.
Proof. elim; eauto using @clos_refl_trans. Qed.

Lemma closed_eval {s t} : L.eval s t -> closed s -> closed t.
Proof. by move=> /eval_iff [/closed_star]. Qed.

Fixpoint cbv_cbn (t : term) : term :=
  match t with
  | var n => lam (app (var 0) (var (S n)))
  | app s t => lam (app (ren S (cbv_cbn s)) (lam (app (ren S (ren S (cbv_cbn t))) (lam (app (app (var 1) (var 0)) (var 2))))))
  | lam s => lam (app (var 0) (ren S (lam (cbv_cbn s))))
  end.

Definition Psi (t : term) :=
  match t with
  | var n => var n
  | app s t => app s t
  | lam s => lam (cbv_cbn s)
  end.

Lemma cbv_cbn_ren xi s : cbv_cbn (ren xi s) = ren xi (cbv_cbn s).
Proof.
  elim: s xi.
  - done.
  - move=> ? IH1 ? IH2 xi /=. by rewrite IH1 IH2 !simpl_term.
  - move=> ? IH xi /=. rewrite IH !simpl_term.
    congr lam. congr app. congr lam.
    apply: ext_ren_term. by case.
Qed.

Lemma cbv_cbn_subst sigma s :
  (forall n, match sigma n with | app _ _ => False | _ => True end) ->
  cbv_cbn (subst sigma s) = subst (funcomp Psi sigma) (cbv_cbn s).
Proof.
  elim: s sigma.
  - move=> n sigma Hsigma /=.
    have := Hsigma n. by case: (sigma n).
  - move=> s IH1 t IH2 sigma Hsigma /=.
    rewrite (IH1 _ Hsigma) (IH2 _ Hsigma) /=.
    rewrite !simpl_term /=.
    congr lam. congr app. congr lam. congr app.
    apply: ext_subst_term => ? /=. by rewrite !simpl_term.
  - move=> s IH sigma Hsigma /=. rewrite IH.
    { move=> [|n] /=; first done.
      have := Hsigma n. by case: (sigma n). }
    rewrite !simpl_term.
    congr lam. congr app. congr lam.
    apply: ext_subst_term => - [|n] /=; first done.
    have := Hsigma n. case: (sigma n); [done|done|].
    move=> s' /= _. rewrite !cbv_cbn_ren.
    congr lam. rewrite !simpl_term.
    apply: ext_ren_term. by case.
Qed.

Fixpoint colon s u :=
  match s with
  | var n => var n
  | lam _ => app u (Psi s)
  | app s t =>
      match s with
      | var n => var n
      | app s1 s2 => (id colon) s (lam (app (ren S (cbv_cbn t)) (lam (app (app (var 1) (var 0)) (ren S (ren S u))))))
      | lam _ => colon t (lam (app (app (ren S (Psi s)) (var 0)) (ren S u)))
      end
  end.

Inductive cbv_step : term -> term -> Prop :=
  | cbv_step_lam s t : cbv_step (app (lam s) (lam t)) (subst (scons (lam t) var) s)
  | cbv_step_appL s s' t : cbv_step s s' -> cbv_step (app s t) (app s' t)
  | cbv_step_appR s t t' : cbv_step t t' -> cbv_step (app (lam s) t) (app (lam s) t').

Lemma cbv_steps_appL {s s' t} :
  clos_refl_trans cbv_step s s' -> clos_refl_trans cbv_step (app s t) (app s' t).
Proof. elim; by eauto using @clos_refl_trans, cbv_step with nocore. Qed.

Lemma cbv_steps_appR {s t t'} :
  clos_refl_trans cbv_step t t' -> clos_refl_trans cbv_step (app (lam s) t) (app (lam s) t').
Proof. elim; by eauto using @clos_refl_trans, cbv_step with nocore. Qed.

Lemma eval_cbv_steps s t : L.eval s t -> closed s -> clos_refl_trans cbv_step s t.
Proof.
  elim; clear s t. { move=> *. by apply: rt_refl. }
  move=> s u t t' v Hsu IH1 Htt' IH2 Hv IH3 /closed_app [Hs Ht].
  move: (Hs) => /IH1 => - {}IH1. move: (Ht) => /IH2 => - {}IH2.
  have Ht' : closed t' by (apply: closed_eval; eassumption).
  have Hu : bound 1 u.
  { by have /closed_dcl /boundE := closed_eval Hsu Hs. }
  have /IH3 {}IH3 : closed (L.subst u 0 t').
  { apply /closed_dcl. apply: closed_subst; [done|by apply /closed_dcl]. }
  move: IH3.
  have -> : L.subst u 0 t' = subst (scons t' var) u.
  { rewrite L_subst_Lambda_subst; first done.
    apply: (bound_ext_subst_term Hu).
    move=> [|n]; [done|lia]. }
  move=> {}IH3.
  apply: rt_trans. { apply: cbv_steps_appL. eassumption. }
  apply: rt_trans. { apply: cbv_steps_appR. eassumption. }
  apply: rt_trans; last by eassumption.
  move: Htt' => /eval_iff [_] [? ->].
  apply: rt_step. by apply: cbv_step_lam.
Qed.

Lemma bound_cbv_cbn k t : bound k t -> bound k (cbv_cbn t).
Proof.
  elim.
  - move=> *. do 3 constructor; lia.
  - move=> * /=. do 2 constructor.
    + by apply: bound_ren; [eassumption|lia].
    + do 2 constructor.
      * rewrite !simpl_term /=. apply: bound_ren; [eassumption|lia].
      * do 2 constructor; [constructor|]; constructor; lia.
  - move=> * /=. do 3 constructor; first by lia.
    apply: bound_ren; [eassumption|].
    move=> [|n] /=; lia.
Qed.

Lemma cbv_step_closed {s s'} : cbv_step s s' -> closed s -> closed s'.
Proof.
  elim.
  - move=> > /closed_app [/closed_dcl /boundE ?] /closed_dcl ?.
    apply /closed_dcl. apply: bound_subst; first by eassumption.
    move=> [|n]; [done|lia].
  - move=> > ? H /closed_app [/H ? ?]. by apply: app_closed.
  - move=> > ? H /closed_app [? /H ?]. by apply: app_closed; [|].
Qed.

Lemma cbv_steps_closed {s s'} : clos_refl_trans cbv_step s s' -> closed s -> closed s'.
Proof. elim; by eauto using cbv_step_closed. Qed.

Lemma cbv_step_L_step s s' : cbv_step s s' -> closed s -> L_facts.step s s'.
Proof.
  elim; clear s s'.
  - move=> s s' /closed_app [/closed_dcl /boundE ?] /closed_dcl ?.
    have := L_facts.stepApp s s'. congr L_facts.step.
    rewrite L_subst_Lambda_subst. { by apply /closed_dcl. }
    apply: bound_ext_subst_term; first by eassumption.
    move=> [|n]; [done|lia].
  - move=> > ? H /closed_app [/H ? ?]. by apply: stepAppL.
  - move=> > ? H /closed_app [? /H ?]. by apply: stepAppR.
Qed.

Lemma steps_to_colon s u : closed s -> clos_trans step (app (cbv_cbn s) u) (colon s u).
Proof.
  elim: s u.
  - by move=> > /not_closed_var.
  - move=> s IH1 t IH2 u /= /closed_app [/[dup] Hs /IH1 {}IH1 /IH2 {}IH2].
    have {}IH1 := IH1 (lam (app (ren S (cbv_cbn t)) (lam (app (app (var 1) (var 0)) (ren S (ren S u)))))).
    have {}IH2 := IH2 (lam (app (app (ren S (Psi s)) (var 0)) (ren S u))).
    move: (s) Hs IH1 IH2 => [].
    + by move=> > /not_closed_var.
    + move=> s1 s2 _. move: (app s1 s2) => s' IH1 IH2.
      apply: t_trans. { apply: t_step. by apply: wCBN_stepSubst. }
      move: IH1. by rewrite /= !simpl_term !ren_as_subst_term.
    + move=> s' _ IH1 IH2.
      apply: t_trans. { apply: t_step. by apply: wCBN_stepSubst. }
      move: IH1 => /t_trans'. apply.
      { rewrite /= !simpl_term !ren_as_subst_term. congr app.
        congr lam. congr app. congr lam. apply: ext_subst_term. by case. }
      apply: t_trans. { apply: t_step. by apply: wCBN_stepSubst. }
      move: IH2. by rewrite /= !simpl_term !ren_as_subst_term.
  - move=> s IH u ?.
    apply: t_step. rewrite /=. apply: stepLam'.
    rewrite /= !simpl_term /=. congr app. congr lam.
    rewrite -[LHS]subst_var_term. apply: ext_subst_term. by case.
Qed.

Lemma colon_redex s t u : closed (lam s) -> closed (lam t) ->
  clos_trans step (colon (app (lam s) (lam t)) u) (colon (subst (scons (lam t) var) s) u).
Proof.
  move=> /= Hs Ht.
  apply: t_trans. { apply: t_step. by apply: wCBN_stepSubst. } rewrite /=.
  apply: t_trans. { apply: t_step. apply: wCBN_stepApp. by apply: wCBN_stepSubst. }
  have /(steps_to_colon _ u) : closed (subst (scons (lam t) var) s).
  { move: Hs Ht => /closed_dcl /boundE ? /closed_dcl ?.
    apply /closed_dcl. apply: bound_subst; first by eassumption.
    move=> [|n]; [done|lia]. }
  rewrite !simpl_term /= cbv_cbn_subst. { by case. }
  congr clos_trans. congr app. apply: ext_subst_term. by case.
Qed.

Lemma cbv_step_to_lam s1 s2 t : cbv_step (app s1 s2) (lam t) ->
  exists s1' s2', s1 = lam s1' /\ s2 = lam s2' /\ lam t = subst (scons (lam s2') var) s1'.
Proof.
  move Es': (app s1 s2) => s'. move Et': (lam t) => t' Hs't'.
  elim: Hs't' s1 s2 t Es' Et'; [|done|done].
  move=> > [? ?] ?. do 2 eexists. by split; [eassumption|split; [eassumption|]].
Qed.

Lemma simulate_cbv_step s s' : cbv_step s s' -> closed s ->
  forall x, clos_trans step (colon s x) (colon s' x).
Proof.
  elim; clear s s'.
  - move=> s t /closed_app [? ?]. move=> ?. by apply: colon_redex.
  - move=> s s' t Hss' IH /closed_app [/[dup] /IH {}IH Hs Ht].
    have [s1 [s2 ?]] : exists s1 s2, s = app s1 s2.
    { by case: Hss' => *; eexists; eexists. }
    subst s. rewrite /=.
    move: s' IH Hss' => [].
    + by move=> ? ? /cbv_step_closed /(_ Hs) /not_closed_var.
    + move=> s'1 s'2 IH Hss' x. by apply: IH.
    + move=> s' IH /cbv_step_to_lam [s1'] [s2'] [? [? Hs']].
      move=> x. subst.
      apply: t_trans. { by apply: IH. }
      apply: t_trans. { apply: t_step. by apply: wCBN_stepSubst. }
      have := steps_to_colon t (lam (app (app (ren S (Psi (lam s'))) # 0) (ren S x))) Ht.
      by rewrite /= !simpl_term !ren_as_subst_term.
  - move=> > ? IH /closed_app [? /IH] {}IH x.
    by apply: IH.
Qed.

Lemma simulation {s t} : L.eval s t -> closed s ->
  forall x, clos_refl_trans step (colon s x) (colon t x).
Proof.
  move=> /eval_cbv_steps + /[dup] => /[apply].
  elim.
  - move=> > /simulate_cbv_step /[apply] H x.
    by apply: clos_t_rt.
  - move=> *. by apply: rt_refl.
  - move=> *. apply: rt_trans; by eauto using cbv_steps_closed.
Qed.

Lemma terminating_Acc s t : clos_refl_trans step s (lam t) -> Acc (fun t' s' => step s' t') s.
Proof.
  move Et': (lam t) => t' /(clos_rt_rt1n term) Hst'.
  elim: Hst' t Et'.
  { move=> ?? <-. by constructor => ? /stepE. }
  move=> x y z Hxy Hyz IH t ?. constructor => y'.
  move: Hxy => /step_fun /[apply] <-. apply: IH. by eassumption.
Qed.

Lemma cbv_step_lam_or_progress {s} : closed s -> (exists s', s = lam s') \/ (exists t, cbv_step s t).
Proof.
  elim: s.
  - by move=> ? /not_closed_var.
  - move=> s IHs t IHt /closed_app [/IHs {}IHs /IHt {}IHt]. right.
    case: IHs; case: IHt.
    + move=> [? ->] [? ->]. eexists. by apply: cbv_step_lam.
    + move=> [? ?] [? ->]. eexists. apply: cbv_step_appR. by eassumption.
    + move=> [? ->] [? ?]. eexists. apply: cbv_step_appL. by eassumption.
    + move=> [? ?] [? ?]. eexists. apply: cbv_step_appL. by eassumption.
  - move=> *. left. by eexists.
Qed.

Lemma clos_trans_swap s t : clos_trans step s t -> clos_trans (fun t' s' => step s' t') t s.
Proof. elim; by eauto using @clos_trans with nocore. Qed.

Lemma inverse_simulation s t :
  clos_refl_trans step (colon s (lam (var 0))) (lam t) -> closed s ->
  exists t', eval s t'.
Proof.
  move=> /terminating_Acc /(Acc_clos_trans term).
  move Es': (colon s (lam (var 0))) => s' Hs'.
  elim: Hs' s Es' => {}s' _ IH s ? Hs. subst s'.
  suff : exists t', star L_facts.step s (lam t').
  { move=> [t' ?]. by exists (lam t'). }
  case: (cbv_step_lam_or_progress Hs).
  { move=> [t' ->]. exists t'. by apply: starR. }
  move=> [s'] /[dup] /cbv_step_L_step /(_ Hs) H0s.
  move=> /[dup] /simulate_cbv_step /(_ Hs (lam (var 0))) /clos_trans_swap /IH H1s.
  move=> /cbv_step_closed /(_ Hs) /H1s => /(_ eq_refl) [? [Hs't']] [t' ?].
  subst. exists t'. by apply: starC; eassumption.
Qed.

Lemma bound_colon {n s u} : bound n s -> bound n u -> bound n (colon s u).
Proof.
  move=> H. elim: H u.
  - move=> * /=. by constructor.
  - move=> ? [?|??|?] ? IH'1 IH1 ? IH2 ??.
    + done.
    + move=> /=. apply: IH1. do 2 constructor.
      * apply: bound_ren; [apply: bound_cbv_cbn; eassumption|lia].
      * do 2 constructor; [do 2 constructor|]; [lia ..|].
        rewrite simpl_term /=.
        apply: bound_ren; [eassumption|lia].
    + move=> /=. apply: IH2. do 2 constructor.
      * do 2 constructor; [|lia].
        move=> /boundE in IH'1.
        apply: bound_ren; [apply: bound_cbv_cbn; eassumption|].
        move=> [|?] /=; lia.
      * apply: bound_ren; [eassumption|lia].
  - move=> * /=. constructor; [done|constructor].
    by apply: bound_cbv_cbn.
Qed.

Lemma closed_colon {s} : closed s -> forall sigma, subst sigma (colon s (lam (var 0))) = colon s (lam (var 0)).
Proof.
  move=> Hs sigma. rewrite subst_closed; last done.
  by apply /closed_dcl /bound_colon; apply /closed_dcl.
Qed.

End Argument.

Require Import Undecidability.Synthetic.Definitions.
Require Undecidability.L.Reductions.HaltL_to_HaltLclosed.
Import HaltL_to_HaltLclosed (HaltLclosed).

#[local] Unset Asymmetric Patterns.

Theorem reduction : HaltLclosed wCBNclosed.
Proof.
  exists (fun '(exist _ s Hs) => exist _ (Argument.colon s (lam (var 0))) (Argument.closed_colon Hs)).
  move=> [s Hs]. split.
  - move=> [t] /= /[dup] [[_ []]] t' ->.
    move=> /eval_iff /= /Argument.simulation.
    move=> /(_ Hs (lam (var 0))) ?. exists (Argument.cbv_cbn t').
    apply: rt_trans; first by eassumption.
    apply: rt_step. by apply: wCBN_stepSubst.
  - move=> [t] /Argument.inverse_simulation. by apply.
Qed.