import RoleFlow.Recipes
import RoleFlow.Search

/-! # The argument-swapping front end

A swappable operation (`Swap`) has two arguments of one sort and a semi-inverse that solves the
first; in the flow signature, `add`. A program that adds `a + b` where `b` is to be consumed is
not typed as written; `swapArgs` rewrites it to `add(b, a)`.

The choice is made by forward inference. Every line contributes its forward form, its solved
form if it has a semi-inverse, and, for a swappable `o(a, b)`, the swapped solved form
`b ← (c, a)`. Starting from the weights and the returned variables, an equation fires when its
reads are known and its results unknown, until nothing fires (`fwdKnown`). The pass swaps
exactly the lines whose swapped form fired. The swap preserves the meaning for a model where
the swappable operations commute (`FrontSem`), and if any swap of the program is typed, the
one chosen here is (`FrontComplete`). -/

namespace RoleFlow

open Search

variable {S : Type}

/-- The swappable operations of a signature: two arguments of one sort, and a semi-inverse that
solves the first. -/
class Swap (Sg : Sig S) where
  swappable : ∀ {as bs : List S}, Sg.Op as bs → Bool
  sorts : ∀ {as bs : List S} (o : Sg.Op as bs), swappable o → ∃ a, as = [a, a]
  k_one : ∀ {as bs : List S} (o : Sg.Op as bs), swappable o → ∃ si, Sg.inv o = some si ∧ si.k = 1

variable {Sg : Sig S} [Swap Sg]

/-- How many leading arguments the semi-inverse of `l` solves, if there is one. -/
def kOf (l : Line Sg) : Option ℕ := (Sg.inv l.op).map (·.k)

/-- The solved form of the line `l`, numbered `i`, at scope `n`. -/
def solvedEqns (i n : ℕ) (l : Line Sg) : List Eqn :=
  match kOf l with
  | some k => [⟨l.args.take k, l.ys n ++ l.args.drop k, i, false⟩]
  | none => []

/-- The swapped solved form: a swappable `o(a, b)` solved for `b`. -/
def swappedEqns (i n : ℕ) (l : Line Sg) : List Eqn :=
  match Swap.swappable l.op, l.args with
  | true, [a, b] => [⟨[b], l.ys n ++ [a], i, true⟩]
  | _, _ => []

/-- The equations of the line `l`: forward, solved, and swapped solved. -/
def lineEqns (i n : ℕ) (l : Line Sg) : List Eqn :=
  ⟨l.ys n, l.args, i, false⟩ :: solvedEqns i n l ++ swappedEqns i n l

/-- The equations of the lines at scope `n`, numbered from `i`. -/
def eqns : ℕ → ℕ → List (Line Sg) → List Eqn
  | _, _, [] => []
  | i, n, l :: ls => lineEqns i n l ++ eqns (i + 1) (n + l.bs.length) ls

/-- Swap the arguments of `l` if it is swappable and `s` holds. -/
def swapIf (s : Bool) (l : Line Sg) : Line Sg :=
  if s && Swap.swappable l.op && l.args.length == 2 then { l with args := l.args.reverse } else l

/-- Swap the lines whose number (from `i`) satisfies `sw`. -/
def swapLines (sw : ℕ → Bool) : ℕ → List (Line Sg) → List (Line Sg)
  | _, [] => []
  | i, l :: ls => swapIf (sw i) l :: swapLines sw (i + 1) ls

/-- The lines whose swapped form fired. -/
def swapSet (P : Prog Sg) : Std.HashSet ℕ :=
  let E := eqns 0 P.ctx₀.length P.lines
  let K₀ := insertAll ∅ (List.range P.wts.length ++ P.ret)
  Std.HashSet.ofList (((fwdKnown E (E.length + 1) K₀ []).2.filter (·.swap)).map (·.line))

/-- The front end: swap the arguments of the lines whose swapped solved form fired. -/
def swapArgs (P : Prog Sg) : Prog Sg :=
  { P with lines := swapLines (swapSet P).contains 0 P.lines }

end RoleFlow