-- https://plfa.github.io/Adequacy/

import Plfl.Init
import Plfl.Untyped.BigStep
import Plfl.Untyped.Denotational.Soundness

namespace Adequacy

open Untyped Untyped.Notation
open Untyped.Subst
open BigStep (Clos ClosEnv Eval.reduce_of_cbn)
open BigStep.Notation
open Denotational Denotational.Notation
open Soundness (soundness)

-- https://plfa.github.io/Adequacy/#the-property-of-being-greater-or-equal-to-a-function
/-- `GtFn u` means that it is "greater than" a certain function value. -/
def GtFn (u : Value) : Prop := ∃ v w, v ⇾ w ⊑ u

/-- If `u` is greater than a function, then an even greater value `u'` is too. -/
lemma GtFn.sub (gt : GtFn u) (lt : u ⊑ u') : GtFn u' :=
  let ⟨v, w, lt'⟩ := gt; ⟨v, w, lt'.trans lt⟩

/-- `⊥` is never greater than a function. -/
lemma not_gtFn_bot : ¬ GtFn ⊥
| ⟨v, w, lt⟩ => by
  have ⟨_, f, s, _⟩ := sub_inv_fn lt; have ⟨_, _, i⟩ := elem_of_allFn f; cases s i

/-- If the join of two values is greater than a function, then at least one of them is too. -/
lemma GtFn.conj (gt : GtFn (u ⊔ v)) : GtFn u ∨ GtFn v := by
  have ⟨_, _, lt⟩ := gt; have ⟨_, f, s, _⟩ := sub_inv_fn lt; have ⟨v, w, i⟩ := elem_of_allFn f
  refine Or.imp ?inl ?inr <| s i <;> (intro i'; exists v, w; exact sub_of_elem i')

/-- If neither of the two values is greater than a function, then nor is their join. -/
lemma not_gtFn_conj (ngt : ¬ GtFn u) (ngt' : ¬ GtFn v) : ¬ GtFn (u ⊔ v) := by
  intro gtuv; exfalso; exact gtuv.conj |>.elim ngt ngt'

/--
If the join of two values is not greater than a function,
then neither of them is individually.
-/
lemma not_gtFn_conj_inv (ngtuv : ¬ GtFn (u ⊔ v)) : ¬ GtFn u ∧ ¬ GtFn v := by
  by_contra h; simp_all only [not_and, not_not]
  have ngtu := ngtuv ∘ (GtFn.sub · <| .conjR₁ .refl)
  have ngtv := ngtuv ∘ (GtFn.sub · <| .conjR₂ .refl)
  exact h ngtu |> ngtv

lemma not_gtFn_conj_iff : (¬ GtFn u ∧ ¬ GtFn v) ↔ ¬ GtFn (u ⊔ v) :=
  ⟨(λ nn => not_gtFn_conj nn.1 nn.2), not_gtFn_conj_inv⟩

instance GtFn.dec {v} : Decidable (GtFn v) := by match v with
| ⊥ => left; exact not_gtFn_bot
| v ⇾ w => right; exists v, w
| .conj u v => cases @dec u with
  | isTrue h => right; have ⟨v, w, lt⟩ := h; exists v, w; exact lt.conjR₁
  | isFalse h => cases @dec v with
    | isTrue h' => right; have ⟨v, w, lt⟩ := h'; exists v, w; exact lt.conjR₂
    | isFalse h' => left; exact not_gtFn_conj h h'

-- https://plfa.github.io/Adequacy/#relating-values-to-closures
mutual
  /--
  `𝕍 v c` will hold when:
  - `c` is in WHNF (i.e. is a λ-abstraction);
  - `v` is a function;
  - `c`'s body evaluates according to `v`.
  -/
  def 𝕍 : Value → Clos → Prop
  | _, .clos (` _) _ => ⊥
  | _, .clos (_ □ _) _ => ⊥
  | ⊥, .clos (ƛ _) _ => ⊤
  | vw@(v ⇾ w), .clos (ƛ n) γ =>
    have : sizeOf w < sizeOf vw := by subst_vars; simp
    ∀ {c}, 𝔼 v c → GtFn w → ∃ c', (γ‚' c ⊢ n ⇓ c') ∧ 𝕍 w c'
  | uv@(.conj u v), c@(.clos (ƛ _) _) =>
    have : sizeOf v < sizeOf uv := by subst_vars; simp
    𝕍 u c ∧ 𝕍 v c

  /--
  `𝔼 v c` will hold when:
  - `v` is greater than a function value;
  - `c` evaluates to a closure `c'` in WHNF;
  - `𝕍 v c` holds.
  -/
  def 𝔼 (v : Value) : Clos → Prop | .clos m γ' => GtFn v → ∃ c, (γ' ⊢ m ⇓ c) ∧ 𝕍 v c
end

/-- `𝔾` relates `γ` to `γ'` if the corresponding values and closures are related by `𝔼` -/
def 𝔾 (γ : Env Γ) (γ' : ClosEnv Γ) : Prop := ∀ {i : Γ ∋ ✶}, 𝔼 (γ i) (γ' i)

def 𝔾.empty : 𝔾 `∅ ∅ := nofun

def 𝔾.ext (g : 𝔾 γ γ') (e : 𝔼 v c) : 𝔾 (γ`‚ v) (γ'‚' c) := by unfold 𝔾; intro
| .z => exact e
| .s _ => exact g

/-- The proof of a term being in Weak-Head Normal Form. -/
def WHNF (t : Γ ⊢ a) : Prop := ∃ n : Γ‚ ✶ ⊢ ✶, t = (ƛ n)

/-- A closure in a 𝕍 relation must be in WHNF. -/
lemma WHNF.of_𝕍 (vc : 𝕍 v (.clos m γ)) : WHNF m := by
  cases m with (try simp [𝕍] at vc; try contradiction) | lam n => exists n

lemma 𝕍.conj (uc : 𝕍 u c) (vc : 𝕍 v c) : 𝕍 (u ⊔ v) c := by
  let .clos m γ := c; cases m with (try simp [𝕍] at *; try contradiction)
  | lam => unfold 𝕍; exact ⟨uc, vc⟩

lemma 𝕍.of_not_gtFn (nf : ¬ GtFn v) : 𝕍 v (.clos (ƛ n) γ') := by induction v with unfold 𝕍
| bot => trivial
| fn v w => exfalso; apply nf; exists v, w
| conj _ _ ih ih' => exact not_gtFn_conj_inv nf |>.imp ih ih'

lemma 𝕍.sub {v v'} (vvc : 𝕍 v c) (lt : v' ⊑ v) : 𝕍 v' c := by
  let .clos m γ := c; cases m with (try simp [𝕍] at *; try contradiction) | lam m =>
    rename_i Γ; induction lt generalizing Γ with
    | bot => trivial
    | conjL _ _ ih ih' => unfold 𝕍; exact ⟨ih _ _ _ vvc, ih' _ _ _ vvc⟩
    | conjR₁ _ ih => apply ih; unfold 𝕍 at vvc; exact vvc.1
    | conjR₂ _ ih => apply ih; unfold 𝕍 at vvc; exact vvc.2
    | trans _ _ ih ih' => apply_rules [ih, ih']
    | @fn v₂ v₁ w₁ w₂ lt lt' ih ih' =>
      unfold 𝕍 at vvc ⊢; intro _ c evc gtw
      have : 𝔼 v₂ c := by
        -- HACK: Broken mutual induction with `𝔼.sub` here.
        cases c; simp only [𝔼] at *; intro gtv'
        have ⟨c, ec, vv₁c⟩ := evc <| gtv'.sub lt; exists c, ec
        cases c with | clos m γ => have ⟨m', h'⟩ := WHNF.of_𝕍 vv₁c; subst h'; exact ih _ γ _ vv₁c
      have ⟨c', ec', vw₂c'⟩ := vvc this (gtw.sub lt'); exists c', ec'
      let .clos _ _ := c'; have ⟨m', h'⟩ := WHNF.of_𝕍 vw₂c'; subst h'; exact ih' _ _ _ vw₂c'
    | @dist v₁ w₁ w₂ =>
      unfold 𝕍 at vvc ⊢; intro _ c ev₁c gt; unfold 𝕍 at vvc
      by_cases hgt₁ : GtFn w₁ <;> by_cases hgt₂ : GtFn w₂
      · have ⟨c₁, ec₁, vw₁⟩ := vvc.1 ev₁c hgt₁; have ⟨c₂, ec₂, vw₂⟩ := vvc.2 ev₁c hgt₂
        exists c₁, ec₁; cases c₁; have ⟨m', h'⟩ := WHNF.of_𝕍 vw₁; subst h'; unfold 𝕍
        exists vw₁; rwa [←ec₁.determ ec₂] at vw₂
      · have ⟨.clos l γ₁, ec₁, vw₁⟩ := vvc.1 ev₁c hgt₁; exists .clos l γ₁, ec₁
        have ⟨m', h'⟩ := WHNF.of_𝕍 vw₁; subst h'; apply vw₁.conj; exact of_not_gtFn hgt₂
      · have ⟨.clos l γ₂, ec₂, vw₂⟩ := vvc.2 ev₁c hgt₂; exists .clos l γ₂, ec₂
        have ⟨m', h'⟩ := WHNF.of_𝕍 vw₂; subst h'; apply (𝕍.conj · vw₂); exact of_not_gtFn hgt₁
      · cases gt.conj <;> contradiction

