import Spa.Language
import Spa.Lattice.FiniteMap
import Spa.Interp

namespace Spa

namespace Forward

variable (L : Type) [Lattice L] (prog : Program)

abbrev VariableValues : Type := FiniteMap String L prog.vars

abbrev StateVariables : Type := FiniteMap prog.State (VariableValues L prog) prog.states

def botV [FiniteHeightLattice L] : VariableValues L prog :=
  (⊥ : VariableValues L prog)

variable {L prog}

omit [Lattice L] in
theorem states_memKey (s : prog.State) (sv : StateVariables L prog) :
    FiniteMap.MemKey s sv :=
  FiniteMap.MemKey_iff.mpr (prog.states_complete s)

def variablesAt (s : prog.State) (sv : StateVariables L prog) :
    VariableValues L prog :=
  (FiniteMap.locate (states_memKey s sv)).1

omit [Lattice L] in
theorem variablesAt_mem (s : prog.State) (sv : StateVariables L prog) :
    (s, variablesAt s sv) ∈ sv :=
  (FiniteMap.locate (states_memKey s sv)).2

theorem variablesAt_le {sv₁ sv₂ : StateVariables L prog} (hle : sv₁ ≤ sv₂)
    (s : prog.State) : variablesAt s sv₁ ≤ variablesAt s sv₂ :=
  FiniteMap.le_of_mem_mem prog.states_nodup hle
    (variablesAt_mem s sv₁) (variablesAt_mem s sv₂)

variable [FiniteHeightLattice L]

def joinForKey (k : prog.State) (sv : StateVariables L prog) :
    VariableValues L prog :=
  (sv.valuesAt (prog.incoming k)).foldr (· ⊔ ·) (botV L prog)

theorem joinForKey_mono (k : prog.State) :
    Monotone (joinForKey (L := L) k) := by
  intro sv₁ sv₂ hle
  exact foldr_mono _ (FiniteMap.valuesAt_le hle (prog.incoming k)) (le_refl _)
    (fun b _ _ hab => sup_le_sup_right hab b)
    (fun a _ _ hab => sup_le_sup_left hab a)

def joinAll (sv : StateVariables L prog) : StateVariables L prog :=
  FiniteMap.generalizedUpdate id joinForKey prog.states sv

theorem joinAll_mono : Monotone (joinAll (L := L) (prog := prog)) :=
  FiniteMap.generalizedUpdate_monotone monotone_id joinForKey_mono

theorem joinAll_mem_eq {s : prog.State} {vs : VariableValues L prog}
    {sv : StateVariables L prog} (h : (s, vs) ∈ joinAll sv) :
    vs = joinForKey s sv :=
  FiniteMap.generalizedUpdate_mem_eq (prog.states_complete s) h

theorem variablesAt_joinAll (s : prog.State) (sv : StateVariables L prog) :
    variablesAt s (joinAll sv) = joinForKey s sv :=
  joinAll_mem_eq (variablesAt_mem s (joinAll sv))

/-! ### Lifting an interpretation to variable maps -/

variable [I : LatticeInterpretation L]

omit [FiniteHeightLattice L] in
instance : Interp (VariableValues L prog) (Env → Prop) where
  interp (vs : VariableValues L prog) (ρ : Env) : Prop :=
    ∀ (k : String) (l : L), (k, l) ∈ vs →
      ∀ (v : Value), Env.Mem (k, v) ρ → I.interp l v

theorem interp_botV_nil : ⟦ botV L prog ⟧ [] := by
  intro k l _ v hmem
  cases hmem

omit [FiniteHeightLattice L] in
theorem interp_sup {vs₁ vs₂ : VariableValues L prog} {ρ : Env}
    (h : ⟦ vs₁⟧ ρ ∨ ⟦ vs₂ ⟧ ρ) : ⟦ vs₁ ⊔ vs₂ ⟧ ρ := by
  intro k l hmem v hv
  obtain ⟨l₁, l₂, rfl, h₁, h₂⟩ := FiniteMap.mem_sup hmem
  rcases h with h | h
  · exact I.interp_sup v (Or.inl (h _ _ h₁ _ hv))
  · exact I.interp_sup v (Or.inr (h _ _ h₂ _ hv))

theorem interp_foldr {vs : VariableValues L prog}
    {vss : List (VariableValues L prog)} {ρ : Env}
    (hvs : ⟦ vs ⟧ ρ) (hmem : vs ∈ vss) :
    ⟦ vss.foldr (· ⊔ ·) (botV L prog) ⟧ ρ := by
  induction vss with
  | nil => cases hmem
  | cons vs' vss' ih =>
    rcases List.mem_cons.mp hmem with rfl | hmem'
    · exact interp_sup (Or.inl hvs)
    · exact interp_sup (Or.inr (ih hmem'))

end Forward

end Spa
