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

import Plfl.Init
import Plfl.Untyped.Denotational.Compositional

namespace Soundness

open Untyped Untyped.Notation
open Untyped.Subst
open Substitution (Rename Subst)
open Denotational Denotational.Notation
open Compositional Compositional.Notation

-- https://plfa.github.io/Soundness/#simultaneous-substitution-preserves-denotations
namespace Env
  /--
  `Eval δ σ γ` means that for every variable `i`,
  `σ i` results in the same value as the one for `i` in the original environment `γ`.
  -/
  abbrev Eval (δ : Env Δ) (σ : Subst Γ Δ) (γ : Env Γ) : Prop := ∀ (i : Γ ∋ ✶), δ ⊢ σ i ￬ γ i
end Env

namespace Notation
  scoped notation:30 δ " `⊢ " σ " ￬ " γ:51 => Env.Eval δ σ γ
end Notation

open Notation

section
  variable {γ : Env Γ} {δ : Env Δ}

  lemma subst_ext (σ : Subst Γ Δ) (d : δ `⊢ σ ￬ γ) : δ`‚ v `⊢ exts σ ￬ (γ`‚ v)
  | .z => .var
  | .s i => rename_pres .s (λ _ => .refl) (d i)

  /-- The result of evaluation is conserved after simultaneous substitution. -/
  theorem subst_pres (σ : Subst Γ Δ) (s : δ `⊢ σ ￬ γ) (d : γ ⊢ m ￬ v)
  : δ ⊢ subst σ m ￬ v
  := by induction d generalizing Δ with
  | var => apply s
  | ap _ _ ih ih'=> exact (ih σ s).ap (ih' σ s)
  | fn _ ih => refine .fn ?_; apply ih (exts σ); exact subst_ext σ s
  | bot => exact .bot
  | conj _ _ ih ih' => exact (ih σ s).conj (ih' σ s)
  | sub _ lt ih => exact (ih σ s).sub lt

  -- https://plfa.github.io/Soundness/#single-substitution-preserves-denotations
  /-- The result of evaluation is conserved after single substitution. -/
  theorem subst₁_pres (dn : γ`‚ v ⊢ n ￬ w) (dm : γ ⊢ m ￬ v) : γ ⊢ n⟦m⟧ ￬ w
  := subst_pres (subst₁σ m) (λ | .z => dm | .s _ => .var) dn

  -- https://plfa.github.io/Soundness/#reduction-preserves-denotations
  theorem reduce_pres (d : γ ⊢ m ￬ v) (r : m —→ n) : γ ⊢ n ￬ v := by induction d with
  | var => contradiction
  | bot => exact .bot
  | fn _ ih => cases r with | lamζ r => exact (ih r).fn
  | conj _ _ ih ih' => exact (ih r).conj (ih' r)
  | sub _ lt ih => exact (ih r).sub lt
  | ap d d' ih ih' => cases r with
    | apξ₁ r => exact (ih r).ap d'
    | apξ₂ r => exact d.ap (ih' r)
    | lamβ => exact subst₁_pres (lam_inv d) d'

  -- https://plfa.github.io/Soundness/#renaming-reflects-meaning
  theorem rename_reflect {ρ : Rename Γ Δ} (lt : δ ∘ ρ `⊑ γ) (d : δ ⊢ rename ρ m ￬ v)
  : γ ⊢ m ￬ v
  := by
    generalize hx : rename ρ m = x at *
    induction d generalizing Γ with
    | bot => exact .bot
    | var => cases m with (injection hx; try subst_vars)
      | var i => exact .sub .var <| (var_inv .var).trans (lt i)
    | ap _ _ ih ih' => cases m with injection hx
      | ap => rename_i hx hx'; exact (ih lt hx).ap (ih' lt hx')
    | fn _ ih => cases m with injection hx
      | lam => refine .fn ?_; apply ih (ρ := ext ρ) (ext_sub' ρ lt); trivial
    | conj _ _ ih ih' => exact (ih lt hx).conj (ih' lt hx)
    | sub _ lt' ih => exact (ih lt hx).sub lt'

  theorem rename_shift_reflect (d : γ`‚ u ⊢ shift m ￬ v) : γ ⊢ m ￬ v :=
    rename_reflect (by rfl) d
end

section
  -- https://plfa.github.io/Soundness/#substitution-reflects-denotations-the-variable-case
  /-- `const` is an `Env` with a single non-trivial mapping entry: from `i` to `v`. -/
  def Env.const (i : Γ ∋ ✶) (v : Value) : Env Γ | j => if i = j then v else ⊥

  variable {γ δ : Env Δ}

  lemma subst_reflect_var {i : Γ ∋ ✶} {σ : Subst Γ Δ} (d : γ ⊢ σ i ￬ v)
  : ∃ (δ : Env Γ), (γ `⊢ σ ￬ δ) ∧ (δ ⊢ ` i ￬ v)
  := by
    exists Env.const i v; unfold Env.const; constructor
    · intro j; by_cases h : i = j <;> simp only [h] at *
      · exact d
      · exact .bot
    · convert Eval.var; simp only [Env.const, ite_true]

  variable {γ₁ γ₂ : Env Γ} {σ : Subst Γ Δ}

  -- https://plfa.github.io/Soundness/#substitutions-and-environment-construction
  lemma subst_bot : γ `⊢ σ ￬ ⊥ | _ => .bot

  lemma subst_conj (d₁ : γ `⊢ σ ￬ γ₁) (d₂ : γ `⊢ σ ￬ γ₂) : γ `⊢ σ ￬ γ₁ ⊔ γ₂
  | i => (d₁ i).conj (d₂ i)
end

-- https://plfa.github.io/Soundness/#simultaneous-substitution-reflects-denotations
/-- Simultaneous substitution reflects denotations. -/
theorem subst_reflect {σ : Subst Γ Δ} (d : δ ⊢ l ￬ v) (h : ⟪σ⟫ m = l)
: ∃ (γ : Env Γ), (δ `⊢ σ ￬ γ) ∧ (γ ⊢ m ￬ v)
:= by
  induction d generalizing Γ with
  | bot => exists ⊥; exact ⟨subst_bot, .bot⟩
  | var => cases m with try contradiction
    | var j => apply subst_reflect_var; convert Eval.var
  | ap d d' ih ih' => rename_i l' _ _ m'; cases m with try contradiction
    | var => apply subst_reflect_var; convert d.ap d'
    | ap =>
      injection h; rename_i h h'
      let ⟨γ, dγ, dm⟩ := ih h; let ⟨γ', dγ', dm'⟩ := ih' h'; exists γ ⊔ γ'; constructor
      · exact subst_conj dγ dγ'
      · exact (sub_env dm <| Env.Sub.conjR₁ γ γ').ap (sub_env dm' <| Env.Sub.conjR₂ γ γ')
  | fn d ih => cases m with try contradiction
    | var => apply subst_reflect_var; convert d.fn
    | lam =>
      injection h; rename_i h; let ⟨γ, dγ, dm⟩ := ih h; exists γ.init; constructor
      · intro i; exact rename_shift_reflect <| dγ i.s
      · rw [Env.init_last γ] at dm; refine .fn (up_env dm ?_); exact var_inv <| dγ .z
  | conj _ _ ih ih' =>
    let ⟨γ, dγ, dm⟩ := ih h; let ⟨γ', dγ', dm'⟩ := ih' h; exists γ ⊔ γ'; constructor
    · exact subst_conj dγ dγ'
    · exact (sub_env dm <| Env.Sub.conjR₁ γ γ').conj (sub_env dm' <| Env.Sub.conjR₂ γ γ')
  | sub _ lt' ih => let ⟨γ, dγ, dm⟩ := ih h; exact ⟨γ, dγ, dm.sub lt'⟩

-- https://plfa.github.io/Soundness/#single-substitution-reflects-denotations
lemma subst₁σ_reflect {δ : Env Δ} {γ : Env (Δ‚ ✶)} (d : δ `⊢ subst₁σ m ￬ γ)
: ∃ w, (γ `⊑ δ`‚ w) ∧ (δ ⊢ m ￬ w)
:= by
  exists γ.last; constructor
  · intro
    | .z => rfl
    | .s i => apply var_inv (d i.s)
  · exact d .z

/-- Single substitution reflects denotations. -/
theorem subst₁_reflect {δ : Env Δ} (d : δ ⊢ n⟦m⟧ ￬ v) : ∃ w, (δ ⊢ m ￬ w) ∧ (δ`‚ w ⊢ n ￬ v)
:= by
  have ⟨γ, dγ, dn⟩ := subst_reflect d rfl; have ⟨w, ltw, dw⟩ := subst₁σ_reflect dγ
  exists w, dw; exact sub_env dn ltw

-- https://plfa.github.io/Soundness/#reduction-reflects-denotations-1
theorem reduce_reflect {γ : Env Γ} {m n : Γ ⊢ a} (d : γ ⊢ n ￬ v) (r : m —→ n) : γ ⊢ m ￬ v := by
  induction r generalizing v with
  | lamβ =>
    rename_i n u; generalize hx : n⟦u⟧ = x at *
    induction d with
    | var => apply beta; rw [hx]; exact .var
    | ap d d' => apply beta; rw [hx]; exact d.ap d'
    | fn d => apply beta; rw [hx]; exact d.fn
    | bot => exact .bot
    | conj _ _ ih ih' => exact (ih hx).conj (ih' hx)
    | sub _ lt ih => exact (ih hx).sub lt
  | lamζ r ihᵣ =>
    rename_i _ n'; generalize hx : (ƛ n') = x at *
    induction d with try contradiction
    | fn d ih => injection hx; subst_vars; exact (ihᵣ <| lam_inv d.fn).fn
    | bot => exact .bot
    | conj _ _ ih ih' => exact (ih r ihᵣ hx).conj (ih' r ihᵣ hx)
    | sub _ lt ih => exact (ih r ihᵣ hx).sub lt
  | apξ₁ r ihᵣ =>
    rename_i l m; generalize hx : l □ m = x at *
    induction d with try contradiction
    | ap d d' _ _ => injection hx; subst_vars; exact (ihᵣ d).ap d'
    | bot => exact .bot
    | conj _ _ ih ih' => exact (ih r ihᵣ hx).conj (ih' r ihᵣ hx)
    | sub _ lt ih => exact (ih r ihᵣ hx).sub lt
  | apξ₂ r ihᵣ =>
    rename_i m l; generalize hx : l □ m = x at *
    induction d with try contradiction
    | ap d d' _ _ => injection hx; subst_vars; exact d.ap <| ihᵣ d'
    | bot => exact .bot
    | conj _ _ ih ih' => exact (ih r ihᵣ hx).conj (ih' r ihᵣ hx)
    | sub _ lt ih => exact (ih r ihᵣ hx).sub lt
  where
    beta {Γ m n v} {γ : Env Γ} (d : γ ⊢ n⟦m⟧ ￬ v) : γ ⊢ (ƛ n) □ m ￬ v := by
      let ⟨v, dm, dn⟩ := subst₁_reflect d; exact dn.fn.ap dm

-- https://plfa.github.io/Soundness/#reduction-implies-denotational-equality
theorem reduce_eq (r : m —→ n) : ℰ m = ℰ n := by
  ext; exact ⟨(reduce_pres · r), (reduce_reflect · r)⟩

theorem soundness (rs : m —↠ ƛ n) : ℰ m = ℰ (ƛ n) := by
  induction rs using Relation.ReflTransGen.head_induction_on with
  | refl => rfl
  | head r _ ih => convert ih using 1; exact reduce_eq r
