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

import Plfl.Init
import Plfl.Untyped.Denotational.Adequacy

namespace ContextualEquivalence

open Untyped Untyped.Notation
open Adequacy
open BigStep BigStep.Notation
open Compositional
open Denotational
open Soundness (soundness)

-- https://plfa.github.io/ContextualEquivalence/#contextual-equivalence
abbrev Terminates (m : Γ ⊢ ✶) : Prop := ∃ n, m —↠ ƛ n

/--
Two terms are contextually equivalent
if plugging them into the same holed program always produces two programs
that either terminate or diverge together.
-/
abbrev ContextualEquiv (m n : Γ ⊢ ✶) : Prop :=
  ∀ {c : Holed Γ ∅}, Terminates (c.plug m) = Terminates (c.plug n)

namespace Notation
  scoped infixl:25 "≃ₕ" => ContextualEquiv
end Notation

open Notation

-- https://plfa.github.io/ContextualEquivalence/#denotational-equivalence-implies-contextual-equivalence
lemma Terminates.of_eq_ℰ {m n : Γ ⊢ ✶} {c : Holed Γ ∅} (he : ℰ m = ℰ n) :
Terminates (c.plug m) → Terminates (c.plug n)
:= by
  intro ⟨n', rs⟩; apply Eval.reduce_iff_cbn.mpr; apply Eval.to_big_step
  calc ℰ (c.plug n)
    _ = ℰ (c.plug m) := compositionality he |>.symm
    _ = ℰ (ƛ n') := soundness rs

theorem ContextualEquiv.of_eq_ℰ {m n : Γ ⊢ ✶} (he : ℰ m = ℰ n) : m ≃ₕ n := by
  intro c; simp only [eq_iff_iff]; constructor
  · exact Terminates.of_eq_ℰ he
  · exact Terminates.of_eq_ℰ he.symm
