import Mathlib
import Langlib.Automata.FiniteState.Equivalence.RegularDFAEquiv
import Langlib.Utilities.Homomorphism
import Langlib.Utilities.ClosurePredicates

/-! # Regular Closure Under Inverse String Homomorphism

This file proves that regular languages are closed under inverse string
homomorphism. Given a DFA `M` recognising `L` and a string homomorphism
`h : β → List α`, we construct a DFA for `h⁻¹(L) = {w ∈ β* | h*(w) ∈ L}` by
composing `h` into the transition function.

## Main results

- `inverseHomDFA` — the DFA for the preimage under a string homomorphism.
- `inverseHomDFA_correct` — correctness of the construction.
- `Language.IsRegular.inverseStringHomomorphism` — if `L` is regular then
  `{w | w.flatMap h ∈ L}` is regular.
-/

open Classical

variable {α β : Type*}

section InverseHomomorphism

variable {σ : Type*} [Fintype σ]

/-- DFA for the inverse homomorphism `h⁻¹(L)`.

Given a DFA `M` over `α` and a string homomorphism `h : β → List α`, the
new DFA has the same state set but transitions via `M.evalFrom q (h b)` — it
processes the entire image `h(b)` in one step. -/
noncomputable def inverseHomDFA (M : DFA α σ) (h : β → List α) : DFA β σ where
  step := fun q b => M.evalFrom q (h b)
  start := M.start
  accept := M.accept

variable (M : DFA α σ) (h : β → List α)

/-
The eval of the inverse-homomorphism DFA equals the original DFA's eval on the
    homomorphic image.
-/
omit [Fintype σ] in
lemma inverseHomDFA_eval (w : List β) :
    (inverseHomDFA M h).eval w = M.eval (w.flatMap h) := by
      unfold inverseHomDFA;
      induction' w using List.reverseRecOn with w x ih;
      · rfl;
      · simp_all +decide [ DFA.eval, DFA.evalFrom ]

omit [Fintype σ] in
@[simp] lemma inverseHomDFA_accept :
    (inverseHomDFA M h).accept = M.accept := rfl

/-
The inverse-homomorphism DFA accepts exactly the preimage of `M.accepts` under
    the string homomorphism.
-/
theorem inverseHomDFA_correct :
    (inverseHomDFA M h).accepts = { w : List β | w.flatMap h ∈ M.accepts } := by
      convert Set.ext _;
      intro w;
      convert ( DFA.mem_accepts _ );
      rw [ inverseHomDFA_eval ];
      rfl

end InverseHomomorphism

namespace Language

/-- Regular languages are closed under inverse string homomorphism. -/
theorem IsRegular.inverseStringHomomorphism {L : Language α}
    (hL : L.IsRegular) (h : β → List α) :
    Language.IsRegular { w : List β | w.flatMap h ∈ L } := by
  obtain ⟨σ, _, M, rfl⟩ := hL
  exact ⟨σ, inferInstance, inverseHomDFA M h, inverseHomDFA_correct M h⟩

end Language

/-- The class of regular languages is closed under inverse string homomorphism. -/
theorem RG_closedUnderInverseHomomorphism :
    ClosedUnderInverseHomomorphism is_RG := by
  intro α β _ L h hL
  exact is_RG_of_isRegular ((isRegular_of_is_RG hL).inverseStringHomomorphism h)
