import Langlib.Grammars.ContextFree.Toolbox
import Langlib.Grammars.ContextFree.EquivMathlibCFG
import Langlib.Classes.RecursivelyEnumerable.Closure.Reverse
import Langlib.Utilities.LanguageOperations
import Langlib.Utilities.ListUtils
import Langlib.Classes.ContextFree.Definition
import Langlib.Classes.ContextFree.Inclusion.ContextSensitive
import Langlib.Grammars.ContextFree.UnrestrictedCharacterization
import Langlib.Utilities.ClosurePredicates

/-! # Context-Free Closure Under Reversal

This file proves that context-free languages are closed under word reversal.

## Strategy

Instead of re-proving the derivation-reversal argument from scratch, we reuse
`grammar_language_reversal_grammar` (the unrestricted result) together with a
commutation lemma that shows reversing a CF grammar and then embedding it into
an unrestricted grammar gives the same result as embedding first and then reversing.

## Main declarations

- `reversal_CF_grammar`  — reverse the RHS of every CF rule
- `grammar_of_cfg_reversal_comm` — commutation with the CF→unrestricted embedding
- `CF_of_reverse_CF`
- `CF_of_reverse_CF_rev`
- `CF_reverse_iff_CF`
-/

variable {T : Type}

section reversal_defs

/-- Reverse the right-hand side of every rule in a context-free grammar. -/
def reversal_CF_grammar (g : CF_grammar T) : CF_grammar T :=
  CF_grammar.mk g.nt g.initial (List.map (
      fun r : g.nt × List (symbol T g.nt) => (r.fst, List.reverse r.snd)
    ) g.rules)

private lemma map_reverse_reverse_comp {nt : Type} (rules : List (nt × List (symbol T nt))) :
    List.map ((fun r => (r.1, r.2.reverse)) ∘ fun r => (r.1, r.2.reverse)) rules = rules := by
  induction rules with
  | nil => rfl
  | cons h t ih =>
    cases h
    simp [Function.comp, List.reverse_reverse, ih]

lemma dual_of_reversal_CF_grammar (g : CF_grammar T) :
    reversal_CF_grammar (reversal_CF_grammar g) = g := by
  cases g with
  | mk nt initial rules =>
    simp [reversal_CF_grammar, map_reverse_reverse_comp]

end reversal_defs

section commutation

/-- Reversing a CF grammar and then embedding it as an unrestricted grammar gives the
same grammar as embedding first and then applying the unrestricted reversal. -/
theorem grammar_of_cfg_reversal_comm (g : CF_grammar T) :
    grammar_of_cfg (reversal_CF_grammar g) = reversal_grammar (grammar_of_cfg g) := by
  cases g with
  | mk nt initial rules =>
    simp only [reversal_CF_grammar, grammar_of_cfg, reversal_grammar]
    congr 1
    rw [List.map_map, List.map_map]
    congr 1

end commutation

section closure

/-- The class of context-free languages is closed under reversal. -/
theorem CF_of_reverse_CF (L : Language T) :
    is_CF L → is_CF (L.reverse) := by
  intro h
  obtain ⟨g, hgL⟩ := is_CF_implies_is_CF_via_cfg h
  apply is_CF_via_cfg_implies_is_CF
  refine ⟨reversal_CF_grammar g, ?_⟩
  rw [CF_language_eq_grammar_language, grammar_of_cfg_reversal_comm,
      grammar_language_reversal_grammar, ← CF_language_eq_grammar_language, hgL]

/-- The converse direction of `CF_of_reverse_CF`, using that reversal is an involution. -/
theorem CF_of_reverse_CF_rev (L : Language T) :
    is_CF (L.reverse) → is_CF L := by
  intro h
  have h' := CF_of_reverse_CF L.reverse h
  simpa using h'

/-- A language is context-free iff its reversal is context-free. -/
@[simp] theorem CF_reverse_iff_CF (L : Language T) :
    is_CF (L.reverse) ↔ is_CF L := by
  constructor
  · exact CF_of_reverse_CF_rev L
  · exact CF_of_reverse_CF L

end closure

/-- The class of context-free languages is closed under reversal. -/
theorem CF_closedUnderReverse : ClosedUnderReverse (α := T) is_CF :=
  fun L hL => CF_of_reverse_CF L hL
