abbrev Elem := UInt32

def badRand (seed : Elem) : Elem :=
seed * 1664525 + 1013904223

def mkRandomArray : Nat → Elem → Array Elem → Array Elem
| 0,   seed, as => as
| i+1, seed, as => mkRandomArray i (badRand seed) (as.push seed)

partial def checkSortedAux (a : Array Elem) : Nat → IO Unit
| i =>
  if i < a.size - 1 then do
    unless (a[i]! <= a[i+1]!) do  throw (IO.userError "array is not sorted");
    checkSortedAux a (i+1)
  else
    pure ()

-- copied from stdlib, but with `UInt32` indices instead of `Nat` (which is more comparable to the other versions)
abbrev Idx := UInt32

macro:max "↑" x:term:max : term => `(UInt32.toNat $x)

@[specialize] private partial def partitionAux {α : Type} [Inhabited α] (lt : α → α → Bool) (hi : Idx) (pivot : α) : Array α → Idx → Idx → Idx × Array α
| as, i, j =>
  if j < hi then
    if lt (as[j.toNat]!) pivot then
      let as := as.swapIfInBounds ↑i ↑j;
      partitionAux lt hi pivot as (i+1) (j+1)
    else
      partitionAux lt hi pivot as i (j+1)
  else
    let as := as.swapIfInBounds ↑i ↑hi;
    (i, as)

set_option pp.all true
@[inline] def partition {α : Type} [Inhabited α] (as : Array α) (lt : α → α → Bool) (lo hi : Idx) : Idx × Array α :=
let mid : Idx := (lo + hi) / 2;
let as  := if lt (as[mid.toNat]!) (as[lo.toNat]!) then as.swapIfInBounds ↑lo ↑mid else as;
let as  := if lt (as[hi.toNat]!)  (as[lo.toNat]!) then as.swapIfInBounds ↑lo ↑hi  else as;
let as  := if lt (as[mid.toNat]!) (as[hi.toNat]!) then as.swapIfInBounds ↑mid ↑hi else as;
let pivot := as[hi.toNat]!;
partitionAux lt hi pivot as lo lo

@[specialize] partial def qsortAux {α : Type} [Inhabited α] (lt : α → α → Bool) : Array α → Idx → Idx → Array α
| as, low, high =>
  if low < high then
    let p   := partition as lt low high;
    -- TODO: fix `partial` support in the equation compiler, it breaks if we use `let (mid, as) := partition as lt low high`
    let mid := p.1;
    let as  := p.2;
    let as  := qsortAux lt as low mid;
    qsortAux lt as (mid+1) high
  else as

@[inline] def qsort {α : Type} [Inhabited α] (as : Array α) (lt : α → α → Bool) : Array α :=
qsortAux lt as 0 (UInt32.ofNat (as.size - 1))

def main (xs : List String) : IO Unit :=
do
let n := xs.head!.toNat!;
n.forM $ fun _ _ =>
n.forM $ fun i _ => do
  let xs := mkRandomArray i (UInt32.ofNat i) Array.empty;
  let xs := qsort xs (fun a b => a < b);
  --IO.println xs;
  checkSortedAux xs 0
