import Langlib.Classes.ContextSensitive.Inclusion.RecursivelyEnumerable
import Langlib.Grammars.ContextSensitive.UnrestrictedCharacterization
import Langlib.Classes.RecursivelyEnumerable.Closure.Concatenation
import Langlib.Utilities.ClosurePredicates

/-! # Context-Sensitive Concatenation Construction

This file sketches a concatenation construction for context-sensitive languages by reusing the unrestricted one.

## Main declarations

- `bonus_CS_of_CS_c_CS`
-/

variable {T : Type}


private def wrap_CS_rule₁ {N₁ : Type} (N₂ : Type) (r : csrule T N₁) :
  csrule T (nnn T N₁ N₂) :=
csrule.mk
  (List.map (wrap_symbol₁ N₂) r.context_left)
  (Sum.inl (some (Sum.inl r.input_nonterminal)))
  (List.map (wrap_symbol₁ N₂) r.context_right)
  (List.map (wrap_symbol₁ N₂) r.output_string)

private def wrap_CS_rule₂ {N₂ : Type} (N₁ : Type) (r : csrule T N₂) :
  csrule T (nnn T N₁ N₂) :=
csrule.mk
  (List.map (wrap_symbol₂ N₁) r.context_left)
  (Sum.inl (some (Sum.inr r.input_nonterminal)))
  (List.map (wrap_symbol₂ N₁) r.context_right)
  (List.map (wrap_symbol₂ N₁) r.output_string)

private def CS_rules_for_terminals₁ (N₂ : Type) (g : CS_grammar T) :
  List (csrule T (nnn T g.nt N₂)) :=
List.map (fun t => csrule.mk [] (Sum.inr (Sum.inl t)) [] [symbol.terminal t])
  (all_used_terminals (grammar_of_csg g))

private def CS_rules_for_terminals₂ (N₁ : Type) (g : CS_grammar T) :
  List (csrule T (nnn T N₁ g.nt)) :=
List.map (fun t => csrule.mk [] (Sum.inr (Sum.inr t)) [] [symbol.terminal t])
  (all_used_terminals (grammar_of_csg g))

private def big_CS_grammar (g₁ g₂ : CS_grammar T) : CS_grammar T where
  nt := nnn T g₁.nt g₂.nt
  initial := Sum.inl none
  rules :=
    (csrule.mk [] (Sum.inl none) [] [
      symbol.nonterminal (Sum.inl (some (Sum.inl g₁.initial))),
      symbol.nonterminal (Sum.inl (some (Sum.inr g₂.initial)))]
    ) :: (
      (List.map (wrap_CS_rule₁ g₂.nt) g₁.rules ++ List.map (wrap_CS_rule₂ g₁.nt) g₂.rules) ++
      (CS_rules_for_terminals₁ g₂.nt g₁ ++ CS_rules_for_terminals₂ g₁.nt g₂)
    )
  output_nonempty := by
    intro r hr
    simp [List.mem_cons] at hr
    rcases hr with rfl | hr
    · nofun
    · rcases hr with ⟨r', hr', rfl⟩ | ⟨r', hr', rfl⟩ | hr | hr
      · -- wrap_CS_rule₁
        simp [wrap_CS_rule₁, csrule.output_string]
        exact g₁.output_nonempty r' hr'
      · -- wrap_CS_rule₂
        simp [wrap_CS_rule₂, csrule.output_string]
        exact g₂.output_nonempty r' hr'
      · -- CS_rules_for_terminals₁
        simp [CS_rules_for_terminals₁, List.mem_map] at hr
        obtain ⟨t, _, rfl⟩ := hr
        simp
      · -- CS_rules_for_terminals₂
        simp [CS_rules_for_terminals₂, List.mem_map] at hr
        obtain ⟨t, _, rfl⟩ := hr
        simp

/-
PROVIDED SOLUTION
Rewrite using CS_language_eq_grammar_language to get grammar_language (grammar_of_csg (big_CS_grammar g₁ g₂)) = grammar_language (big_grammar (grammar_of_csg g₁) (grammar_of_csg g₂)).

Then show that grammar_of_csg (big_CS_grammar g₁ g₂) = big_grammar (grammar_of_csg g₁) (grammar_of_csg g₂).

