/-! # Permutations as data

A permutation of the entries of a vector of length `n` is a sequence of steps (`PermS`), each a
numpy operation on the vector reshaped, row-major, to a shape `dims` with `n` entries:

| step | numpy |
|---|---|
| `tr dims axes` | `x.reshape(dims).transpose(axes).reshape(-1)` |
| `flip dims k` | `jnp.flip(x.reshape(dims), k).reshape(-1)` |
| `roll dims k s` | `jnp.roll(x.reshape(dims), s, axis=k).reshape(-1)` |

The steps apply in order. Each acts as a gather: output position `i` reads input position
`idx i`, so the sequence `[p₁, p₂, …]` reads `p₁.idx (p₂.idx (… i))` (`PermS.idx`). The squeezes
and patchings of the models are transposes, their channel and token reversals flips, and their
rotations rolls.

Nothing here uses Mathlib: the flow signature (`Flow.Basic`) reads `PermS.idx` as an
`Equiv.Perm`, and the printer (`Flow.Print`) prints the steps. -/

namespace RoleFlow.Flow

/-- One step of a permutation, on the vector reshaped to `dims`. -/
inductive PermStep
  /-- Permute the axes: output axis `j` is input axis `axes[j]`. -/
  | tr (dims axes : List Nat)
  /-- Reverse axis `k`. -/
  | flip (dims : List Nat) (k : Nat)
  /-- Rotate axis `k` by `s`: output index `j` reads `j - s` (mod the axis length). -/
  | roll (dims : List Nat) (k s : Nat)
  deriving DecidableEq, Repr, Inhabited

/-- A permutation: its steps, applied in order. -/
abbrev PermS := List PermStep

namespace PermStep

/-- The row-major digits of `i` in the shape `dims`. -/
def digits (dims : List Nat) (i : Nat) : List Nat :=
  (dims.foldr (fun d (acc, j) => (j % d :: acc, j / d)) ([], i)).1

/-- The position of the digits `ds` in the shape `dims`. -/
def undigits (dims ds : List Nat) : Nat :=
  (dims.zip ds).foldl (fun acc (d, x) => acc * d + x) 0

def dims : PermStep → List Nat
  | .tr d _ | .flip d _ | .roll d _ _ => d

/-- The input position that output position `i` reads. -/
def idx : PermStep → Nat → Nat
  | .tr dims axes, i =>
    let e := digits (axes.map fun a => dims.getD a 1) i
    undigits dims ((List.range dims.length).map fun a => e.getD (axes.idxOf a) 0)
  | .flip dims k, i =>
    let ds := digits dims i
    undigits dims (ds.set k (dims.getD k 1 - 1 - ds.getD k 0))
  | .roll dims k s, i =>
    let ds := digits dims i
    let d := dims.getD k 1
    undigits dims (ds.set k ((ds.getD k 0 + d - s % d) % d))

/-- The step that undoes this one. -/
def inv : PermStep → PermStep
  | .tr dims axes => .tr (axes.map fun a => dims.getD a 1) ((List.range axes.length).map axes.idxOf)
  | .flip dims k => .flip dims k
  | .roll dims k s => let d := dims.getD k 1; .roll dims k ((d - s % d) % d)

/-- The step is well formed on vectors of length `n`: its shape has `n` entries, `axes` is a
permutation of the axes, and `k` is an axis. -/
def ok (n : Nat) (p : PermStep) : Bool :=
  p.dims.foldl (· * ·) 1 == n && match p with
    | .tr dims axes => axes.length == dims.length && (List.range dims.length).all axes.contains
    | .flip dims k | .roll dims k _ => k < dims.length

/-- The step on the middle of the shape `[outer] ++ dims ++ [inner]` (an axis of length `1` is
left out). -/
def embed (outer inner : Nat) (p : PermStep) : PermStep :=
  let pre := if outer = 1 then [] else [outer]
  let post := if inner = 1 then [] else [inner]
  let o := pre.length
  match p with
  | .tr dims axes =>
    .tr (pre ++ dims ++ post) ((List.range o) ++ axes.map (· + o) ++ (post.map fun _ => dims.length + o))
  | .flip dims k => .flip (pre ++ dims ++ post) (k + o)
  | .roll dims k s => .roll (pre ++ dims ++ post) (k + o) s

/-- The step does nothing: a transpose with the axes in order, a flip of an axis of length at most
`1`, a roll by a multiple of the axis length. -/
def isId : PermStep → Bool
  | .tr _ axes => axes == List.range axes.length
  | .flip dims k => dims.getD k 1 ≤ 1
  | .roll dims k s => s % dims.getD k 1 == 0

/-- The step in the surface syntax of the webapp, before its argument: `tr<[2, 3], [1, 0]>`. -/
def text : PermStep → String
  | .tr dims axes => s!"tr<{dims}, {axes}>"
  | .flip dims k => s!"flip<{dims}, {k}>"
  | .roll dims k s => s!"roll<{dims}, {k}, {s}>"

end PermStep

namespace PermS

/-- The steps applied to `x`, in the surface syntax of the webapp. -/
def text (x : String) (ps : PermS) : String := ps.foldl (fun x p => s!"{p.text}({x})") x

/-- The input position that output position `i` reads. -/
def idx (ps : PermS) (i : Nat) : Nat := ps.foldr PermStep.idx i

/-- The steps that undo `ps`. -/
def inv (ps : PermS) : PermS := ps.reverse.map PermStep.inv

/-- Every step is well formed on vectors of length `n`. -/
def ok (n : Nat) (ps : PermS) : Bool := ps.all (PermStep.ok n)

/-- The index list `[idx 0, …, idx (n - 1)]`. -/
def idxList (n : Nat) (ps : PermS) : List Nat := (List.range n).map ps.idx

/-- `ps` is a permutation of `0, …, n - 1`: well formed, and its index list is one. -/
def valid (n : Nat) (ps : PermS) : Bool :=
  ps.ok n && (let l := ps.idxList n
    let seen := l.foldl (fun (a : Array Bool) j => if j < n then a.set! j true else a)
      (Array.replicate n false)
    l.all (· < n) && seen.all id)

end PermS

end RoleFlow.Flow