/-
Port of `Language.agda` (the `Program` record and re-exports).

Correspondence:
  Program record      ↦ structure Program (defs in the `Program` namespace)
  graph               ↦ Program.graph
  State               ↦ Program.State
  initialState        ↦ Program.initialState
  finalState          ↦ Program.finalState
  trace               ↦ Program.trace
  vars, vars-Unique   ↦ Program.vars, Program.vars_nodup
                        (Finset.toList + Finset.nodup_toList replace
                        `to-Listˢ` and the intrinsic MapSet uniqueness)
  states, states-complete, states-Unique
                      ↦ Program.states, .states_complete, .states_nodup
  code                ↦ Program.code
  _≟_, _≟ᵉ_           ↦ (instances, automatic for Fin/products)
  incoming            ↦ Program.incoming
  initialState-pred-∅ ↦ Program.incoming_initialState_eq_nil
  edge⇒incoming       ↦ Program.mem_incoming_of_edge
-/
import Spa.Language.Base
import Spa.Language.Semantics
import Spa.Language.Graphs
import Spa.Language.Traces
import Spa.Language.Properties
import Mathlib.Data.Finset.Sort
import Mathlib.Data.String.Basic

namespace Spa

structure Program where
  rootStmt : Stmt

namespace Program

variable (p : Program)

def graph : Graph := Graph.wrap (buildCfg p.rootStmt)

abbrev State : Type := p.graph.Index

def initialState : p.State := (buildCfg p.rootStmt).wrapInput

def finalState : p.State := (buildCfg p.rootStmt).wrapOutput

/-- Agda: `Program.trace`. -/
theorem trace {ρ : Env} (h : EvalStmt [] p.rootStmt ρ) :
    Trace p.graph p.initialState p.finalState [] ρ := by
  obtain ⟨i₁, h₁, i₂, h₂, tr⟩ := EndToEndTrace.wrap (buildCfg_sufficient h)
  rw [Graph.wrap_inputs, List.mem_singleton] at h₁
  rw [Graph.wrap_outputs, List.mem_singleton] at h₂
  subst h₁; subst h₂
  exact tr

/-- Agda: `vars` (via `vars-Set = Stmt-vars rootStmt`). `Finset.toList` is
noncomputable, so the variables are listed in sorted order instead — this is
the computable stand-in for MapSet's `to-List`. -/
def vars : List String := p.rootStmt.vars.sort (· ≤ ·)

/-- Agda: `vars-Unique`. -/
theorem vars_nodup : p.vars.Nodup := Finset.sort_nodup _ _

def states : List p.State := p.graph.indices

/-- Agda: `states-complete`. -/
theorem states_complete (s : p.State) : s ∈ p.states := p.graph.mem_indices s

/-- Agda: `states-Unique`. -/
theorem states_nodup : p.states.Nodup := p.graph.nodup_indices

/-- Agda: `code`. -/
def code (st : p.State) : List BasicStmt := p.graph.nodes st

/-- Agda: `incoming`. -/
def incoming (s : p.State) : List p.State := p.graph.predecessors s

/-- Agda: `initialState-pred-∅`. -/
theorem incoming_initialState_eq_nil : p.incoming p.initialState = [] :=
  Graph.wrap_predecessors_eq_nil (buildCfg p.rootStmt) p.initialState
    (by rw [Graph.wrap_inputs]; exact List.mem_singleton_self _)

/-- Agda: `edge⇒incoming`. -/
theorem mem_incoming_of_edge {s₁ s₂ : p.State}
    (h : (s₁, s₂) ∈ p.graph.edges) : s₁ ∈ p.incoming s₂ :=
  p.graph.mem_predecessors_of_edge h

end Program

end Spa
