import Spa.Analysis.Forward
import Spa.Lattice.Finset
import Spa.Showable

namespace Spa

open Forward

instance {n : ℕ} : Showable (Finset (Fin n)) :=
  ⟨fun s =>
    "{" ++ (List.finRange n).foldr
        (fun i rest => if i ∈ s then show' i ++ ", " ++ rest else rest) ""
        ++ "}"⟩

abbrev DefSet (prog : Program) : Type := Finset prog.State

namespace ReachingAnalysis

variable (prog : Program)

def eval (s : prog.State) (vs : VariableValues (DefSet prog) prog) : VariableValues (DefSet prog) prog :=
  match prog.code s with
  | none => vs
  | some bs =>
    match bs with
    | .assign k _ => FiniteMap.generalizedUpdate id (fun _ _ => {s}) [k] vs
    | .noop => vs

lemma eval_mono (s : prog.State) :
    Monotone (eval prog s) := by
  intros vs₁ vs₂ hle
  unfold eval; split <;> try simpa
  split <;> try simpa
  apply FiniteMap.generalizedUpdate_monotone monotone_id (fun _ => monotone_const)
  assumption

instance stmtEvaluator : StmtEvaluator (DefSet prog) prog :=
  ⟨eval prog, eval_mono prog⟩

def output : String :=
  show' (result (DefSet prog) prog)

/-- The statements a trace executed, paired with the state each executed at,
    most recent first (matching `LastAssign`, which scans for the most recent
    assignment). This is `Trace.steps` (chronological) reversed, so facts about
    concatenating traces reduce to mathlib's `List.append`/`List.reverse` lemmas. -/
abbrev Run (prog : Program) : Type := List (prog.State × BasicStmt)

@[aesop unsafe cases]
inductive LastAssign (prog : Program) (x : String) : Run prog → prog.State → Prop
  | here (s : prog.State) (e : Expr) (rest : Run prog) :
      LastAssign prog x ((s, .assign x e) :: rest) s
  | there (s : prog.State) (bs : BasicStmt) (hc : prog.code s = some bs)
      (rest : Run prog) {n : prog.State} :
      (∀ e, bs ≠ .assign x e) → LastAssign prog x rest n →
      LastAssign prog x ((s, bs) :: rest) n

def runOfTraceₗ {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
    (tr : Traceₗ prog.cfg s₁ s₂ ρ₁ ρ₂) : Run prog :=
  tr.steps.reverse

def runOfTrace {s₁ s₂ : prog.State} {ρ₁ ρ₂ : Env}
    (tr : Trace prog.cfg s₁ s₂ ρ₁ ρ₂) : Run prog :=
  tr.steps.reverse

instance stateInterp : StateInterpretation (DefSet prog) prog where
  Proj := Run prog
  Pre := @runOfTraceₗ prog
  Post := @runOfTrace prog

  interp vs run := ∀ (x : String) (assigners : DefSet prog), (x, assigners) ∈ vs →
    ∀ (n : prog.State), LastAssign prog x run n → n ∈ assigners
  interp_sup := by
    intro vs₁ vs₂ run h x assigners hmem n hla
    obtain ⟨a₁, a₂, rfl, h₁, h₂⟩ := FiniteMap.mem_sup hmem
    aesop (add simp Finset.mem_union)
  interp_inf := by
    intro vs₁ vs₂ run h x assigners hmem n hla
    obtain ⟨a₁, a₂, rfl, h₁, h₂⟩ := FiniteMap.mem_inf hmem
    aesop (add simp Finset.mem_inter)

  post_pre := by
    intro vs s₁ s₂ s₃ ρ₁ ρ₂ tr hedge hvs
    simpa [runOfTrace, runOfTraceₗ] using hvs

private lemma valid_step (s : prog.State) {ρ₁ ρ₂ : Env}
    {obs : Option BasicStmt} (hcode : prog.code s = obs)
    (hbs : EvalBasicStmtOpt ρ₁ obs ρ₂)
    {vs : VariableValues (DefSet prog) prog} {run : Run prog}
    (hvs : ⟦vs⟧ run) :
    ⟦eval prog s vs⟧ ((hbs.steps s).reverse ++ run) := by
  cases hbs with
  | none => simpa [eval, hcode, EvalBasicStmtOpt.steps] using hvs
  | some hbs =>
  cases hbs with
  | noop =>
    simp [eval, hcode, EvalBasicStmtOpt.steps]
    intro x assigners hmem n hla; aesop
  | assign x e v hev =>
    simp [eval, hcode, EvalBasicStmtOpt.steps]; intro k assigners hmem n hla
    by_cases hx : k = x
    · subst hx
      have hd := FiniteMap.generalizedUpdate_mem_eq (List.mem_singleton.mpr rfl) hmem
      rcases hla <;> simp [hd] <;> aesop
    · have hmem' := FiniteMap.generalizedUpdate_not_mem_backward
        (fun hc => hx (List.mem_singleton.mp hc)) hmem
      aesop

instance validStateEvaluator : ValidStateEvaluator (DefSet prog) prog where
  valid := by
    intro s₁ s₂ ρ₁ ρ₂ ρ₃ vs tr hbs hvs
    show ⟦eval prog s₂ vs⟧ (runOfTrace prog (tr ++ hbs))
    simpa [runOfTrace, runOfTraceₗ] using valid_step prog s₂ rfl hbs hvs
  botV_init := by intro x assigners _ n hla; cases hla

theorem analyze_correct {ρ : Env} (hrun : EvalStmt [] prog.rootStmt ρ) :
    ⟦ variablesAt prog.finalState (result (DefSet prog) prog) ⟧
      (runOfTrace prog (prog.trace hrun)) :=
  Forward.analyze_correct' (DefSet prog) prog hrun

theorem analyze_correct_at {ρf : Env} (hrun : EvalStmt [] prog.rootStmt ρf)
    {s : prog.State} {ρin ρout : Env}
    (hr : Reaches (prog.trace hrun) s ρin ρout) :
    ⟦ joinForKey s (result (DefSet prog) prog) ⟧ (runOfTraceₗ prog hr.pre)
      ∧ ⟦ variablesAt s (result (DefSet prog) prog) ⟧ (runOfTrace prog hr.post) :=
  Forward.analyze_correct_at (DefSet prog) prog hrun hr

end ReachingAnalysis

end Spa
