module

public import Langlib.Classes.ContextSensitive.Definition
public import Langlib.Classes.RecursivelyEnumerable.Closure.Homomorphism
public import Langlib.Utilities.ClosurePredicates

@[expose]
public section

/-! # Context-Sensitive Closure Under ε-Free Homomorphism

This file proves that context-sensitive languages are closed under string
homomorphisms that do not erase symbols.
-/

variable {α β : Type}

private theorem homLiftRule_context_sensitive (h : α → List β) {g : grammar α}
    {r : grule α g.nt}
    (hr : initial_epsilon_rule g r ∨ grule_noncontracting r) :
    initial_epsilon_rule (hom_grammar g h) (@homLiftRule α β g.nt r) ∨
      @grule_noncontracting β (g.nt ⊕ α) (@homLiftRule α β g.nt r) := by
  rcases hr with hε | hnc
  · left
    rcases hε with ⟨hL, hN, hR, hO⟩
    simp [initial_epsilon_rule, hom_grammar, homLiftRule, homLiftStr, hL, hN, hR, hO]
  · right
    simpa [grule_noncontracting, homLiftRule, homLiftStr]
      using hnc

private theorem homExpandRule_noncontracting (h : α → List β)
    (heps : IsEpsFreeHomomorphism h) (a : α) {N : Type} :
    grule_noncontracting (@homExpandRule α β N h a) := by
  simp [grule_noncontracting, homExpandRule]
  exact List.length_pos_iff.mpr (heps a)

private theorem hom_grammar_context_sensitive (g : grammar α) (h : α → List β)
    (heps : IsEpsFreeHomomorphism h) (hg : grammar_context_sensitive g) :
    grammar_context_sensitive (hom_grammar g h) := by
  refine ⟨fun r hr => ?_, ?_⟩
  · change r ∈
      (g.rules.map (@homLiftRule α β g.nt) ++
        (all_used_terminals g).map (@homExpandRule α β g.nt h)) at hr
    simp only [List.mem_append, List.mem_map] at hr
    rcases hr with ⟨r₀, hr₀, rfl⟩ | ⟨a, _ha, rfl⟩
    · exact homLiftRule_context_sensitive h (hg.1 r₀ hr₀)
    · exact Or.inr (homExpandRule_noncontracting h heps a)
  · -- The ε-rule on `hom_grammar` must be a lifted ε-rule of `g`.
    rintro ⟨r, hr, hε⟩
    change r ∈
      (g.rules.map (@homLiftRule α β g.nt) ++
        (all_used_terminals g).map (@homExpandRule α β g.nt h)) at hr
    simp only [List.mem_append, List.mem_map] at hr
    rcases hr with ⟨r₀, hr₀, rfl⟩ | ⟨a, _ha, rfl⟩
    · -- A lifted rule is the ε-rule iff `r₀` is the ε-rule of `g`.
      have hε₀ : initial_epsilon_rule g r₀ := by
        rcases hε with ⟨hL, hN, hR, hO⟩
        refine ⟨?_, ?_, ?_, ?_⟩
        · simpa [homLiftRule, homLiftStr] using hL
        · have : Sum.inl r₀.input_N = (Sum.inl g.initial : g.nt ⊕ α) := by
            simpa [hom_grammar, homLiftRule] using hN
          exact Sum.inl.inj this
        · simpa [homLiftRule, homLiftStr] using hR
        · simpa [homLiftRule, homLiftStr] using hO
      have hrhs := hg.2 ⟨r₀, hr₀, hε₀⟩
      -- Push `initial_not_on_rhs` forward along the lift.
      intro r' hr'
      change r' ∈
        (g.rules.map (@homLiftRule α β g.nt) ++
          (all_used_terminals g).map (@homExpandRule α β g.nt h)) at hr'
      simp only [List.mem_append, List.mem_map] at hr'
      rcases hr' with ⟨r₀', hr₀', rfl⟩ | ⟨a, _ha, rfl⟩
      · -- Lifted rule: `S = inl g.initial` in output iff `g.initial` in `r₀'`'s output.
        have := hrhs r₀' hr₀'
        simp only [hom_grammar, homLiftRule, homLiftStr]
        intro hmem
        rw [List.mem_map] at hmem
        obtain ⟨s, hs, hseq⟩ := hmem
        cases s with
        | terminal a => simp [homLiftSym] at hseq
        | nonterminal n =>
            simp only [homLiftSym, symbol.nonterminal.injEq, Sum.inl.injEq] at hseq
            exact this (hseq ▸ hs)
      · -- Expand rule: output is all terminals, no nonterminal `S`.
        simp [hom_grammar, homExpandRule]
    · -- An expand rule has `input_N = Sum.inr a ≠ Sum.inl g.initial`, never the ε-rule.
      rcases hε with ⟨_, hN, _, _⟩
      simp [hom_grammar, homExpandRule] at hN

/-- Context-sensitive languages are closed under ε-free string homomorphism. -/
public theorem CS_closedUnderEpsFreeHomomorphism :
    ClosedUnderEpsFreeHomomorphism is_CS := by
  intro α β _ _ L h heps hL
  obtain ⟨g, hcs, hgL⟩ := hL
  exact ⟨hom_grammar g h, hom_grammar_context_sensitive g h heps hcs,
    by rw [hom_grammar_language_epsfree g h heps, hgL]⟩