This is a definitional equality: both grammars have:
- Same nt type: nnn T g₁.nt g₂.nt
- Same initial: Sum.inl none
- Same rules (need to show the rule lists are equal)

For the rules, unfold everything (grammar_of_csg, big_CS_grammar, big_grammar, wrap_CS_rule₁, wrap_CS_rule₂, CS_rules_for_terminals₁, CS_rules_for_terminals₂, wrap_grule₁, wrap_grule₂, rules_for_terminals₁, rules_for_terminals₂) and use:
- List.map_map to compose functions
- List.map_append for map over concatenation
- ext/funext to show the composed functions are equal elementwise
- For each csrule r, grammar_of_csg maps it to grule with output_string = r.context_left ++ r.output_string ++ r.context_right. Then wrap_grule₁ maps this grule by mapping wrap_symbol₁ over everything, giving output = map wrap₁ (r.context_left ++ r.output_string ++ r.context_right) = map wrap₁ r.context_left ++ map wrap₁ r.output_string ++ map wrap₁ r.context_right. On the other side, wrap_CS_rule₁ maps the csrule first by wrapping, then grammar_of_csg maps the result giving output = map wrap₁ r.context_left ++ map wrap₁ r.output_string ++ map wrap₁ r.context_right. So they're equal.

For terminal rules: CS_rules_for_terminals₁ uses all_used_terminals (grammar_of_csg g₁), and rules_for_terminals₁ also uses all_used_terminals. The CS terminal rules have context_left = [], context_right = [], output_string = [terminal t]. After grammar_of_csg, they become grule.mk [] (Sum.inr (Sum.inl t)) [] ([] ++ [terminal t] ++ []) = grule.mk [] ... [] [terminal t]. This matches rules_for_terminals₁.

So unfold everything and use `congr`, `ext`, `simp [List.map_append, List.map_map]`, and `rfl`.
-/
private lemma big_CS_grammar_same_language (g₁ g₂ : CS_grammar T) :
  CS_language (big_CS_grammar g₁ g₂) = grammar_language (big_grammar (grammar_of_csg g₁) (grammar_of_csg g₂)) :=
by
  convert CS_language_eq_grammar_language ( big_CS_grammar g₁ g₂ ) using 2;
  unfold grammar_of_csg big_grammar big_CS_grammar;
  unfold CS_rules_for_terminals₁ CS_rules_for_terminals₂; simp +decide [ List.map_append, List.map_map ] ;
  congr! 2;
  · ext; simp +decide [ wrap_CS_rule₁ ] ;
    exact congr_arg₂ _ ( by aesop ) ( by aesop );
  · unfold wrap_CS_rule₂; simp +decide [ List.map_append, List.map_map ] ;
    intros; congr! 1;
    rw [ ← List.map_append, ← List.map_append ];
    rfl

theorem CS_of_CS_c_CS (L₁ : Language T) (L₂ : Language T) :
  is_CS L₁  ∧  is_CS L₂   →   is_CS (L₁ * L₂)   :=
by
  rintro ⟨h₁, h₂⟩
  obtain ⟨g₁, eq_L₁⟩ := is_CS_implies_is_CS_via_csg h₁
  obtain ⟨g₂, eq_L₂⟩ := is_CS_implies_is_CS_via_csg h₂
  rw [CS_language_eq_grammar_language g₁] at eq_L₁
  rw [CS_language_eq_grammar_language g₂] at eq_L₂

  apply is_CS_via_csg_implies_is_CS
  use big_CS_grammar g₁ g₂
  rw [big_CS_grammar_same_language]

  apply Set.Subset.antisymm
  ·
    intros w hyp
    rw [←eq_L₁]
    rw [←eq_L₂]
    exact in_concatenated_of_in_big hyp
  ·
    intros w hyp
    rw [←eq_L₁] at hyp
    rw [←eq_L₂] at hyp
    exact in_big_of_in_concatenated hyp

/-- The class of context-sensitive languages is closed under concatenation. -/
theorem CS_closedUnderConcatenation : ClosedUnderConcatenation (α := T) is_CS :=
  fun L₁ L₂ h₁ h₂ => CS_of_CS_c_CS L₁ L₂ ⟨h₁, h₂⟩
