///|
/// fmat (LapackMat, FixedArray[Byte] backed) element-wise ops:
/// scalar manual byte decode (the old fmat_add implementation) vs
/// vDSP zero-copy (the new implementation).
///
/// This is the apples-to-apples comparison: same storage, different op.
/// The vec_accelerate_bench Array[Float] case loses to scalar because of
/// the FFI byte-encoding hop; here both paths skip that hop.

///|
fn make_fmat(n : Int, seed : Int) -> LapackMat {
  let arr : Array[Float] = Array::make(n * n, Float::from_int(0))
  let mut x = seed
  for i = 0; i < n * n; i = i + 1 {
    x = x * 1103515245 + 12345
    arr[i] = Float::from_int((x >> 16) & 0x7fff) / 32768.0
  }
  fmat_from_mat(mat_view(arr, n, n))
}

///|
/// The pre-Accelerate fmat_add: inline byte decode + manual loop on bytes.
/// Kept for reference / parity check.
fn fmat_add_scalar_baseline(a : LapackMat, b : LapackMat) -> LapackMat {
  let c = fmat_zeros(a.rows, a.cols)
  let n = a.rows * a.cols
  for i = 0; i < n; i = i + 1 {
    let idx = i * 4
    let a_bits = a.data[idx].to_int() |
      (a.data[idx + 1].to_int() << 8) |
      (a.data[idx + 2].to_int() << 16) |
      (a.data[idx + 3].to_int() << 24)
    let b_bits = b.data[idx].to_int() |
      (b.data[idx + 1].to_int() << 8) |
      (b.data[idx + 2].to_int() << 16) |
      (b.data[idx + 3].to_int() << 24)
    let sum = Float::reinterpret_from_int(a_bits) +
      Float::reinterpret_from_int(b_bits)
    let sum_bits = sum.reinterpret_as_int()
    c.data[idx] = (sum_bits & 0xFF).to_byte()
    c.data[idx + 1] = ((sum_bits >> 8) & 0xFF).to_byte()
    c.data[idx + 2] = ((sum_bits >> 16) & 0xFF).to_byte()
    c.data[idx + 3] = ((sum_bits >> 24) & 0xFF).to_byte()
  }
  c
}

///|
fn fmat_sum_scalar_baseline(a : LapackMat) -> Float {
  let mut sum = Float::from_int(0)
  let n = a.rows * a.cols
  for i = 0; i < n; i = i + 1 {
    let idx = i * 4
    let bits = a.data[idx].to_int() |
      (a.data[idx + 1].to_int() << 8) |
      (a.data[idx + 2].to_int() << 16) |
      (a.data[idx + 3].to_int() << 24)
    sum = sum + Float::reinterpret_from_int(bits)
  }
  sum
}

///|
test "bench: fmat_add scalar (byte-decode) vs vDSP, 32x32 / 128x128 / 512x512" (
  t : @bench.T,
) {
  let sizes = [32, 128, 512]
  for s = 0; s < sizes.length(); s = s + 1 {
    let dim = sizes[s]
    let fa = make_fmat(dim, 1)
    let fb = make_fmat(dim, 2)
    t.bench(name="fmat_add scalar   \{dim}x\{dim}", fn() {
      let _ = fmat_add_scalar_baseline(fa, fb)
    })
    t.bench(name="fmat_add vDSP     \{dim}x\{dim}", fn() {
      let _ = fmat_add(fa, fb)
    })
  }
}

///|
test "bench: fmat_sum scalar (byte-decode) vs vDSP, 32x32 / 128x128 / 512x512" (
  t : @bench.T,
) {
  let sizes = [32, 128, 512]
  for s = 0; s < sizes.length(); s = s + 1 {
    let dim = sizes[s]
    let fa = make_fmat(dim, 1)
    t.bench(name="fmat_sum scalar   \{dim}x\{dim}", fn() {
      let _ = fmat_sum_scalar_baseline(fa)
    })
    t.bench(name="fmat_sum vDSP     \{dim}x\{dim}", fn() {
      let _ = fmat_sum(fa)
    })
  }
}

///|
/// In-place variants: same as above but reuse a pre-allocated output buffer.
/// This isolates pure compute time (no fmat_zeros allocation per call).
test "bench: fmat_add_into in-place vDSP, 32x32 / 128x128 / 512x512" (
  t : @bench.T,
) {
  let sizes = [32, 128, 512]
  for s = 0; s < sizes.length(); s = s + 1 {
    let dim = sizes[s]
    let fa = make_fmat(dim, 1)
    let fb = make_fmat(dim, 2)
    let out = fmat_zeros(dim, dim)
    t.bench(name="fmat_add_into vDSP \{dim}x\{dim}", fn() {
      fmat_add_into(fa, fb, out~)
    })
  }
}