lemma 𝔼.sub (evc : 𝔼 v c) (lt : v' ⊑ v) : 𝔼 v' c := by
  let .clos m γ := c; simp only [𝔼] at *; intro gtv'
  have ⟨c, ec, vvc⟩ := evc <| gtv'.sub lt; exists c, ec; exact vvc.sub lt

-- https://plfa.github.io/Adequacy/#programs-with-function-denotation-terminate-via-call-by-name
theorem 𝔼.of_eval {Γ} {γ : Env Γ} {γ' : ClosEnv Γ} {m : Γ ⊢ ✶} (g : 𝔾 γ γ') (d : γ ⊢ m ￬ v)
: 𝔼 v (.clos m γ')
:= by
  generalize hx : v = x at *
  induction d generalizing v with (unfold 𝔼; intro gt)
  | @var _ γ i =>
    unfold 𝔾 𝔼 at g; have := @g i; split at this
    have ⟨c, em', vγi⟩ := this gt; refine ⟨c, ?_, vγi⟩; apply em'.var; trivial
  | @ap _ _ _ _ _ m _ _ ih ih' =>
    unfold 𝔼 at ih; have ⟨.clos l' δ, e_cl', v_cl'⟩ := ih g rfl ⟨_, _, .refl⟩
    have ⟨m', h'⟩ := WHNF.of_𝕍 v_cl'; subst h'; unfold 𝕍 at v_cl'
    have ⟨c', em'c', v_c'⟩ := @v_cl' (.clos m γ') (ih' g rfl) gt; exact ⟨c', e_cl'.ap em'c', v_c'⟩
  | @fn _ _ n _ _ _ ih =>
    unfold 𝔼 at ih; exists .clos (ƛ n) γ', .lam; unfold 𝕍; intro _ c ev₁c; exact ih (g.ext ev₁c) rfl
  | bot => subst_vars; exfalso; exact not_gtFn_bot gt
  | sub _ lt ih =>
    unfold 𝔼 at ih; have ⟨c, e_c, v_c⟩ := ih g rfl <| gt.sub lt; exact ⟨c, e_c, v_c.sub lt⟩
  | @conj _ _ _ w w' _ _ ih ih' =>
    by_cases hgt : GtFn w <;> by_cases hgt' : GtFn w'
    · unfold 𝔼 at ih ih'; have ⟨c, e_c, vwc⟩ := ih g rfl hgt; exists c, e_c
      have ⟨_, e_c', vw'c⟩ := ih' g rfl hgt'; rw [←e_c.determ e_c'] at vw'c; exact vwc.conj vw'c
    · unfold 𝔼 at ih; have ⟨.clos l γ, e_cl, vw⟩ := ih g rfl hgt; exists .clos l γ, e_cl
      have ⟨m', h'⟩ := WHNF.of_𝕍 vw; subst h'; apply vw.conj; exact 𝕍.of_not_gtFn hgt'
    · unfold 𝔼 at ih'; have ⟨.clos l' γ', e_cl', vw'⟩ := ih' g rfl hgt'; exists .clos l' γ', e_cl'
      have ⟨m', h'⟩ := WHNF.of_𝕍 vw'; subst h'; apply (𝕍.conj · vw'); exact 𝕍.of_not_gtFn hgt
    · cases gt.conj <;> contradiction

section
  variable {m : ∅ ⊢ ✶} {n : ∅‚ ✶ ⊢ ✶}

  -- https://plfa.github.io/Adequacy/#proof-of-denotational-adequacy
  theorem Eval.to_big_step (he : ℰ m = ℰ (ƛ n))
  : ∃ (Γ : Context) (n' : Γ‚ ✶ ⊢ ✶) (γ : ClosEnv Γ), ClosEnv.empty ⊢ m ⇓ .clos (ƛ n') γ
  := by
    have : ℰ (ƛ n) ∅ (⊥ ⇾ ⊥) := by apply_rules [Eval.fn, Eval.bot]
    rw [←he] at this; have := 𝔼.of_eval 𝔾.empty this; unfold 𝔼 at this
    have ⟨.clos _ γ, emc, v_cl⟩ := this ⟨_, _, .refl⟩
    have ⟨m', h'⟩ := WHNF.of_𝕍 v_cl; subst h'; exists _, m', γ

  theorem adequacy (he : ℰ m = ℰ (ƛ n)) : ∃ n', m —↠ ƛ n' := by
    have ⟨_, _, _, e⟩ := Eval.to_big_step he; exact e.reduce_of_cbn

  -- https://plfa.github.io/Adequacy/#call-by-name-is-equivalent-to-beta-reduction
  /--
  If the program can be reduced to a λ-abstraction via β-rules,
  then call-by-name can produce a value.
  -/
  theorem Eval.reduce_to_cbn (rs : m —↠ ƛ n)
  : ∃ (Δ : Context) (n' : Δ‚ ✶ ⊢ ✶) (δ : ClosEnv Δ), ClosEnv.empty ⊢ m ⇓ .clos (ƛ n') δ
  := soundness rs |> to_big_step
end

theorem Eval.reduce_iff_cbn {m : ∅ ⊢ ✶}
: ∃ (n : ∅‚ ✶ ⊢ ✶), m —↠ ƛ n
↔ ∃ (Δ : Context) (n' : Δ‚ ✶ ⊢ ✶) (δ : ClosEnv Δ), ClosEnv.empty ⊢ m ⇓ .clos (ƛ n') δ
:= by
  constructor
  · intro ⟨_, r⟩; exact reduce_to_cbn r
  · intro ⟨_, _, _, e⟩; exact Eval.reduce_of_cbn e
